Swift SDKを使って写真をNCMBへアップロードする

ニフクラ mobile backendでは各種言語向けにSDKを提供しています。その中で最も新しい[Swift SDK](https://mbaas.nifcloud.com/doc/current/introduction/quickstart_swift.html)について、その使い方を紹介します。

今回はファイルストア（バイナリファイルのストレージ機能）を使って、撮影した写真をアップロードします。

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

## 謝辞

この記事を書くにあたって、写真を撮影またはライブラリから取得する部分のコードを[【SwiftUI】カメラ機能の実装方法【撮影画像とライブラリー画像の利用】](https://tomato-develop.com/swiftui-how-to-use-camera-and-select-photos-from-library/)から拝借しています。ありがとうございます。

```swift
import SwiftUI

struct Imagepicker : UIViewControllerRepresentable {
    
    @Binding var show:Bool
    @Binding var image:Data
    var sourceType:UIImagePickerController.SourceType
 
    func makeCoordinator() -> Imagepicker.Coodinator {
        
        return Imagepicker.Coordinator(parent: self)
    }
      
    func makeUIViewController(context: UIViewControllerRepresentableContext<Imagepicker>) -> UIImagePickerController {
        
        let controller = UIImagePickerController()
        controller.sourceType = sourceType
        controller.delegate = context.coordinator
        
        return controller
    }
    
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<Imagepicker>) {
    }
    
    class Coodinator: NSObject,UIImagePickerControllerDelegate,UINavigationControllerDelegate {
        
        var parent : Imagepicker
        
        init(parent : Imagepicker){
            self.parent = parent
        }
        
        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            self.parent.show.toggle()
        }
        
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            
            let image = info[.originalImage] as! UIImage
            let data = image.jpegData(compressionQuality: 0.8)
            self.parent.image = data!
            self.parent.show.toggle()
        }
    }
}
```

## Swift SDKの導入法

Swift SDKはCocoaPods向けに提供しています。Xcodeで新しいiOSプロジェクトを作ったら、Podfileを用意します。

```
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'todoapp' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for todoapp
  pod 'NCMB', :git => 'https://github.com/NIFCLOUD-mbaas/ncmb_swift.git'
end
```

そして `pod install` でSwift SDKをインストールします。

## 初期化について

現在、新規でiOSアプリを作成すると、InterfaceがSwiftUI、Life CycleがSwiftUI Appとなっています。この状態で作ると `AppDelegate.swift` はなく、 `(アプリ名)App.swift` というファイルが作られます。

この場合、まずSwift SDKを読み込みます。

```swift
import SwiftUI
import NCMB   // 追加
```

そして `var body` の上に `@Environment` を追加します。

```swift
@Environment(\.scenePhase) private var scenePhase // 追加
var body: some Scene {
```

最後に `WindowGroup` に `onChange` を追加します。このonChangeの中で初期化処理を行います。

```swift
WindowGroup {
    ContentView()
}
.onChange(of: scenePhase) { scene in
    switch scene {
    case .active:
        NCMB.initialize(applicationKey: "YOUR_APPLICATION_KEY", clientKey: "YOUR_CLIENT_KEY")
    case .background:
        print("background")
    case .inactive:
        print("inactive")
    @unknown default:
        print("default")
    }
}
```

これで利用可能になります。

## 必要なステートを準備

まず写真データを扱うために必要なステートを準備します。

```swift
struct ContentView: View {
    // 画像データが入ります
    @State var imageData : Data = .init(capacity:0)
    // フォトライブラリまたはカメラの指定が入ります
    @State var source:UIImagePickerController.SourceType = .photoLibrary
    // イメージピッカーの表示/非表示を切り替えます
    @State var isImagePicker = false
    // アップロード状態を管理します
    @State var uploaded = false

    // : 略
}
```

## 表示内容の作成

次に表示内容を作ります。イメージピッカー部分も合わせて記載しています。

```swift
// 表示    
var body: some View {
        NavigationView{
            VStack(spacing:0){
                    ZStack{
                        // イメージピッカーの表示/非表示用
                        NavigationLink(
                            destination: Imagepicker(show: $isImagePicker, image: $imageData, sourceType: source),
                            isActive:$isImagePicker,
                            label: {
                                Text("")
                            })
                        VStack{
                            // イメージデータの有無で表示路切り替え
                            if imageData.count != 0{
                                // イメージデータがある場合
                                // 画像を表示
                                Image(uiImage: UIImage(data: self.imageData)!)
                                    .resizable()
                                    .aspectRatio(contentMode: .fill)
                                    .frame(height: 250)
                                    .cornerRadius(15)
                                    .padding()
                                // アップロード用ボタンの表示
                                Button(action: {
                                    // 後述
                                }, label: {
                                    Text("アップロード")
                                })
                                .alert(isPresented: $uploaded, content: {
                                    // アップロード完了したらアラート
                                    Alert(title: Text("アップロード完了"), message: Text("写真をアップロードしました"), dismissButton: .default(Text("閉じる")))
                                })
                            }
                            // 写真撮影またはフォトライブラリを開くボタン
                            HStack(spacing:30){
                                // フォトライブラリを開く
                                Button(action: {
                                    self.source = .photoLibrary
                                    self.isImagePicker.toggle()
                                }, label: {
                                    Text("フォトライブラリ")
                                })
                                // カメラを開く
                                Button(action: {
                                    self.source = .camera
                                    self.isImagePicker.toggle()
                                }, label: {
                                    Text("写真を撮影")
                                })
                            }
                        }
                    }
            }
            .navigationBarTitle("Home", displayMode: .inline)
        }
    .ignoresSafeArea(.all, edges: .top)
    .background(Color.primary.opacity(0.06).ignoresSafeArea(.all, edges: .all))
}
```

![IMG_2317.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/b439ecae-3c73-65c4-f438-279ee240224b.png)


## ファイルのアップロード処理

ではファイルストアへのアップロード処理を解説します。これは `アップロード` をタップした際に実行します。今回は写真の名前は `photo.jpg` で固定ですが、好きなものを指定してください。

![IMG_2318.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/e0a9d955-eb6f-52a9-f11a-d267f31fd544.png)


```swift
// アップロード用ボタンの表示
Button(action: {
    // NCMBFileを作成
    let file : NCMBFile = NCMBFile(fileName: "photo.jpg")
    // 保存処理の実行
    file.saveInBackground(data: self.imageData, callback: { result in
        // 処理結果の判定
        switch result {
        case .success:
            // 処理がうまくいっていれば、フラグを立てる
            self.uploaded = true
        case let .failure(error):
            // 失敗した場合
            print("保存に失敗しました: \(error)")
            return;
        }
    })
}, label: {
    Text("アップロード")
})
.alert(isPresented: $uploaded, content: {
    // アップロード完了したらアラート
    Alert(title: Text("アップロード完了"), message: Text("写真をアップロードしました"), dismissButton: .default(Text("閉じる")))
})
```

画像データはNCMBFileのdataに対して、そのまま適用できます。処理は `saveInBackground` で非同期に処理されますので、結果を受け取ってからステートのフラグを立てることで alert イベントを実行しています。

コードの全体像は次のようになります。

```swift
import SwiftUI
import NCMB

struct ContentView: View {
    // 画像データが入ります
    @State var imageData : Data = .init(capacity:0)
    // フォトライブラリまたはカメラの指定が入ります
    @State var source:UIImagePickerController.SourceType = .photoLibrary
    // イメージピッカーの表示/非表示を切り替えます
    @State var isImagePicker = false
    // アップロード状態を管理します
    @State var uploaded = false

    // 表示    
    var body: some View {
            NavigationView{
                VStack(spacing:0){
                        ZStack{
                            // イメージピッカーの表示/非表示用
                            NavigationLink(
                                destination: Imagepicker(show: $isImagePicker, image: $imageData, sourceType: source),
                                isActive:$isImagePicker,
                                label: {
                                    Text("")
                                })
                            VStack{
                                // イメージデータの有無で表示路切り替え
                                if imageData.count != 0{
                                    // イメージデータがある場合
                                    // 画像を表示
                                    Image(uiImage: UIImage(data: self.imageData)!)
                                        .resizable()
                                        .aspectRatio(contentMode: .fill)
                                        .frame(height: 250)
                                        .cornerRadius(15)
                                        .padding()
                                    // アップロード用ボタンの表示
                                    Button(action: {
                                        // NCMBFileを作成
                                        let file : NCMBFile = NCMBFile(fileName: "photo.jpg")
                                        // 保存処理の実行
                                        file.saveInBackground(data: self.imageData, callback: { result in
                                            // 処理結果の判定
                                            switch result {
                                            case .success:
                                                // 処理がうまくいっていれば、フラグを立てる
                                                self.uploaded = true
                                            case let .failure(error):
                                                // 失敗した場合
                                                print("保存に失敗しました: \(error)")
                                                return;
                                            }
                                        })
                                    }, label: {
                                        Text("アップロード")
                                    })
                                    .alert(isPresented: $uploaded, content: {
                                        // アップロード完了したらアラート
                                        Alert(title: Text("アップロード完了"), message: Text("写真をアップロードしました"), dismissButton: .default(Text("閉じる")))
                                    })
                                }
                                // 写真撮影またはフォトライブラリを開くボタン
                                HStack(spacing:30){
                                    // フォトライブラリを開く
                                    Button(action: {
                                        self.source = .photoLibrary
                                        self.isImagePicker.toggle()
                                    }, label: {
                                        Text("フォトライブラリ")
                                    })
                                    // カメラを開く
                                    Button(action: {
                                        self.source = .camera
                                        self.isImagePicker.toggle()
                                    }, label: {
                                        Text("写真を撮影")
                                    })
                                }
                            }
                        }
                }
                .navigationBarTitle("Home", displayMode: .inline)
            }
        .ignoresSafeArea(.all, edges: .top)
        .background(Color.primary.opacity(0.06).ignoresSafeArea(.all, edges: .all))
    }
}
```

## まとめ

写真アプリなど、写真データをクラウドにアップロードしたいという機会は多いかと思います。そうした時にサーバを用意せずに使えるNCMBは、アプリ開発を高速化してくれる良い手段になるはずです。ぜひお使いください！

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