ニフクラ mobile backend(NCMB)では各種言語向けにSDKを提供しています。その中で最も新しいSwift SDKについて、その使い方を紹介します。
今回から複数回に分けて、掲示板アプリを作ってみます。Swift SDKの使い方はもちろん、NCMBの基本的な利用法を学ぶのに役立ててください。
なお、今回のコードはNCMBMania/Swift_Forum_Demo: Swift SDKで作る掲示板アプリのデモコードですにアップしてあります。
掲示板アプリの機能について
掲示板アプリは一般的なスレッド、コメント形式の掲示板となっています。認証は匿名認証を使っており、自分で立てたスレッドやコメントは削除できます。スレッドにはアイコンとして画像をアップロードできます。
利用する機能について
掲示板アプリで利用するNCMBの機能は次の通りです。
- 認証機能
- 匿名認証
- データストア
- スレッドクラスへのデータ登録、一覧表示、削除
- コメントクラスへのデータ登録、一覧表示、削除
- ファイルストア
- スレッドクラスに紐付いた写真のアップロード
- 写真のダウンロード
画面について
掲示板アプリでは次の画面(View)を用意しています。
- 全般
- ImagePickerView
画像を撮影、またはフォトライブラリからピックアップする用 - ImageView
画像を表示するためのView
- ImagePickerView
- スレッド用
- ThreadListView
スレッド一覧用 - ThreadListRow
スレッド一覧のリスト表示部分用 - ThreadView
スレッド詳細用 - AddThreadView
スレッド追加用
- ThreadListView
- コメント用
- CommentListRow
スレッド詳細でのコメントリスト表示部分用 - AddCommentView
コメント投稿用
- CommentListRow
ContentView
スレッド一覧用のViewを読み込んでいるだけです。
struct ContentView: View {
var body: some View {
ZStack {
ThreadListView()
}
}
}
ThreadListView
スレッドのリスト表示と、ナビゲーションバーにスレッドを新規作成させるモーダルを表示させるプラスアイコンを表示しています。スレッド一覧をタップすると 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
スレッドの一覧表示用です。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
スレッド追加用のモーダルです。画像追加をタップすると、フォトライブラリから写真を選択する表示になります。
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
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
コメントを追加する際のモーダルを表示します。
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と選択します。
出てきたダイアログでSwift SDKのGitリポジトリURLを入力します。GitHubのリポジトリでHTTPSとして取得できるもの、または下記URLになります。
https://github.com/NIFCLOUD-mbaas/ncmb_swift.git
バージョンは最新のものでかまいません。
後はFinishボタンを押せば完了です。
初期化
今回は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の管理画面でアプリ設定を開き、匿名認証を有効にします。
これでSwift SDKの初期化と匿名認証処理が完了になります。
まとめ
今回は掲示板アプリの画面に関する説明と、Swift SDKの初期化、そして匿名認証の実装を行いました。次回はスレッドの作成と、一覧表示、削除を実装します。