【SwiftUI】カウントダウン＋TODOアプリを作ってみた（初）

# はじめに
Qiita初投稿です。
勉強中のSwiftUIで自分の為にAPPを作りたい！
そんな感じです。

# 何を作ったの？
TODOリストにタイマーをくっつけたもの。
初めてだから無理せずシンプルに！

# 開発環境
Swift version 5.3.1
Xcode Version 12.5.1

# code
~~~swift
    import SwiftUI

    struct TodoModel: Codable, Identifiable {
        var id = UUID()
        let todo: String
        var imcomplete: Bool
        
        init(todo: String, imcomplete: Bool) {
            self.todo = todo
            self.imcomplete = imcomplete
        }
    }

    class TimerModel: ObservableObject {
        @Published var deadLine : Date {
            didSet {
                UserDefaults.standard.set(deadLine, forKey: "deadLine")
            }
        }
        init() {
            deadLine = UserDefaults.standard.object(forKey: "deadLine") as? Date ?? Date()
        }
    }

    struct ContentView: View {
        
        init() {
                UITableView.appearance().backgroundColor = UIColor.clear
            }

        
        @State private var newTodo = ""
        @State private var allTodos: [TodoModel] = []
        @EnvironmentObject var timerModel: TimerModel
        @State var nowDate: Date = Date()
        
        var timer: Timer {
            Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {_ in
                self.nowDate = Date()
            }
        }
        
        var body: some View {
            NavigationView {
                VStack {
                    VStack {
                        HStack {
                            DatePicker("", selection: $timerModel.deadLine, in: Date()...).labelsHidden()
                        }
                        Text("期限まであと")
                            .frame(maxWidth: .infinity, alignment: .center)
                        let timerFunc = TimerFunc(self.timerModel.deadLine)
                        HStack {
                            if timerFunc.day <= 0,
                               timerFunc.hour <= 0,
                               timerFunc.minute <= 0,
                               timerFunc.second <= 0 {
                                Text("Time Up!")
                                    .fontWeight(.bold)

                            } else if timerFunc.day <= 0,
                                      timerFunc.hour <= 0,
                                      timerFunc.minute <= 0 {
                                Text("\(timerFunc.second)")
                                    .fontWeight(.bold)
                            } else if timerFunc.day <= 0,
                                      timerFunc.hour <= 0 {
                                Text("\(timerFunc.minute):")
                                    .fontWeight(.bold)
                                Text("\(timerFunc.second)")
                                    .fontWeight(.bold)
                            } else if timerFunc.day <= 0 {
                                Text("\(timerFunc.hour):")
                                    .fontWeight(.bold)
                                Text("\(timerFunc.minute):")
                                    .fontWeight(.bold)
                                Text("\(timerFunc.second)")
                                    .fontWeight(.bold)
                            } else {
                                VStack {
                                    Text("\(timerFunc.day) day")
                                        .font(.largeTitle)
                                        .fontWeight(.bold)
                                    HStack {
                                        Text("\(timerFunc.hour):")
                                            .fontWeight(.bold)
                                        Text("\(timerFunc.minute):")
                                            .fontWeight(.bold)
                                        Text("\(timerFunc.second)")
                                            .fontWeight(.bold)

                                    }
                                }
                            }
                        }.onAppear(perform: {_ = self.timer}).font(.title)
                    }
                    
                    List {
                        ForEach (0..<allTodos.count, id: \.self) { index in
                            HStack {
                                Image(systemName: allTodos[index].imcomplete ? "square" : "checkmark.square.fill")
                                    .foregroundColor(.blue)
                                Text(self.allTodos[index].todo)
                                Spacer()
                                if allTodos[index].imcomplete {
                                    Text("")
                                } else {
                                    Text("complete!")
                                        .foregroundColor(.blue)
                                }
                            }.onTapGesture {
                                allTodos[index].imcomplete.toggle()
                                self.saveTodos()
                            }
                        }.onDelete(perform: deleteTodos)
                        HStack {
                            TextField("Add todo", text: $newTodo)
                            Button(action: {
                                guard !self.newTodo.isEmpty else { return } 
                                self.allTodos.append(TodoModel(todo: self.newTodo, imcomplete: true))
                                self.newTodo = ""
                                self.saveTodos()
                            }) {
                                Image(systemName: "plus")
                                    .foregroundColor(.blue)
                            }
                        }
                    }
                }
            }
            .onAppear(perform: loadTodos)
        }
        
        func TimerFunc(_ date:Date) -> (day: Int, hour: Int, minute: Int, second: Int) {
            let cal = Calendar(identifier: .japanese)
            
            let timeVal = cal.dateComponents([.day, .hour, .minute, .second], from: nowDate, to: timerModel.deadLine)
            
                return (Int(timeVal.day ?? 0),
                        Int(timeVal.hour ?? 0),
                        Int(timeVal.minute ?? 0),
                        Int(timeVal.second ?? 0))
        }
        
        private func saveTodos() {
            UserDefaults.standard.set(try? PropertyListEncoder().encode(self.allTodos), forKey: "todosKey")
        }
        
        private func loadTodos() {
            if let todosData = UserDefaults.standard.value(forKey: "todosKey") as? Data {
                if let todosList = try? PropertyListDecoder().decode(Array<TodoModel>.self, from: todosData) {
                    self.allTodos = todosList
                }
            }
        }
        
        private func deleteTodos(at offsets: IndexSet) {
            self.allTodos.remove(atOffsets: offsets)
            saveTodos()
        }
    }

    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
                .environmentObject(TimerModel())
        }
    }
~~~
# つまずいた！
① **userdefaltsに保存できない**
　いろいろ調べた結果、構造体及びタプル型は保存出来ないらしいので***Codable***を使って処理
https://ichi.pro/swiftui-chu-toriaru-saisho-no-ios-apuri-o-sakuseisuru-212038845663015

② **チェックボックスの動きと処理**
　***.onTapGesture***で解決

③ **タイマーの処理と表示**
参考にしたページを真似すると「**0day0:0:1**」とかになり「**0**」が邪魔なので改造した。
しかしながら、それが原因で冗長に……
https://betterprogramming.pub/make-a-simple-countdown-with-timer-and-swiftui-3ce355b54986
https://qiita.com/yuujioka/items/53f555ccba5e97bedb59
https://mt312.com/785

# 今後はどうするの？
せっかくなので元グラフィックデザイナーを生かして見た目もイケてるものにupdateしていこうと思う。
機能的には十分だが、タイマーを複数個別に走らせて複数のタスクリストをもてるようにしたいかな〜