NCMBのSwift SDKを使って掲示板アプリを作る（その2：スレッドの作成と一覧表示）

ニフクラ mobile backend（NCMB）では各種言語向けにSDKを提供しています。その中で最も新しいSwift SDKについて、その使い方を紹介します。

この記事では複数回に分けて、掲示板アプリを作ってみます。Swift SDKの使い方はもちろん、NCMBの基本的な利用法を学ぶのに役立ててください。[前回は画面の説明と匿名認証の実装を行いました](https://qiita.com/goofmint/items/35631d9e8cb261c322f8)ので、今回はスレッドの作成と一覧表示を作成していきます。

なお、今回のコードは[NCMBMania/Swift_Forum_Demo: Swift SDKで作る掲示板アプリのデモコードです](https://github.com/NCMBMania/Swift_Forum_Demo)にアップしてあります。

## スレッド一覧画面について

![ScreenShot_ 2021-08-06 18.36.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/daacd996-7dd9-4174-f22d-68b44dcb4934.png)


前回も紹介しましたが、再度スレッド一覧画面 `ThreadListView` を紹介します。この画面で、プラスアイコンをタップすると `AddThreadView` が表示されます。

```swift
struct ThreadListView: View {
    @State var threads: [NCMBObject] = []
    @State private var showModal = false
    @State private var showAlert = false
    
    var body: some View {
        NavigationView {
            ZStack(alignment: .bottomTrailing) {
                List {
                    ForEach(self.threads, id: \.objectId) { thread in
                        NavigationLink(
                            destination: ThreadView(thread: thread)
                        ) {
                            ThreadListRow(thread: thread)
                        }
                    }
                    .onDelete(perform: delete)
                }
                .navigationBarTitle("掲示板", displayMode: .inline)
                .navigationBarItems(trailing:
                                        Button(action: {
                                            showModal.toggle()
                                        }, label: {
                                            Image(systemName: "plus")
                                                .resizable()
                                                .padding(6)
                                                .frame(width: 24, height: 24)
                                                .foregroundColor(.blue)
                                        })
                )
            }
        }
        .onAppear {
            getThread()
        }
        .sheet(isPresented: $showModal, content: {
            AddThreadView()
        })
        .onChange(of: showModal, perform: { value in
            if (!showModal) {
                getThread()
            }
        })
        .alert(isPresented: $showAlert, content: {
            Alert(title: Text("削除に失敗しました。権限がないようです。"))
        })
    }
}
```

`AddThreadView` の内容は次のようになっています。スレッドのタイトル、説明、そして画像を追加できるようになっています。情報を入力してスレッド追加ボタンをタップすると `add` 関数が呼ばれます。

![ScreenShot_ 2021-08-06 18.36.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/ab55f865-aeef-7150-5e1b-2b5228991b05.png)


```swift
struct AddThreadView: View {
    @Environment(\.presentationMode) private var presentationMode
    @State private var title = ""
    @State private var description = ""
    @State var showingPicker = false
    @State var image: UIImage?
    var body: some View {
        VStack {
            NavigationView {
                Form {
                    TextField("スレッドのタイトルを入力してください", text: $title)
                    TextField("スレッドの紹介文を入力してください", text: $description)
                    Button(action: {
                        showingPicker.toggle()
                    }) {
                        Text("画像追加")
                    }
                    if let image = image {
                        Image(uiImage: image)
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                    }
                    Button(action: {
                        add()
                    }) {
                        Text("スレッド追加")
                    }
                }
            }
        }
        .sheet(isPresented: $showingPicker) {
            ImagePickerView(image: $image, sourceType: .library)
        }
    }
}
```

### add関数について

add関数の内容です。詳細はコメントを参考にしてください。

```swift
func add() {
    // スレッドクラスのNCMBObjectを作成
    let thread = NCMBObject(className: "Thread")
    // スレッドのタイトルと説明をセット
    thread["title"] = title
    thread["description"] = description
    // ACL（アクセス権限）を作成
    let user = NCMBUser.currentUser // ログインユーザの取得
    var acl = NCMBACL.empty
    // 誰でも読み込み可能、削除不可
    acl.put(key: "*", readable: true, writable: false)
    // 作成したユーザは読み書き可能
    acl.put(key: user!.objectId!, readable: true, writable: true)
    // ACLをセット
    thread.acl = acl
    // 画像が指定されている場合
    if let image = image {
        // ファイル名はUUIDでユニークなもの
        let uuid = UUID()
        // ファイル名
        let fileName = "\(uuid).jpg"
        // ファイルストアのオブジェクトを作成
        let photo = NCMBFile(fileName: fileName, acl: acl)
        // JPEGとしてデータを保存
        _ = photo.save(data: image.jpegData(compressionQuality: 80.0)!)
        // 保存したファイル名をNCMBObjectにセット
        thread["fileName"] = fileName
    }
    // 保存
    let results = thread.save()
    // 処理が成功したらモーダルを閉じる
    if case .success(_) = results {
        presentationMode.wrappedValue.dismiss()
    } else {
        print("Error")
    }
}
```

### スレッド一覧の取得

![ScreenShot_ 2021-08-07 11.31.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/0c877cd3-e896-fc7c-335a-d93f5c984cf3.png)


スレッド一覧の取得は `ThreadListView` の `getThread` 関数になります。こちらも実装はコメントを参照してください。

```swift
func getThread() {
    // スレッドクラスのNCMBQueryを準備
    let query = NCMBQuery.getQuery(className: "Thread")
    // 取得
    let results = query.find()
    switch results {
    case let .success(ary): // 取得成功した場合
        threads = ary // スレッドに適用
    case .failure(_): break
    }
}
```

これでスレッドを登録し、一覧表示まで行われるようになります。

## スレッドの削除

![ScreenShot_ 2021-08-07 16.45.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/0aa443e5-59c0-7f46-86ba-a1c364f45c69.png)


スレッドはリストを左にスワイプすると、削除できるようになります。この時呼ばれるのは `ThreadListView` の `delete` メソッドです。

```swift
func delete(at offsets: IndexSet) {
    // 処理対象のスレッドを取得
    let thread = self.threads[Array(offsets)[0]] as NCMBObject
    // 削除処理実行
    let results = thread.delete()
    // 処理結果の判定
    switch results {
    case .success(_): // 処理がうまくいったらスレッドを取り直し
        getThread()
    case .failure(_): // 処理が失敗したらアラートを出す
        showAlert = true
    }
}
```

処理が失敗した場合には `alert` を使ってアラート表示していています。自分に削除権限がない場合（自分が立てたスレッドでない場合）、削除は失敗します。

```swift
.alert(isPresented: $showAlert, content: {
    Alert(title: Text("削除に失敗しました。権限がないようです。"))
})
```

## まとめ

今回は掲示板アプリのスレッド追加と一覧表示、削除について実装を行いました。次回はスレッドの詳細表示とコメントの追加、表示、削除を実装します。