NCMBのSwift SDKを使って日報アプリを作る（その1：画面の説明とSDKの導入）

NCMBのSwift SDKを使ってデモアプリを作ってみます。今回は業務系でよくあるニーズの日報アプリを作ってみます。実際にはデータストアやファイルストアを使うので、応用すれば汎用的に使えるはずです。

今回は画面の説明とSDKの導入までを進めます。

## コードについて

今回のコードは[NCMBMania/swift\_daily\_report](https://github.com/NCMBMania/swift_daily_report)にアップロードしてあります。実装時の参考にしてください。

## 利用技術について

今回は次のような組み合わせになっています。

- Swift 5.5
- Xcode 13.0
- NCMB Swift SDK 1.2.1

## 仕様について

今回はシンプルにするために認証は利用していません。入力された日報データをデータストアに保存しますが、写真が追加されている場合にはファイルストアにも保存します。また、レポート画面では日付を使ってデータストアの検索を行い、一致するデータの一覧表示を行います。

## 利用する機能について

チャットアプリで利用するNCMBの機能は次の通りです。

- データストア
  - 日報データの保存
  - 日報データの検索
- ファイルストア
  - 写真のアップロード
  - 写真のダウンロード

## 画面について

今回はSwiftUIを以下の5つのViewに分けています。

### ContentView.swift

タブを2つ表示します。1つ目は入力画面（InputView）、2つ目はレポート画面（ReportView）です。

```swift
struct ContentView: View {
    var body: some View {
        TabView {
            InputView()
                .tabItem {
                    VStack {
                        Image(systemName: "pencil")
                        Text("入力")
                    }
            }.tag(1)
            ReportView()
                .tabItem {
                    VStack {
                        Image(systemName: "list.bullet")
                        Text("レポート")
                    }
            }.tag(2)
        }
    }
}
```

![Simulator Screen Shot - iPod touch (7th generation) - 2021-10-06 at 14.57.17のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/9ebf30bb-366b-7c03-8f5c-8bb4d2082fb1.png)


### InputView.swift

日報の入力画面です。本文と日付が基本で、任意で写真を追加できます。ナビゲーションバーに保存ボタンを追加して、押すとデータをデータストアに保存します。

```swift
struct InputView: View {
    @State private var date = Date()     // 日報の日付
    @State private var text = ""         // 日報の本文
    @State private var showAlert = false // アラート表示用のフラグ
    @State private var message = ""      // アラートのメッセージ
    
    @State var imageData : Data = .init(capacity:0) // // 選択された写真データ
    @State var source:UIImagePickerController.SourceType = .photoLibrary // カメラまたはフォトライブラリ
    @State var isImagePicker = false // 写真ピッカーを表示する際のフラグ
    
    var body: some View {
        NavigationView {
            VStack(spacing: 10) {
                // 画像モーダルへの遷移用
                NavigationLink(
                    destination: Imagepicker(
                        show: $isImagePicker,
                        image: $imageData,
                        sourceType: source
                    ),
                    isActive:$isImagePicker,
                    label: {
                        Text("")
                    })
                // 日報の日付を選択
                DatePicker("日付",
                    selection: $date,
                    displayedComponents: .date
                )
                // 日報の内容を記述するTextEditor
                TextEditor(text: $text)
                    .frame(width: UIScreen.main.bounds.width * 0.8, height: 200)
                    .overlay(
                        RoundedRectangle(cornerRadius: 5)
                            .stroke(Color.blue, lineWidth: 2)
                    )
                // 画像が指定されれば、サムネイル表示
                if imageData.count != 0 {
                    Image(uiImage: UIImage(data: self.imageData)!)
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(height: 100)
                        .cornerRadius(15)
                        .padding()
                }
                // 写真選択モーダルの表示
                Button(action: {
                    self.source = .photoLibrary
                    self.isImagePicker.toggle()
                }, label: {
                    Text("写真を選択")
                })
            }
            .navigationBarTitle("日報入力", displayMode: .inline)
            .toolbar {
                // 右上のアイコンで保存しょりを実行
                ToolbarItem(placement: .navigationBarTrailing){
                    Button(action: {
                        save()
                    }) {
                        Image(systemName: "icloud.and.arrow.up")
                    }
                }
            }
        }
        .alert(isPresented: $showAlert) {
            Alert(title: Text(self.message))
        }
    }
    
    // 日報を保存する処理
    func save() {
    }
}
```

![Simulator Screen Shot - iPod touch (7th generation) - 2021-10-06 at 14.57.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/72181e7b-dace-90e8-b384-32a9777cceb7.png)


### ReportView.swift

日報データを検索、結果を一覧表示する画面です。最初は日付だけを表示します。

```swift
struct ReportView: View {
    @State private var date = Date()            // 検索対象の日付
    @State private var ary: [NCMBObject]? = nil // 検索結果のNCMBObject（配列）
    
    var body: some View {
        NavigationView {
            VStack(spacing: 10) {
                if self.ary == nil {
                    VStack {
                        // 検索対象を指定（日報の日付）
                        DatePicker("日付を選択してください",
                            selection: $date,
                            displayedComponents: .date
                        )
                        // 検索実行
                        Button(action: {
                            search()
                        }, label: {
                            Text("日報を表示")
                        })
                    }
                } else {
                    // 検索結果をリスト表示
                    List {
                        ForEach(self.ary!, id: \.objectId) { report in
                            ReportRowView(report: report)
                        }
                    }
                }
            }
            .navigationBarTitle("日報検索", displayMode: .inline)
            .toolbar {
                // 日付データを消す（最初の状態に戻す）アイコン
                ToolbarItem(placement: .navigationBarTrailing){
                    Button(action: {
                        self.ary = nil
                    }) {
                        Image(systemName: "xmark.circle")
                    }
                    .disabled(self.ary == nil)
                }
            }

        }
    }
    
    // 指定された日付の日報データを検索する関数
    func search() {
    }
}
```

![Simulator Screen Shot - iPod touch (7th generation) - 2021-10-06 at 14.59.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/09847e6b-46e0-21c6-7d38-5da1bc20092e.png)


### ReportRowView.swift

検索結果の一覧表示用、1行データ用のViewです。表示されたタイミングで画像データをファイルストアからダウンロード、表示します。

```swift
struct ReportRowView: View {
    @State var report:NCMBObject // 表示する日報データ
    @State var imageData : Data = .init(capacity:0) // 日報に紐付いた画像データ
    
    var body: some View {
        // タップした際にの遷移
        NavigationLink(destination: DetailView(obj: report, imageData: $imageData)) {
            HStack(alignment: .top) {
                Spacer().frame(width: 10)
                VStack(alignment: .leading) {
                    HStack(alignment: .top) {
                        VStack {
                            // 画像データの有無を確認
                            if self.imageData.count == 0 {
                                // ないときにはダミー画像
                                Image(systemName: "photo")
                                    .resizable()
                                    .frame(width: 60, height: 60)
                                    .scaledToFit()
                            } else {
                                // 画像があれば表示
                                Image(uiImage: UIImage(data: self.imageData)!)
                                    .resizable()
                                    .aspectRatio(contentMode: .fit)
                                    .frame(width: 60, height: 60)
                            }
                        }
                        VStack(alignment: .leading) {
                            // 日報本文の一部を表示
                            Text((self.report["text"] ?? "") as! String)
                                .lineLimit(3)
                        }
                    }
                }
                Spacer()
            }
            .onAppear {
                // 画面が表示されたタイミングで画像を読み込む
                loadImage()
            }
        }
    }
    
    // 画像を読み込む関数
    func loadImage() {
    }
}
```

### DetailView.swift

一覧でタップされた日報の詳細表示を行うViewです。一覧から渡されたデータストアオブジェクト、画像を表示します。

```swift
// 日報の詳細表示用View
struct DetailView: View {
    @State var obj: NCMBObject    // 日報用データストアのオブジェクト
    @Binding var imageData : Data // 日報に添付されていた画像データ
    
    var body: some View {
        VStack {
            // 画像データがあれば表示
            if self.imageData.count > 0 {
                Image(uiImage: UIImage(data: self.imageData)!)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 200, height: 200)
            }
            // 日報の本文
            Text((obj["text"] ?? "") as! String)
        }
        // タイトルに日付を表示
        .navigationBarTitle(dateTitle(), displayMode: .inline)
    }
    
    // タイトルに出す内容を作成する
    func dateTitle() -> String {
        // 日付フォーマットの作成
        let dateFormatter = DateFormatter()
        dateFormatter.locale = Locale(identifier: "ja_JP")
        dateFormatter.dateFormat = "M月d日の日報"
        // 日報データの日付を使ってタイトルを作成
        return dateFormatter.string(from: (obj["date"] ?? Date()) as! Date)
    }
}
```

![Simulator Screen Shot - iPod touch (7th generation) - 2021-10-06 at 14.59.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/65a8ccc0-b649-e761-1e37-c649ceffbc75.png)


### Imagepicker

写真を撮影、またはフォトライブラリから写真を選択します。コードは[【SwiftUI】カメラ機能の実装方法【撮影画像とライブラリー画像の利用】](https://tomato-develop.com/swiftui-how-to-use-camera-and-select-photos-from-library/)から拝借しています。

## 今回のプロジェクト

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

### SDKのインストール

プロジェクトを開いて、Package Dependenciesの中にある + アイコンをクリックします。

![ScreenShot__2021-10-04_18_35_29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/20d0f49e-5afc-1ecd-4602-04a3dbc7ebd6.png)

パッケージ名が色々と出てくるので、左上のURL入力欄にて下記のURLを入力します。

![ScreenShot__2021-10-04_18_35_37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/d49802de-0551-0388-5b1e-ab922a8705cb.png)


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

そうすると ncmb_swift が出てくるので、右下にある Add Package ボタンを押します。

![ScreenShot__2021-10-04_18_35_41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/2f45f740-a1ab-7b04-946b-5cf2ce40f69d.png)


確認ダイアログはそのままで、Add Package ボタンを押します。

![ScreenShot_ 2021-10-04 18.35.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/d83394e7-4e7a-fa6b-b804-bb39a6001a83.png)


これでインストール完了です。

![ScreenShot_ 2021-10-04 18.35.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/e7b5a8c6-a65e-da9d-0c79-eb42413e1728.png)

## 初期化

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

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

```swift
import SwiftUI
import NCMB
```

ContentViewをダミーのVStackでラップします。

```swift
VStack {
    ContentView()
}
```

そのVStackのonAppear内で、NCMBの初期化を行います。

```swift
var body: some Scene {
        WindowGroup {
            VStack {
                ContentView()
            }.onAppear() {
                // NCMBのアプリケーションキーとクライアントキーを設定
                let applicationKey = "YOUR_APPLICATION_KEY"
                let clientKey = "YOUR_CLIENT_KEY"
                // 初期化
                NCMB.initialize(applicationKey: applicationKey, clientKey: clientKey)
            }
        }
}
```

## まとめ

今回は日報アプリの仕様と画面、NCMBの初期化までを解説しました。次は日報入力データの登録を解説します。