NCMBのSwift SDKを使って掲示板アプリを作る（その3：スレッドの表示とコメント一覧表示、投稿、削除）

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

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

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

## スレッド詳細表示

![simulator_screenshot_E591F098-318C-4D2D-813B-CF0B1BF83207.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/65182b6c-5186-8ca3-8fce-3587c9f2aa9f.png)


スレッド一覧をタップすると、スレッド詳細表示 `ThreadView` を呼び出します。

```swift
// ThreadListView.swift
List {
    ForEach(self.threads, id: \.objectId) { thread in
        NavigationLink(
            destination: ThreadView(thread: thread)
        ) {
            ThreadListRow(thread: thread)
        }
    }
    .onDelete(perform: delete)
}
```

`ThreadView` では、表示されたタイミングでコメントの取得処理 `getComments` を実行しています。

```swift
struct ThreadView: View {
    var thread: NCMBObject
    @State private var showModal = false
    @State private var comments: [NCMBObject] = []
    @State private var showAlert = false
    
    var body: some View {
        VStack {
            ImageView(fileName: (thread["fileName"] ?? "") as String, size: 200)
            Text("コメント").font(/*@START_MENU_TOKEN@*/.title/*@END_MENU_TOKEN@*/)
            List {
                ForEach(self.comments, id: \.objectId) { comment in
                    CommentListRow(comment: comment)
                }
                .onDelete(perform: delete)
            }
            .navigationBarTitle((thread["title"] ?? ""), displayMode: .inline)
            .navigationBarItems(trailing:
                                    Button(action: {
                                        showModal.toggle()
                                    }, label: {
                                        Image(systemName: "bubble.middle.bottom.fill")
                                            .resizable()
                                            .padding(6)
                                            .frame(width: 24, height: 24)
                                            .foregroundColor(.blue)
                                    })
            )
        }
        .onAppear {
            getComments()
        }
        .sheet(isPresented: $showModal, content: {
            AddCommentView(thread: thread)
        })
        .onChange(of: showModal, perform: { value in
            if (!showModal) {
                getComments()
            }
        })
        .alert(isPresented: $showAlert, content: {
            Alert(title: Text("削除に失敗しました。権限がないようです。"))
        })
    }
}
```

`getComments` の実装は次のようになります。実装はコメントを参照してください。今回はスレッドクラスのポインターを指定しています。ポインターデータについては[NCMBのSwift SDKを使ってポインターデータを扱う - Qiita](https://qiita.com/goofmint/items/a2342aa61cf51ed4fd5a)を参照してください。

```swift
func getComments() {
    // コメントクラスのNCMBQueryを作成
    var query = NCMBQuery.getQuery(className: "Comment")
    // 検索条件として、スレッドデータを指定（ポインター形式）
    query
        .where(field: "thread", equalTo: [
            "objectId": (thread["objectId"] ?? "") as String,
            "__type": "Pointer",
            "className": "Thread"
        ])
    // 検索実行
    let results = query.find()
    // 結果判定
    switch results {
    case let .success(ary): // 検索結果がうまくいったら、commentsに結果を適用
        comments = ary
    case .failure(_): break
    }
}
```

## コメントを投稿する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/0bf3275a-7dea-2059-5000-231a28ac8468.png)

新しいコメントを投稿する際には、ナビゲーションバーにあるコメントアイコンをタップします。

```swift
// ThreadView.swift
.navigationBarItems(trailing:
                        Button(action: {
                            showModal.toggle()
                        }, label: {
                            Image(systemName: "bubble.middle.bottom.fill")
                                .resizable()
                                .padding(6)
                                .frame(width: 24, height: 24)
                                .foregroundColor(.blue)
                        })
)
```

そうするとモーダルのフラグが立って、 `AddCommentView` が表示されます。

![simulator_screenshot_EC73CF0D-04CB-4CB4-8282-A3E7FB73169E.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/dab9d8e9-c3f5-f37f-6c9e-db189400b693.png)


```swift
// ThreadView.swift
.sheet(isPresented: $showModal, content: {
    AddCommentView(thread: thread)
})
```

### コメント投稿フォーム

`AddCommentView` はスレッドを立てる時の画面に似ていますが、コメント文しか入力できないのが特徴です。コメントするをタップすると `add` 関数が実行されます。

```swift
struct AddCommentView: View {
    @Environment(\.presentationMode) private var presentationMode
    
    var thread: NCMBObject
    @State private var text = ""
    
    var body: some View {
        VStack {
            NavigationView {
                        Form {
                            TextField("コメントを入力してください", text: $text)
                            Button(action: {
                                add()
                            }) {
                                Text("コメントする")
                            }
                        }
                    }
        }
    }
}
```

#### add関数について

`add` 関数でコメントクラスにデータを登録します。実装内容はコメントを参照してください。

```swift
func add() {
    // コメントクラスのNCMBObjectを用意
    let comment = NCMBObject(className: "Comment")
    // コメント文をセット
    comment["text"] = text
    // 該当スレッドのデータをセット（ポインターデータとして）
    comment["thread"] = [
        "objectId": (thread["objectId"] ?? "") as String,
        "__type": "Pointer",
        "className": "Thread"
    ]
    // 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を設定
    comment.acl = acl
    // 保存処理実行
    let results = comment.save()
    // 処理がうまくいったらモーダルを閉じる
    if case .success(_) = results {
        presentationMode.wrappedValue.dismiss()
    } else {
        print("Error")
    }
}
```

## コメントを削除する

![ScreenShot_ 2021-08-07 17.07.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/ed539273-3701-109a-6040-4932ee63505b.png)


投稿したコメントは左にスワイプすると削除できます。実行する関数は `delete` です。

```swift
// ThreadView.swift
List {
    ForEach(self.comments, id: \.objectId) { comment in
        CommentListRow(comment: comment)
    }
    .onDelete(perform: delete)
}
```

`delete` 関数の実装は次のようになります。詳細はコメントを参照してください。

```swift
func delete(at offsets: IndexSet) {
    // 該当するコメントを取得
    let comment = comments[Array(offsets)[0]]
    // 削除実行
    let results = comment.delete()
    // 結果判定
    switch results {
    case .success(_):
        getComments() // 処理成功していればコメントを取得し直す
    case .failure(_):
        showAlert = true // 失敗した場合はアラート
    }
}
```

失敗した場合には（削除権限がない場合）、アラートを出します。

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

これで掲示板アプリの完成です。

## まとめ

NCMBのSwift SDKを使うことで、テキストなどのデータをクラウドに保存したり、ファイルストレージに写真を保存したりするのが簡単にできるようになります。さらにプッシュ通知を使えば過去にスレッドへ書き込んだ人たちに新着コメントをお知らせすることもできるでしょう。

他にも認証はソーシャル認証（Signin with Appleのような）にも対応しています。ぜひNCMB Swift SDKをアプリ開発に役立ててください。

[mBaaSでサーバー開発不要！ | ニフクラ mobile backend](https://mbaas.nifcloud.com/)