GoogleのML Kitを使って、オフラインで日本語テキスト認識するカメラメモアプリを作る（その3：写真の一覧と個別表示）

iOS 15ではOCR機能がOSに標準搭載されます（Live Text）。これはオフラインの機械学習モデルを使っているので、ネットワークに画像データが乗ったり、プライバシーの心配なく使えるのが魅力です。しかし、リリース時点では日本語に対応していません。

そこで使ってみたのがGoogleのML Kitになります。[まだベータ版なのですが、バージョン2から日本語にも対応しています](https://developers.google.com/ml-kit/vision/text-recognition/v2/ios?hl=ja)。また、ML Kitはオフライン動作も可能で、Google Cloudへの登録なども不要です。これを使えばLive Text的なことはできそうです。

[前回はML Kitを使ってテキストを抽出するのと、NCMBへ写真とメモをアップロードする処理を実装しました](https://qiita.com/goofmint/items/dbe0f604a167d0272e19)。今回はNCMBに保存されている写真データを取得し、さらにその個別表示を実装します。

## メモデータの取得

メモデータの取得は `ImageView.swift` にて実装します。すでに紹介済みですが、もう一度画面構成を掲載します。

```swift
struct ImageView: View {
    @State var memos: [NCMBObject] = []
    // LazyVGrid用
    var columns: [GridItem] = Array(repeating: .init(.fixed(200)), count: 2)
    
    var body: some View {
        ScrollView(.vertical) {
            LazyVGrid(columns: columns, alignment: .center, spacing: 200) {
                ForEach (memos, id: \.objectId) { memo In
                    GridImageView(Memo: memo)
                }
            }
            .onAppear() {
                getAllPhotos()
            }
        }
    }
    func getAllPhotos() {
      // メモデータを取得します（後述）
    }
}
```

メモデータの取得は `getAllPhotos` にて行います。まずNCMBを検索するためのNCMBQueryを用意します。対象となるクラス名（DBでいうテーブル相当）は保存したのと同じMemoです。

```swift
let query = NCMBQuery.getQuery(className: "Memo")
```

そして検索を実行します。検索結果はそのまま `memos` の中に入れます。

```swift
// データ取得実行
let result = query.find()
// 結果の判定
switch result {
    case let .success(array):
        self.memos = array
    case let .failure(error):
        print("取得に失敗しました: \(error)")
}
```

memosの中にデータが入ると、ForEachで繰り返し描画されます。この時、ユニークキーは objectId としてください。

```swift
ForEach (memos, id: \.objectId) { memo in
    GridImageView(memo: memo)
}
```

## グリッド表示について

メモのグリッド表示は `GridImageView.swift` にて行います。こちらもすでに紹介済みですが、再度掲載します。

```swift
struct GridImageView: View {
    @State private var imageData: Data? = .init(capacity:0)
    @State var memo: NCMBObject? = nil
    @State private var isShowing = false
    
    var body: some View {
        GeometryReader { geometry in
            if imageData?.count ?? 0 > 0 {
                Image(uiImage: UIImage(data: imageData!)!)
                    .resizable()
                    .scaledToFill()
                    .frame(width: geometry.size.width, height: geometry.size.width)
                    .clipped()
            } else {
                Rectangle().fill(Color.clear)
            }
        }.onAppear() {
            loadImage()
        }.onTapGesture {
            isShowing = true
        }.fullScreenCover(isPresented: $isShowing) {
            ModalView(isActive: $isShowing, memo: memo!, imageData: imageData!)
        }
        
    }

    func loadImage() {
      // 画像を読み込みます（後述）
    }
}
```

## 画像データを読み込む

ファイルストアから画像データを読み込む処理は `loadImage` 関数で行っています。ファイルストアではHTTPSアクセスも可能ですが、ACL（アクセス制限）を行っている場合にはデータを取得する方が良いでしょう。こうすることで、他のユーザからは読まれないデータとして利用できます。

まずメモデータからファイル名を取得します。

```swift
if let fileName : String = self.memo?["fileName"] {
}
```

この fileName を使ってNCMBFileオブジェクトを作ります。

```swift
let file : NCMBFile = NCMBFile(fileName: fileName)
```

ファイルデータの取得は `fetchInBackground` にて行います。

```swift
// ファイルデータを取得
let result = file.fetch()
```

処理がうまくいっていればデータが取得できますので、それをimageDataとして適用します。

```swift
// 結果の判定
switch result {
    case let .success(data):
        self.imageData = data
    case let .failure(error):
        print(error)
}
```

imageDataが入れば、それをUIImageのdataとして適用し、Imageオブジェクトで画像として表示します。

```swift
if imageData?.count ?? 0 > 0 {
    Image(uiImage: UIImage(data: imageData!)!)
        .resizable()
        .scaledToFill()
        .frame(width: geometry.size.width, height: geometry.size.width)
        .clipped()
} else {
    Rectangle().fill(Color.clear)
}
```

これでファイルストアから取得した画像データを描画できます。

## 画像をタップした際の処理

画像をタップした場合にはモーダル表示を行っています。

```swift
}.onTapGesture {
    isShowing = true
}.fullScreenCover(isPresented: $isShowing) {
    ModalView(isActive: $isShowing, memo: memo!, imageData: imageData!)
}
```

このModalViewは `ModalView.swift` にて定義しています。ここでは受け取ったNCMBObjectと画像データをそのまま描画しています。

```swift
struct ModalView: View {
    @Binding var isActive: Bool
    @State var memo: NCMBObject
    @State var imageData: Data
    
    var body: some View {
        HStack {
            Spacer()
            VStack {
                Spacer()
                GeometryReader { geometry in
                    Image(uiImage: UIImage(data: imageData)!)
                        .resizable()
                        .scaledToFill()
                        .frame(width: geometry.size.width, height: geometry.size.height)
                        .clipped()
                }
                if let text: String = memo["text"] {
                    Text(text).padding()
                }
                Button("閉じる") {
                    isActive = false
                }
                Spacer()
            }
            Spacer()
        }
        .padding()
        .background(Color(.black))
    }
}
```

これでカメラメモアプリの完成です。

## コード

今回のデモアプリのコードは[NCMBMania/Swift_CameraMemo_MLKit: Swift SDKによるカメラメモアプリにML Kitを追加した版です](https://github.com/NCMBMania/Swift_CameraMemo_MLKit)にアップロードしてあります。

## まとめ

今回のカメラメモアプリを通して、NCMBの次の機能を利用しました。

- ファイルストア
  - ファイルアップロード
  - ファイルダウンロード
- データストア
  - ファイルストアと紐付けたメモを保存

さらに今回はML Kitを使って、オフラインで写真から日本語を抽出しました。実装はとても簡単なので、写真からデータを取り出して、後で検索に使ったりするのも簡単にできそうです。

スマートフォンアプリでは写真データもよく利用するでしょう。今回のコードを参考にSwift SDKをぜひご利用ください。

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