0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

NCMBのSwift SDKを使って掲示板アプリを作る(その1:Viewの説明とSwift SDKの初期化)

Posted at

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

今回から複数回に分けて、掲示板アプリを作ってみます。Swift SDKの使い方はもちろん、NCMBの基本的な利用法を学ぶのに役立ててください。

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

掲示板アプリの機能について

掲示板アプリは一般的なスレッド、コメント形式の掲示板となっています。認証は匿名認証を使っており、自分で立てたスレッドやコメントは削除できます。スレッドにはアイコンとして画像をアップロードできます。

利用する機能について

掲示板アプリで利用するNCMBの機能は次の通りです。

  • 認証機能
    • 匿名認証
  • データストア
    • スレッドクラスへのデータ登録、一覧表示、削除
    • コメントクラスへのデータ登録、一覧表示、削除
  • ファイルストア
    • スレッドクラスに紐付いた写真のアップロード
    • 写真のダウンロード

画面について

掲示板アプリでは次の画面(View)を用意しています。

  • 全般
    • ImagePickerView
      画像を撮影、またはフォトライブラリからピックアップする用
    • ImageView
      画像を表示するためのView
  • スレッド用
    • ThreadListView
      スレッド一覧用
    • ThreadListRow
      スレッド一覧のリスト表示部分用
    • ThreadView
      スレッド詳細用
    • AddThreadView
      スレッド追加用
  • コメント用
    • CommentListRow
      スレッド詳細でのコメントリスト表示部分用
    • AddCommentView
      コメント投稿用

ContentView

スレッド一覧用のViewを読み込んでいるだけです。

struct ContentView: View {
    var body: some View {
        ZStack {
            ThreadListView()
        }
    }
}

ThreadListView

simulator_screenshot_AC78BBD8-519D-439D-94B4-975297AB23BE.png

スレッドのリスト表示と、ナビゲーションバーにスレッドを新規作成させるモーダルを表示させるプラスアイコンを表示しています。スレッド一覧をタップすると ThreadView に遷移します。

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("削除に失敗しました。権限がないようです。"))
        })
    }
}

ThreadListRow

image.png

スレッドの一覧表示用です。ImageViewを使って指定した画像を表示します。

struct ThreadListRow: View {
    var thread: NCMBObject
    var body: some View {
        HStack {
            ImageView(fileName: (thread["fileName"] ?? "") as String, size: 50)
            VStack {
                Text((thread["title"] ?? "") as String)
                    .font(.title3)
                Text((thread["description"] ?? "") as String)
                    .font(.caption)
                    .foregroundColor(.gray)
            }
            Spacer()
        }
    }
}

ImageView

指定された画像をファイルストアからダウンロードして、表示します。パラメータとして、表示する大きさを指定できます。

struct ImageView: View {
    var fileName: String?
    var size: CGFloat
    var body: some View {
        if let image = image() {
            Image(uiImage: image)
                .resizable()
                .frame(width: size, height: size)
        } else {
            Image(systemName: "bubble.left.and.bubble.right.fill")
                .resizable()
                .frame(width: size, height: size)
                .foregroundColor(.blue)
        }
    }
}

AddThreadView

simulator_screenshot_EF3F865E-86E5-4331-8CB4-F2F4FBB1C66B.png

スレッド追加用のモーダルです。画像追加をタップすると、フォトライブラリから写真を選択する表示になります。

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)
        }
    }
}

ThreadView

simulator_screenshot_10BB3F68-BCC1-4405-BD22-6E1820A9846D.png

ThreadViewはスレッド詳細とコメント一覧を表示します。スレッド情報を前画面から受け取ります。

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("削除に失敗しました。権限がないようです。"))
        })
    }
}

CommentListRow

コメントの一行表示用のViewです。

struct CommentListRow: View {
    var comment: NCMBObject
    var body: some View {
        HStack {
            Text((comment["text"] ?? "") as String)
        }
    }
}

AddCommentView

simulator_screenshot_2AA69A41-F390-43ED-BF4E-C9C0A3EDB0FC.png

コメントを追加する際のモーダルを表示します。

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("コメントする")
                            }
                        }
                    }
        }
    }
}

