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

NCMBのSwift SDKを使ってデモアプリを作ってみます。今回はDeepL APIを使った翻訳アプリです。翻訳結果をNCMBのデータストアに保存し、後で確認できるようにします。データストアにデータを保存したり、逆に取り出すのはよくあるケースなので、様々な場面で応用できるはずです。

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

## コードについて

今回のコードは[NCMBMania/Swift_DeepL_App: Swift NCMB SDKとDeepL APIを組み合わせた翻訳アプリのデモです](https://github.com/NCMBMania/Swift_DeepL_App)にアップロードしてあります。実装時の参考にしてください。

## 利用技術について

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

- Swift 5.5
- Xcode 13.0
- NCMB Swift SDK 1.2.2

## 仕様について

今回はシンプルにするために認証は利用していません。入力されたテキストデータと、DeepL APIで翻訳したデータをデータストアに保存します。履歴画面では最大10件のデータを取得、一覧表示します。

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

翻訳アプリで利用するNCMBの機能は次の通りです。

- データストア
  - 翻訳データの保存
  - 翻訳データの検索

## 画面について

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

### ContentView.swift

タブを2つ表示します。1つ目は翻訳画面（TranslateView）、2つ目は履歴画面（HistoryView）です。

```swift
// タブを表示
struct ContentView: View {
    var body: some View {
        TabView {
                    TranslateView()
                        .tabItem {
                            VStack {
                                Image(systemName: "textformat")
                                Text("翻訳")
                            }
                    }.tag(1)
                    HistoryView()
                        .tabItem {
                            VStack {
                                Image(systemName: "list.bullet")
                                Text("履歴")
                            }
                    }.tag(2)
                }
    }
}
```

![translate-app-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/0cca9ad1-94e2-0800-2d4d-ed9b03bb79c6.png)



### TranslateView.swift

翻訳実行画面です。日本語を入力して、翻訳するボタンをタップするとDeepL APIを使って英語に翻訳します。翻訳処理がうまくいったら、日本語と英語をNCMBのデータストアへ保存します。

```swift
// DeepL APIのレスポンス用構造体
struct DeepLResult: Codable {
    let translations: [Translation]
    struct Translation: Codable {
        var detected_source_language: String
        var text: String
    }
}

// 翻訳用ビュー
struct TranslateView: View {
    // 日本語のテキスト
    @State private var originalText = ""
    // 翻訳結果のテキスト
    @State private var translatedText = ""
    // 保存完了した際のアラート制御用
    @State private var uploaded = false
    // JSONデコード用
    let decoder: JSONDecoder = JSONDecoder()
    
    // 翻訳実行
    func translation() {
    }
    
    // 翻訳結果をNCMBに保存する処理
    func saveResult() {
    }
    
    // 画面表示
    var body: some View {
        NavigationView{
            VStack(spacing:0){
                ZStack{
                    VStack(spacing:20){
                        // 日本語入力欄
                        TextEditor(text: $originalText)
                            .textFieldStyle(RoundedBorderTextFieldStyle())
                            .frame(maxWidth: 280, maxHeight: 100)
                        // 翻訳結果があれば表示
                        if translatedText != "" {
                            Text(translatedText).padding()
                        }
                        // 翻訳実行するボタン
                        Button(action: {
                            translation()
                        }, label: {
                            Text("翻訳する")
                        })
                    }
                }
            }
            .navigationBarTitle("翻訳", displayMode: .inline)
            // 保存完了時のアラート用
            .alert(isPresented: $uploaded, content: {
                Alert(
                    title: Text("保存完了"),
                    message: Text("翻訳結果を記録しました"),
                    dismissButton: .default(Text("閉じる"))
                )
            })
                
        }
    }
}
```

![translate-app-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/0fe79d4d-8e9e-8ece-a1c5-7ae5d700b9f8.png)


### HistoryView.swift

翻訳履歴データを検索、結果を一覧表示する画面です。最初は日本語だけを表示します。

```swift
// NCMBから取得した結果が入るObservableObject
class Histories: ObservableObject {
    @Published var items: [NCMBObject] = []
}

// 履歴画面用
struct HistoryView: View {
    // 履歴が入る配列
    @ObservedObject var History = Histories()
    // NCBMから履歴を取得する処理
    func getHistories() {
    }
    // 画面表示
    var body: some View {
        NavigationView{
            VStack(spacing:0){
                ZStack{
                    List {
                        ForEach(self.History.items, id: \.objectId) { translate in
                            // translateはNCMBObjectです
                            // タスク文字列をタップしたらDetailViewを呼び出します
                            NavigationLink(destination: DetailView(translate: translate)) {
                                Text((translate["original"] ?? "") as String)
                            }
                        }
                    }
                }
            }
            .navigationBarTitle("履歴", displayMode: .inline)
            .onAppear {
                // 画面表示したら履歴を取得する処理を呼び出す
                getHistories()
            }
        }
    }
}
```

![translate-app-5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/ef938d4f-0dfe-f485-2bf4-18d248c4119c.png)

### DetailView.swift

一覧でタップされた翻訳履歴の詳細表示を行うViewです。一覧から渡されたデータストアオブジェクトを表示します。

```swift
// 詳細表示画面
struct DetailView: View {
    @Environment(\.presentationMode) var presentation
    // 一覧画面からNCMBObjectを受け取る
    @State var translate: NCMBObject
    
    // 画面表示
    var body: some View {
        NavigationView{
            VStack(spacing:0){
                Text((translate["original"] ?? "") as String)
                Spacer()
                Text((translate["translate"] ?? "") as String)
                Spacer()
            }
        }
    }
}
```

![translate-app-6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/a0f6bf86-3cd1-cc7f-22f0-1800dd8722fb.png)


## 今回のプロジェクト

今回は言語が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)

