Todo List App with SwiftUI 3.0 (簡単なTodoList Appを作ってみました。)

# 簡単なTodoList Appを作ってみました。
PHPでの開発は3年以上しましたが、Swiftは最近学び始めて、ほぼ知識がない状態でTodoList App tutorialをしてみました。
Youtubeの SwiftUI + MVVMでTodoList Appを作る動画を真似してTodoList Appを作ってみました。
Todo List App with SwiftUI 3.0 | MVVM(https://www.youtube.com/watch?v=1SsRsDoC_eI)

---


## 実装した物
`Model`, `ViewModel`, `View`を作りました。
```
├── App
│   ├── Models
│   │   └── TodoModel.swift
│   ├── TodoListApp.swift
│   ├── ViewModels
│   │   ├── AddTodoViewModel.swift
│   │   └── TodoListViewModel.swift
│   └── Views
│       ├── AddTodoView.swift
│       ├── TodoListRowView.swift
│       └── TodoListView.swift
```

- TodoModel : `Todo`の`Model`(id, title, 完了ステータスを管理) 
```
│   ├── Models
│   │   └── TodoModel.swift
```

- AddTodoViewModel : `Todo`が登録される時に行う動作を持っている`Model`(チェック, 入力したTodo取得)
- TodoListViewModel : `Todo`が登録される時に行う動作を持っている`Model` (TodoListViewの動作時のロジック管理)

```
│   ├── ViewModels
│   │   ├── AddTodoViewModel.swift
│   │   └── TodoListViewModel.swift
```

- TodoListView　→ TodoListの一覧 (編集ボタン, Todo表示, Todo登録ボタン)
- TodoListRowView　→ Todo(やること,　やったかのチェックマーク)
- AddTodoView → Todo登録画面(text入力フォーム, Saveボタン)
```
│   └── Views
│       ├── AddTodoView.swift
│       ├── TodoListRowView.swift
│       └── TodoListView.swift
```

--- 

# コード的な背景知識(調べた内容 +個人的な考え)

## 構造体（struct）とは

Swiftにはクラスとよく似た機能として構造体（struct）があります。

構造体（struct）とは、クラスと同様にカプセル化を実現する方法として提供されている機能です。

```
struct 構造体名 {
    var num1:Int;
    var num2:Int = 100;
    var str:String;
    init(value: Int) {
        val = 150
    }
}
```

```
structは継承ができない。
structはデイニシャライザ（クラスのインスタンス破棄）ができない。
classは参照型、structは値渡し。
```

## イニシャライザの定義

initというメソッドでイニシャライザを定義することができる

swiftではクラスの方もinitを書いてイニシャライザを定義できる

```swift
//structの定義
struct Area {
    var radius: Int    // 半径
    var pi: Double     // 円周率
    // イニシャライザ
    init(radius: Int, pi: Double) {
        self.radius = radius
        self.pi = pi
    }
}
```
 
class のほうが多機能である分複雑で、
 
Swift の公式ドキュメントでは特に必要がない限り struct を使うことがおすすめされています。


## Manageing Model Data

(https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app)

- ObservedObject    iOS 13+
- EnvironmentObject  iOS 13+
- StateObject  iOS 14+ 

Modelクラスを定義した時にViewの中でModelを呼ぶためのConnectionを提供してくれるのが上記のObejctの役割

各Objectの差に対してはまだよくわからないです。

Viewの中で定義する場合は StateObject, Classで定義する場合はObservableObjectを使うという認識でした。

## $0とはなんでしょう？

$0とはクロージャの第一引数を表す記号

こちらで確認できます。

https://kichie-com.hatenablog.com/entry/2017/02/01/194217

## 配列に要素を追加する場合

+= 演算子あるいはappendメソッドを使う

```swift
var strArray = ["Google", "Apple", "Facebook"]
 
strArray += ["Twitter"]
strArray.append("Instagram")
 
print(strArray) // ["Google", "Apple", "Facebook", "Twitter", "Instagram"]
```

---

# View的な部分(調べた内容 + 個人的な考え)

 ## ViewControllerとは(今回は使われてない)
 VIewControllerは、名前の通り表示されるViewを管理・操作(表示・非表示・配置・アニメーションなど)をする役割を持つクラスです。

 ViewControllerでは表示, 操作を管理する役割ができます。
 その中で viewDidLoadedは画面がロードされるイベントが発生した時に行う動作を定義するfunctionです。

![画像](https://camo.qiitausercontent.com/af7b161dbba4c2644ca996ec74b7bf4f53712b41/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f34353532352f36373064303033382d366630332d303935662d636132322d3930633531306638626162662e706e67)


 ## SwiftUI : Viewの定義
 HTMLのタグみたいにViewファイルの内部で text, image, buttonなどを定義するとクライエントの画面で表示する要素を作れる
 要素のデザインはメソッドチェイニングみたいに.backgroundColorみたいに定義して変えられる


 ## SwiftUI ： Stack
```
VStack・・・垂直方向にViewを並べる
HStack・・・水平方向にViewを並べる
ZStack・・・Viewを重ねる
```
 
 ## SwiftUI : List 
 Listはデータの一覧表示をするのに適したViewです。
画面に収まらない量の場合はスクロール表示になるなど、UIKitのUITableViewに似ていますが、はるかに簡単に使えます。
ListはForEachとセットで使うケースが多い

動的にリストを生成する時には以下のように
```swift
struct ContentView: View {
    let fruits = ["りんご", "オレンジ", "バナナ"]
    var body: some View {
        List {
            ForEach(0 ..< fruits.count) { index in
                Text(fruits[index])
            }

        }
    }
}
```

```
List{
    ForEach(HogeVM.hogeList) {item in
        HogeView(hoge: item)
    }.onDelete{
        //リストから削除された時に行う挙動
    }.onMove{
        // リストから変更された時に行う挙動
    }
}
```


## SwiftUI : NavigationBar にボタンや画像を配置する
```swift
List {
    
}.navigationBarTitle(Text("ほげほげ画面"), displayMode: .inline)
 .navigationBarItems(
     leading: Text("左"),
     trailing: Text("右")
 )
```

leadingかtrailingのところにEditButton()を入れるとlistの編集ボタンができます。


## SwiftUI : NavigationLink 画面の遷移を設定できる

NavigationLink(destination{押下時遷移されるところ}, label:{Text:リンクを表示するテキスト})

```swift
List {
    
}.navigationTitle("Todo List")
    .navigationBarItems(leading: EditButton(),
    trailing: NavigationLink(destination: { HogeView() }, label: {Text("Add Hoge")}))
```

--- 
# 実装したソースコード

## TodoModel
 初期化時に `uuidString`を生成してidがユニクなTodoを作ってくれる
 `onCompletedToggle()`はTodoの完了チェックボタン押下時の処理, isCompletedフラグを変えてreturn

```swift
import Foundation

struct TodoModel: Identifiable {
    let id: String
    let title: String
    let isCompleted: Bool
    
    init(id: String = UUID().uuidString, title: String, isCompleted: Bool) {
        self.id = id
        self.title = title
        self.isCompleted = isCompleted
    }
    
    func onCompletedToggle() -> TodoModel{
        return TodoModel(id: id, title: title, isCompleted: !isCompleted)
    }
}

```

## TodoListViewModel
TodoListのViewで行う処理的な部分をまとめているmodel

- onMove : ソート
- onDelete : 削除
- updateItem : 更新
- onSave : 登録 （Todoのidが被った時の防止策処理が入っているという認証でした。）


```swift
import Foundation

class TodoListViewModel: ObservableObject {
    @Published var todoList: [TodoModel] = []
    
    init(){
        todoList.append(contentsOf: [
            TodoModel(title: "Item 1", isCompleted: false),
            TodoModel(title: "Item 2", isCompleted: false)
        ])
    }
    
    func onMove(indexSet: IndexSet, to: Int) {
        todoList.move(fromOffsets: indexSet, toOffset: to)
    }
    
    func onDelete(indexSet: IndexSet) {
        todoList.remove(atOffsets: indexSet)
    }
    
    func updateItem(item: TodoModel) {
        if let index = todoList.firstIndex(where: {$0.id == item.id}) {
            todoList[index] = item
        }
    }
    
    func onSave(item: TodoModel) {
        if let index = todoList.firstIndex(where: {$0.id == item.id}) {
            todoList[index] = item
            return
        }
        todoList.append(item)
    }
}

```

## TodoListView

TodoListの一覧, Editボタン, 登録ボタンを表示するためのViewファイル`TodoListApp.swift`で呼ぶと
アプリを開いた時にこちらを表示してくれる

Webだとしたら`HTML`的なもので`Model`の`todolist`の情報をForeachでListのRowで表示してくれる
また画面のボタンを押下した時に`ViewModel`の`func`を使ってどういう動作をするかを定義している

```swift

import SwiftUI

struct TodoListView: View {
    
    @StateObject var todoListVM = TodoListViewModel()
    
    
    var body: some View {
        List{
            ForEach(todoListVM.todoList) {item in
                TodoListRowView(
                        todo: item,
                        onCompletedToggle: {
                            todoListVM.updateItem(item: item.onCompletedToggle())
                    }
                )
            }
                .onDelete(perform: todoListVM.onDelete)
                .onMove(perform: todoListVM.onMove)
        }.navigationTitle("Todo List")
            .navigationBarItems(leading: EditButton(), trailing: NavigationLink(destination: {
                AddTodoView { todo in
                    todoListVM.onSave(item: todo)
                }
            }, label: {
              Text("Add items")
            }))
    }
}

struct TodoListView_Previews: PreviewProvider {
    static var previews: some View {
        NavigationView{
            TodoListView()
        }
    }
}
```


## AddTodoViewModel

`登録`する時の`View`の動作, 処理を持っている`Model`

- canSave : 最大件数を確認して登録できるかを判別
- getTodo : 登録する時に登録画面に書いたTodo情報を取得

```swift
import Foundation

class AddTodoViewModel: ObservableObject {
    @Published var title: String = ""
    
    func canSave () -> Bool {
        if title.isEmpty {
            return false
        }else if title.count < 5 {
            return false
        }
        return true
    }
    
    func getTodo(id: String = UUID().uuidString) -> TodoModel {
        return TodoModel(id: id, title: title, isCompleted: false)
    }
}

```

## AddTodoView

Todo登録画面でテキスト入力フォームと登録ボタンがある
TodoListViewと同様で表示とボタン押下時のイベントを管理している`HTML`みたいな表示テンプレートの役割

```swift
import SwiftUI

struct AddTodoView: View {
    @Environment(\.presentationMode) var presentationMode
    
    let onSave: (_ todo: TodoModel) -> Void
    let id: String = UUID().uuidString
    @StateObject var addTodoVM = AddTodoViewModel()
    
    var body: some View {
        VStack {
            ScrollView {
                TextField("Todo", text: $addTodoVM.title)
                    .padding()
                    .background(Color(UIColor.secondarySystemBackground))
                    .cornerRadius(12)
                    .onSubmit {
                        onSaveClick()
                    }
            }
            Button {
                if addTodoVM.canSave() {
                    onSaveClick()
                }
            } label: {
                Text("Save")
                    .foregroundColor(.white)
                    .font(.headline)
                    .frame(height: 56)
                    .frame(maxWidth: .infinity)
                    .background(.primary)
            }
        }.navigationTitle("Add Todo")
    }
    
    func onSaveClick() {
        let todo: TodoModel = addTodoVM.getTodo(id: id)
        onSave(todo)
        presentationMode.wrappedValue.dismiss()
    }
}

struct AddItemView_Previews: PreviewProvider {
    static var previews: some View {
        NavigationView {
            AddTodoView{ todo in
                
            }
        }
    }
}
```

--- 
# 実際動かしてみた。

## 一覧表示
![스크린샷 2022-06-07 09.34.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2620841/22373362-5fe1-5109-e383-e66434712ade.png)

## 削除
![스크린샷 2022-06-07 09.34.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2620841/fb6dcc7a-1b18-c9a8-4ccf-39a5ef80b45d.png)

## 登録
![스크린샷 2022-06-07 09.35.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2620841/f9a639ef-ac67-ca7c-44a9-bc6b7450005e.png)

## Todo完了
![스크린샷 2022-06-07 09.35.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2620841/e0108d12-55e8-80f8-563c-b2d393f52026.png)

## ソート（下から上に変更）
![스크린샷 2022-06-07 09.36.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2620841/6e2c0577-c7b5-cb22-3cd9-0910c438864b.png)


---



## 感想

- MVVMアーキテクチャ, SwiftUIでTodoListAPPを作ってみる動画をまねしながら色々勉強しました。
- ViewModelはViewを完全にデザイン的な両駅にしたいという観点, Web開発だったらJS的なポジションだと思いました。
- xcodeの開発する時にcanvasにpreviewがすぐ反映されるのは良かったが、以外にcanvas気になって書くときも見ました。
- TodoListAppのtutorialでDBは使ってなかったですが、CRUD的な機能を味わったから今後DBと一緒に動かせてみたいです。