Swift SDKを使ってカメラメモアプリを作る（その2：認証処理と写真アップロードまで）

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

この記事では複数回に分けて、カメラメモアプリを作ってみます。Swift SDKの使い方はもちろん、NCMBの基本的な利用法を学ぶのに役立ててください。[前回は画面の仕様とSwift SDK導入まで](https://qiita.com/goofmint/items/937823dda1c83cf711d5)を解説しました。今回は認証処理の実装と、写真アップロード処理までを作成します。

なお、今回のコードは[NCMBMania/camera_memo_swift: Swift SDKを使ったカメラメモアプリのコードです](https://github.com/NCMBMania/camera_memo_swift)にアップしてあります。

## 認証画面について

認証処理を行う画面は `LoginView.swift` になります。前回も書きましたが、NCMBの処理を抜いたSwiftUIのコードは次のようになります。

```swift
//
//  LoginView.swift
//  camera
//
//  Created by Atsushi on 2021/06/23.
//

import SwiftUI
import NCMB

struct LoginView: View {
    @State private var userName: String = ""
    @State private var password: String = ""
    @Binding var isLogin: Bool
    
    var body: some View {
        VStack(spacing: 16) {
            TextField("ユーザ名", text: $userName)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .frame(maxWidth: 280)
            SecureField("パスワード", text: $password)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .frame(maxWidth: 280)
            Button(action: {
                signUpOrLogin()
            }, label: {
                Text("新規登録/ログイン")
            })
        }
    }

    func signUpOrLogin() {
      // ここに認証処理を実装します
    }
}
```

## 認証処理について

今回はユーザ登録とログインを一つの画面で行うようにします。もちろん分けて実装しても問題ありません。実装は `signUpOrLogin` 内で行います。まず入力されたユーザ名 `userName` とパスワード `password` を使ってユーザ登録処理を実行します。

```swift
let user = NCMBUser()
user.userName = userName
user.password = password
// ユーザ登録処理の実行
user.signUpInBackground(callback: { _ in
})
```

もしすでに一度ユーザ登録を行っている場合、この処理はエラーになります。しかし、それを無視してそのままログイン処理を実行します。

```swift
NCMBUser.logInInBackground(userName: userName, password: password, callback: { _ in
    self.isLogin = NCMBUser.currentUser != nil
})
```

ログイン処理がうまくいっていれば `NCMBUser.currentUser` にログインユーザの情報が入ってきます。ログインユーザ情報が入っている（= nil ではない）状態であれば isLogin が true になります。

`signUpOrLogin` 関数の実装は次の通りです。

```swift
func signUpOrLogin() {
    let user = NCMBUser()
    user.userName = userName
    user.password = password
    user.signUpInBackground(callback: { _ in
        NCMBUser.logInInBackground(userName: userName, password: password, callback: { _ in
            self.isLogin = NCMBUser.currentUser != nil
        })
    })
}
```

## メモ入力画面について

ログインが完了すると isLogin が true になります。その結果、 `UploaderView.swift` にて写真選択/入力画面が表示されます。

```swift
// 以下抜粋
if isLogin {
    VStack(spacing:30){
        MemoView(imageData: $imageData)
        HStack(spacing:60){
            Button(action: {
                self.source = .photoLibrary
                self.isImagePicker.toggle()
            }, label: {
                Text("Photo library")
            })
            Button(action: {
                self.source = .camera
                self.isImagePicker.toggle()
            }, label: {
                Text("Take Photo")
            })
        }
// 以下略
```

写真を撮影または選択すると、そのデータは `imageData` に入ります。そうすると `MemoView` にて選択した写真が表示されます。

```swift
var body: some View {
    // 画像データがあれば表示
    if imageData.count != 0 {
        Image(uiImage: UIImage(data: self.imageData)!)
            .resizable()
            .aspectRatio(contentMode: .fill)
            .frame(height: 250)
            .cornerRadius(15)
            .padding()
        TextField("メモ", text: $text)
            .textFieldStyle(RoundedBorderTextFieldStyle())
            .frame(maxWidth: 280)
        Button(action: {
          // アップロード処理（後述）
        }, label: {
            Text("Upload")
        })
        .alert(isPresented: $uploaded, content: {
            Alert(title: Text("アップロード完了"), message: Text("写真をアップロードしました"), dismissButton: .default(Text("閉じる"))
            )
        })
        
    }
}
```

### アップロード処理について

`Upload` ボタンをタップするとNCMBへのアップロード処理を開始します。まずファイル名をUUIDを使ってユニークなものを生成し、その名前を使ってNCMBFileオブジェクトを作ります。

```swift
let fileName = "\(UUID()).jpg"
let file = NCMBFile(fileName: fileName)
```

次にそのデータへのアクセス権限として NCMBAcl を設定します。ログインユーザだけが読み書き可能としています。それを NCMBFile オブジェクトの acl に設定します。

```swift
var acl = NCMBACL.empty
let user = NCMBUser.currentUser
acl.put(key: user!.objectId!, readable: true, writable: true)
file.acl = acl
```

アップロードは `saveInBackground` メソッドで実行されます。引数として、写真データを渡します。保存処理は非同期です。

```swift
file.saveInBackground(data: self.imageData, callback: { result in
    switch result {
    case .success:
      // 処理成功
    case let .failure(error):
        print("保存に失敗しました: \(error)")
        return;
    }
})
```

アップロード処理が成功したら、続けてメモを保存します。メモ用のNCMBObjectを作ります。クラス名（DBでいうテーブル名相当）はMemoとします。

```swift
let memo = NCMBObject(className: "Memo");
```

そしてフィールド（DBでいうテーブルのカラム名）として、メモの入力内容 `text` とアップロードしたファイル名 `fileName` を設定します。ACLもファイルと同じものを設定します。

```swift
memo["text"] = text
memo["fileName"] = fileName
memo.acl = acl
```

そしてデータストアもファイルストアと同じく `saveInBackground` で保存処理を行います。保存がうまくいったら入力内容を消したり、キーボードを非表示にしています。

```swift
memo.saveInBackground(callback: {_ in
    self.uploaded.toggle()
    self.text = ""
    DispatchQueue.main.async {
        UIApplication.shared.closeKeyboard()
    }
})
```

## まとめ

今回は認証処理とファイルストアへのアップロード、データストアへの保存処理を作成しました。とても簡単にクラウドへの保存処理が行えますので、ぜひ使ってみてください。

[次回は保存したデータを取得して一覧表示する処理](https://qiita.com/goofmint/items/d0ab37886c7a6ce928e3)を作成します。

[イントロダクション (Swift) : クイックスタート | ニフクラ mobile backend](https://mbaas.nifcloud.com/doc/current/introduction/quickstart_swift.html)