## NCMBのAPIキーを取得

[mBaaSでサーバー開発不要！ | ニフクラ mobile backend](https://mbaas.nifcloud.com/)にてアプリを作成し、アプリケーションキーとクライアントキーを作成します。

## DeepLのAPI認証キーを取得

[DeepL翻訳：世界一高精度な翻訳ツール](https://www.deepl.com/translator)にて開発者登録し、API認証キーを取得します。


## APIKey.plistの作成

[API keyを.plistにStringで保存して隠す(例: Google Maps Api) - Qiita](https://qiita.com/Saayaman/items/c50000e6103358c8c4d6)を参考に、KeyManger.swiftを作成します。

```swift
import Foundation

// APIKey.plistの内容を返してくれるオブジェクト
struct KeyManager {
    private let keyFilePath = Bundle.main.path(forResource: "APIKey", ofType: "plist")
    func getKeys() -> NSDictionary? {
        guard let keyFilePath = keyFilePath else {
            return nil
        }
        return NSDictionary(contentsOfFile: keyFilePath)
    }
    func getValue(key: String) -> AnyObject? {
        guard let keys = getKeys() else {
            return nil
        }
        return keys[key]! as AnyObject
    }
}
```

先ほど取得したNCMBのAPIキー、DeepLのAPI認証キーをそれぞれAPIKey.plistファイル（新規作成）に設定します。キーは次の通りです。

- DeepLAuthKey
- ClientKey
- ApplicationKey

![ScreenShot_ 2022-07-06 23.57.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/0c843b58-f03b-0ec8-59c4-a2bc9083d721.png)


## 初期化

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

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

```swift
import SwiftUI
import NCMB
```

プロジェクトの App にて、init関数を定義します。その中でアプリケーションキー、クライアントキーを取得してNCMBの初期化処理を行います。

```swift
struct translationAppApp: App {
    init() {
        let applicationKey = KeyManager().getValue(key: "ApplicationKey") as! String
        let clientKey = KeyManager().getValue(key: "ClientKey") as! String
        // APIキーの設定とSDK初期化
        NCMB.initialize(applicationKey: applicationKey, clientKey: clientKey)
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
```

## まとめ

今回は翻訳アプリの仕様と画面、NCMBの初期化までを解説しました。[次は翻訳処理とデータ登録を解説します](https://qiita.com/goofmint/items/73c96af20c1963bc8fee)。