잡초의 일지

[Swift] [SwiftUI] Todo - List 만들기 본문

Just for fun/iOS

[Swift] [SwiftUI] Todo - List 만들기

JabCho 2021. 7. 12. 13:52
728x90
반응형
SMALL

개요

온전한 내 힘으로 MVVM 패턴을 적용한 무언가를 만들고 싶다는 생각이 들었다.

가장 쉬운 todolist를 만들어보기로 했다.

완성본

https://youtu.be/0zDHYsUIRpg

이 영상을 올리려고 처음으로 유튜브에 동영상을 올려봤다..ㅎㅎ

디자인 & 기능

아이디어의 러프 스케치는 아래와 같다.

알람이나 날짜 기록 등의 기능은 제외하고, 주요 기능만 추려 단순하게 만들었다.

코드 내용

MVVM 패턴으로 구현하려고 노력하였다.

완료한 일을 체크하는 코드를 짤 때, 이미 Todo객체로 만들어진 것을 어떻게 수정해야 할 지 고민이었다. 각 todo를 하나의 view로 표현하여 여러 셀들로 표현해야 하나 생각했다. 하지만, 이것은 기존의 코드를 많이 수정해야 했다. 따라서, modifyTodoDone이라는 함수를 추가하였다. 기존 TodoList에서 unique한 id값을 매개변수로 하여 인덱스를 찾고, 해당 todo에 대해서 replaceSubrange를 사용하여 변경된 값을 가진 새로운 Todo객체로 변경하였다.  배열로 표현된 TodoList의 크기가 크지 않을것이라고 판단하여,(투두리스트에 적는 일이 아주 많지 않을것이다..) for문을 사용하였다.

클릭 시 버튼의 label이나 글자색이 변하는 등의 변화를 표현하기 위해, 삼항연산자를 사용하였다.

Setting 뷰에 대한 것은 아직 구상중이다.

+) TodoList에 'delete all' 같은 버튼을 만들어주면, 일일이 지우지 않아도 되기 때문에 더 편할것 같다.

 

<View>

import SwiftUI

struct ContentView: View {
    var body: some View {
        TabView {
                   TodoView()
                     .tabItem {
                        Image(systemName: "checkmark.circle.fill")
                        Text("Todo")
                      }

                   SettingView()
                     .tabItem {
                        Image(systemName: "gear")
                        Text("Setting")
                      }
                }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
import SwiftUI
import Combine

struct TodoView: View {
    @ObservedObject var todoStore = TodoStore()
    @State var newTodo: String = ""
    @State var isImportant: Bool = false
    
    struct ImportantTodo: ViewModifier {
        var importantTodoColor = Color.pink
        
        func body(content: Content) -> some View {
            return content
                .padding(.vertical, 5)
                .foregroundColor(importantTodoColor)
        }
    }
    
    var addTodoBar: some View{
        HStack{
            Button(action: {
                isImportant.toggle()
            }, label: {
                Image(systemName: "exclamationmark.triangle.fill")
                    .foregroundColor(Color.orange)
            })
  
            TextField("Enter new tasks", text: self.$newTodo)
                .padding(5)
                .background(RoundedRectangle(cornerRadius: 5).fill(isImportant ?  Color.orange : Color(red: 0.0, green: 0.0, blue: 0.0, opacity: 0.2)))
            
            Button(action: {
                    todoStore.addNew(newTodo, isImportant)
                    newTodo = ""
                    isImportant = false
                }, label: {
                    Image(systemName: "plus.circle.fill")
                        .foregroundColor(Color.red)
                })
        }
    }
    
    var body: some View {
        NavigationView {
            VStack{
                addTodoBar
                    .padding()
                
                List{
                    ForEach(self.todoStore.todos) { todo in
                        HStack{
                            Button(action: {
                                todoStore.modifyTodoDone(todo.id)
                            }, label: {
                                Image(systemName: todo.done ? "largecircle.fill.circle" : "circle")
                                    .foregroundColor(Color.gray)
                            })
                            
                            Text(todo.todoItem)
                                .modifier(todo.done ? ImportantTodo(importantTodoColor: Color.gray) : (todo.important ? ImportantTodo() : ImportantTodo(importantTodoColor: Color.primary)))
                        }
                    }
                    .onMove(perform: self.todoStore.move)
                    .onDelete(perform: self.todoStore.delete)
                }
            }
            .navigationBarTitle("Todo List")
            .navigationBarItems(trailing: EditButton())
        }
        .onAppear(perform: todoStore.loadTodos)
    }
}

struct TodoView_Previews: PreviewProvider {
    static var previews: some View {
        TodoView()
    }
}
import SwiftUI

struct SettingView: View {
    var body: some View {
        VStack(alignment: .leading){
                Text("Setting")
                    .font(.title)
                    .fontWeight(.bold)
                    .multilineTextAlignment(.leading)
                    .padding(5)
        }
        
    }
}

struct SettingView_Previews: PreviewProvider {
    static var previews: some View {
        SettingView()
    }
}

<ViewModel>

import SwiftUI
import Combine

class TodoStore: ObservableObject{
    @Published var todos = [Todo]()
    private let todosKey = "TodoListKey"

    func addNew(_ newTodo:String, _ important:Bool){
        guard !newTodo.isEmpty else { return }
        if important{
            self.todos.insert(Todo(todoItem: newTodo, important: important), at: 0)
        }else{
            self.todos.append(Todo(todoItem: newTodo, important: important))
        }
        saveTodos()
    }
    
    func modifyTodoDone(_ id:UUID){
        for i in todos.indices {
            if (todos[i].id==id){
                let newDone:Bool = todos[i].done ? false : true
                let modifiedTodo = Todo(id: todos[i].id, todoItem: todos[i].todoItem, important: todos[i].important, done: newDone)
                todos.replaceSubrange(i...i, with: repeatElement(modifiedTodo, count: 1))
                break
            }
        }
        saveTodos()
    }
    
    func move(from source: IndexSet, to destination: Int){
        self.todos.move(fromOffsets: source, toOffset: destination)
    }

    func delete(at offests: IndexSet){
        self.todos.remove(atOffsets: offests)
        saveTodos()
    }
    
    // MARK:- Store TodoList data using UserDefaults
    func saveTodos() {
      UserDefaults.standard.set(try? PropertyListEncoder().encode(self.todos), forKey: todosKey)
    }
     
    func loadTodos() {
      if let todoData = UserDefaults.standard.value(forKey: todosKey) as? Data {
        if let todoList = try? PropertyListDecoder().decode(Array<Todo>.self, from: todoData) {
          self.todos = todoList
        }
      }
    }
}

<Model>

import SwiftUI

struct Todo: Identifiable, Codable{
    var id = UUID()
    let todoItem: String
    let important: Bool
    var done: Bool = false
}

참고

더보기

 

728x90
반응형
LIST
Comments