今回のプロジェクト

今回は言語がSwift、インタフェースがSwiftUI、ライフサイクルはSwiftUI Appとしています。

SDKのインストール

FileメニューからSwift Packages > Add Package Dependencyと選択します。

ScreenShot_ 2021-08-02 9.35.23.png

出てきたダイアログでSwift SDKのGitリポジトリURLを入力します。GitHubのリポジトリでHTTPSとして取得できるもの、または下記URLになります。

ScreenShot_ 2021-08-02 9.35.56.png

https://github.com/NIFCLOUD-mbaas/ncmb_swift.git

バージョンは最新のものでかまいません。

ScreenShot_ 2021-08-02 9.36.05.png

後はFinishボタンを押せば完了です。

ScreenShot_ 2021-08-02 9.36.40.png

初期化

今回はSwiftUIを利用しています。ライフサイクルもSwiftUIです。

まずSDKをインポートします。

import SwiftUI
import NCMB

次に scenePhase を追加します。

@main
struct forumApp: App {
    // 追加
    @Environment(\.scenePhase) private var scenePhase

後は body 内で onChange を使って初期化します。

var body: some Scene {
    WindowGroup {
        ContentView()
    }.onChange(of: scenePhase) { scene in
        switch scene {
        case .active:
            // キーの設定
            let applicationKey = "YOUR_APPLICATION_KEY"
            let clientKey = "YOUR_CLIENT_KEY"
            // 初期化
            NCMB.initialize(applicationKey: applicationKey, clientKey: clientKey)
        case .background: break
        case .inactive: break
        default: break
        }
    }
}

匿名認証

今回は匿名認証(ID、パスワードを使わない、デバイス固有に生成したUUIDを使った認証)を利用します。そこで、初期化した後にSwift SDKの匿名認証を有効にします。

NCMBUser.enableAutomaticUser()

認証状態の確認

次に認証状態を確認する checkAuth 関数を呼び出します。内容は次の通りです。 NCMBUser.currentUser が nil の場合は認証されていないので、匿名認証を実行します。

func checkAuth() -> Void {
    // 認証データがあれば処理は終了
    if NCMBUser.currentUser != nil {
        return;
    }
    // 匿名認証実行
    _ = NCMBUser.automaticCurrentUser()
}

セッションの有効性チェック

認証されている場合でも、セッションの有効性は確認していません。ローカルにある認証データを復元している状態のためです。そこで、データストアに一度アクセスを行い、API通信の有効性を確認します。

func checkSession() -> Bool {
    var query : NCMBQuery<NCMBObject> = NCMBQuery.getQuery(className: "Todo")
    query.limit = 1 // レスポンス件数を最小限にする
    // アクセス
    let results = query.find()
    // 結果の判定
    switch results {
    case .success(_): break
    case .failure(_):
        // 強制ログアウト処理
        _ = NCMBUser.logOut()
        return false
    }
    return true
}

上記コードでログアウト処理を実行していますが、これは必ず失敗します。セッションが無効になっているため、ログアウトAPIの実行もまた、失敗するためです。そこで NCMB/NCMBUser.swift を開いて logOutInBackground を次のように修正します。

public class func logOutInBackground(callback: @escaping NCMBHandler<Void>) -> Void {
    NCMBLogoutService().logOut(callback: {(result: NCMBResult<NCMBResponse>) -> Void in
        // レスポンスに関係なくログアウト処理
        deleteFile()
        _currentUser = nil
        switch result {
            case .success(_):
                callback(NCMBResult<Void>.success(()))
                break
            case let .failure(error):
                callback(NCMBResult<Void>.failure(error))
                break
        }
    })
}

NCMBの管理画面を修正

最後にNCMBの管理画面でアプリ設定を開き、匿名認証を有効にします。

FireShot_Capture_219_-ニフクラ_mobile_backend-_console_mbaas_nifcloud_com_15-23-35.jpg

これでSwift SDKの初期化と匿名認証処理が完了になります。

まとめ

今回は掲示板アプリの画面に関する説明と、Swift SDKの初期化、そして匿名認証の実装を行いました。次回はスレッドの作成と、一覧表示、削除を実装します。

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?