丁寧に解説「AWS Amplifyを使用してシンプルなiOSアプリケーションを構築する」

# 目的
このページでは、AWSから提供されている以下のチュートリアルを試行錯誤の末にクリアした時の情報を共有致します。
公式ページの情報が一部古く、今だと書いてある通りにはできない箇所もあったので、その辺りでどなたかの助けになればいいなと思って書きます。

https://aws.amazon.com/jp/getting-started/hands-on/build-ios-app-amplify/

# 前提
- AWSのアカウントは既に持っていること
- Mac端末を持っていること
- Xcodeがインストール済みであること
- AnacondaもしくはDocker等で仮想環境を作成できること

# 私の環境
- MacBook Pro (macOS Big Sur ver. 11.5.2)
- Xcode ver. 12.4 (12D4e)
- Anaconda Navigator 2.0.3

# 1/7 今回開発に使う仮想環境を作成する。
必須ではないと思いますが、クリーンな環境で開発した方が無駄なつまづきを防げます。
ライブラリやパッケージの依存関係ではできるだけ悩みたくないですからね。

ちなみに私は、Anacondaを使って「iOSAmplify」という名前の仮想環境を作成しました。Python 3.8です。


# 2/7 「はじめに」を実施する。
下記のリンクのページ内容を実施します。

https://aws.amazon.com/jp/getting-started/hands-on/build-ios-app-amplify/

公式ページの右側に、以下のような表示があると思います。
リンクになっている[「手順をご確認ください。」](https://github.com/sebsto/amplify-ios-getting-started/blob/main/install_prerequisites.sh)をクリックしてください。GitHubのページが開きます。

<img width="200" alt="2021-11-21 10.49.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2307205/6c9bea2d-5010-be6d-3617-87a657a9fdd3.png">

Mac端末でTerminalを起動します。仮想環境切り替える必要がある方はお忘れなく。
Anacondaで仮想環境作った方は、以下のコマンドを実行すれば切り替えられます。
<>自体は入力する必要ありません。

```
conda activate <作った仮想環境名>
```

Terminal上で、先ほど開いた[GitHubのページ](https://github.com/sebsto/amplify-ios-getting-started/blob/main/install_prerequisites.sh)に書かれているコマンドを **brew install node まで**実行します。

:::note warn
ここで、一つ確認してください。
Xcodeを起動して、Preferences -> Locations と開くと、
Command Line Tools という欄が空欄になっていないでしょうか？

空欄になっている方は、設定してください。私は、Xcode 12.4 (12D4e) を設定しました。
これが設定できていないと sudo gem install cocoapods がエラーになる場合があるようです。
:::


:::note info
この辺りで一度、端末の再起動をおすすめします。
:::


さて、保留しておいた sudo gem install cocoapods を実行しましょう。
その後のバージョン確認も実施して、公式より新しいバージョンがインストールされていることを確認してください。
ちなみに、私の環境では以下のようになっていました。

```
# brew --verison
Homebrew 3.3.4

# python3 --version
Python 3.8.12

# aws --version
aws-cli/2.4.0 Python/3.9.8 Darwin/20.6.0 source/x86_64 prompt/off

# node --version
v17.0.1

# pod --version
1.11.2
```

もし、sudo gem install cocoapods ができなかった場合は、brew install cocoapods でならインストールできるかも知れません。
gemは、Ruby言語用のパッケージ管理システムで、Homebrewは、MacOS用のパッケージ管理システムです。


# 3/7 「iOSアプリを作成する」を実施する。
下記のリンクのページ内容を実施します。

https://aws.amazon.com/jp/getting-started/hands-on/build-ios-app-amplify/module-one/

### iOS アプリケーションを作成する
Xcode起動 -> Create a new Xcode project を選択します。

:::note warn
公式では[iOS], [Application], [Single View App]を選択するように書かれていますが、
今のXcodeでは、[Single View App]ではなく、[App]と表示されていますので、[App]を選択して[Next]をクリックします。
<img width="300 alt="2021-11-21 12.31.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2307205/6daa5fc5-d5b2-ad46-ff4b-36b89a3af9a4.png">
:::

その後は、公式に従います。プロジェクト名は自由につけてください。私は iOSAmplify としました。
言語とUIはそれぞれ Swift, SwiftUI としてください。設定できたら[Next]をクリックします。
Project Name: iOSAmplify
Language: **Swift**
User Interface: **SwiftUI** 

公式に従い、ディレクトリを選択して、[Create]をクリックします。
そうすると、Xcodeでプロジェクトが開かれると思います。


### メインビューを更新する
ここは公式に従います。ContentView.swift を丸ごと置き換えて保存します。

### 構築およびテストする
ここも公式に従います。

:::note info
シミュレーターの選択の仕方が記載されていなかったので、補足します。
Xcode上で Product -> Destination -> 好きな端末を選択する。
という流れです。ちなみに私はiPhone SE (2nd generation) を選択しました。
:::


# 4/7 「Amplifyを初期化する」を実施する。
下記のリンクのページ内容を実施します。

https://aws.amazon.com/jp/getting-started/hands-on/build-ios-app-amplify/module-two/?e=gs2020&p=build-a-react-app-one

### Amplify CLI をインストールする
公式に従います。

```
# Amplify CLI をインストール
npm install -g @aws-amplify/cli

# バージョン確認
amplify --version
```

ちなみに、私の環境では、バージョンは7.4.4でした。


### Amplify バックエンドを初期化する
公式に従って作業を進める前に、今回使うAWSのIAMユーザが用意できていない方は、作成しておきます。

既に作成済みのIAMユーザを使う場合は、Access key ID, Secret key ID をご用意ください。

厳密には、公式に書かれた手順を進めると、たとえIAMユーザが作られていなくてもTerminal上で作成させてくれます。ですので、それでも構いません。

以下、公式に従って作業を進めます。

:::note info
補足です。
「ターミナルを開き、ディレクトリをプロジェクトディレクトリに変更します。」とありますが、要するに、Terminal上で cd をして3/7で作ったプロジェクトフォルダに移動してくださいという意味です。

移動した場所で ls -al コマンドを実行して、公式のような出力が確認できれば正しく移動できています。
:::

次に、amplify init コマンドを実行すると、複数の質問が表示されてそれに回答する形で設定が進められていきます。質問が公式と異なる部分がありますので補足しておきます。

:::note info
この後、実行するコマンドで選択ミスをした場合、ctrl + c で一度プロセスを停止して、再度 amplify init コマンドを実行すれば大丈夫ですよ。
:::

```
? Enter a name for the project
# (自身のプロジェクト名)が薄く表示されていると思います。デフォルトのまま Enter キーを押してください。

# 次の質問が公式の2,3,4番目の質問をまとめたものになっています。
? Initialize the project with the above configuration?
# Environmentはdev, App typeはiosです。
# Default editorはお好きなものを選んでください。私は、Xcodeを選択しました。
# Default editorを変更したい場合は、一度 n で答えると一つずつ質問されて設定できます。

? Select the authentication method you want to use
# AWS profile

? Setup new user
# 新規でIAMユーザを作りたい方は yes と答え、指示に従ってください。
# 私は既存のIAMユーザを使うため、No と答えました。
? accessKeyId:
# IAMユーザのAccess key ID を入力してください。
? secretAccessKey:
# IAMユーザのSecret access key を入力してください。
? region:
# お好きなリージョンを選択してください。
# 私は ap-northeast-1 を選択しました。東京リージョンです。
```

### プロジェクトに Amplify ライブラリを追加する
:::note warn
公式にも書かれていますが、必ずXcodeを終了させてください。
:::

pod init コマンドの実行まで公式に従ってください。

:::note info
「c.Podfileを更新して次のポッドが含まれるようにしてから、プラトフォームを更新します。」を補足します。

この Podfile は、あなたのプロジェクトフォルダの中に作られています。このファイルを開いて、以下の作業をして保存します。
・platform :ios, '9.0' の行をアンコメントし、13.0に変更
・公式と同じ位置に「pod 'Amplify', '~> 1.0'」「pod 'Amplify/Tools', '~> 1.0'」を追加
:::

公式に従って作業を進めてください。

:::note warn
xed . でXcodeが開かなかった方は、Xcode -> Preferences -> Locations -> Command Line Tools が設定されているか確認してください。
:::


### 実行時に Amplify を初期化する

:::note info
「a.Amplify 設定ファイルをプロジェクトに追加する」を補足します。
「awsconfiguration.json」「amplifyconfiguration.json」をXcodeプロジェクトにドラッグアンドドロップする際に、ポップアップ画面が出てきます。そこで「Create folder references」ではなく「Create Groups」が選択されていることを確認してください。
:::

:::note info
「b.実行時に Amplify クラスを読み込む」を補足します。
新しく作る「Backend.swift」は、「awsconfiguration.json」「amplifyconfiguration.json」と同じ階層に作ってください。
:::

:::note warn
Xcode 12以降のお使いの方には、「AppDelegate.swift」は作られていません。代わりとなるのが「プロジェクト名App.swift」ファイルです。

このファイルを開き、ファイルの最後に以下のコードを追加してください。公式が追記してくださいと言っている Backend.initialize() を含んだ内容になっています。
:::

```swift
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        
        // initialize Amplify
        let _ = Backend.initialize()
        
        return true
    }
}
```

:::note info
上記のコードを追加した直後、「Backend クラスが見つからない」といった類の警告が表示される場合がありますが、一旦無視して次の「設定を検証する」を実施すると警告が消えると思います。
:::



### 設定を検証する
うまく行きましたでしょうか？


# 5/7 「認証を追加する」を実施する。
下記のリンクのページ内容を実施します。

https://aws.amazon.com/jp/getting-started/hands-on/build-ios-app-amplify/module-three/?e=gs2020&p=build-a-react-app-two

### 認証サービスを作成する
amplify add auth コマンドを実行すると、質問が開始されます。
以下に、質問と回答を記載しておきます。

```
Do you want to use the default authentication and security configuration? 
# Default configuration with Social Provider (Federation)

How do you want users to be able to sign in?
# Username

Do you want to configure advanced settings?
# No, I am done.

What domain name prefix do you want to use? 
# 何もせずデフォルトのまま Enter キーを押してください。

Enter your redirect signin URI:
# gettingstarted://

? Do you want to add another redirect signin URI
# N

Enter your redirect signout URI:
# gettingstarted://

? Do you want to add another redirect signout URI
# N

Select the social providers you want to configure for your user pool:
# 何も選ばずに Enter キーを押してください。
```


### 認証サービスをデプロイする
特に書くことがありません。
ここまで順調に進んでおりますでしょうか？

### Amplify 認証ライブラリをプロジェクトに追加する
ここも特に書くことがありません。
Podfile はあなたのプロジェクトフォルダの中にありましたね。

### Amplify 認証ライブラリを実行時に設定する

:::note info
「ビルド時に"development team"を選んでください」というようなエラーが出た方は、Xcodeでプロジェクト名（下の画像の部分です）-> Signing & Capabilities とクリックして Team を設定してください。 
<img width="300 alt="2021-11-21 19.37.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2307205/ed0d3c0e-6bf4-93d7-3508-25a96cb69f2b.png">
:::


### 認証を実行時にトリガーします

:::note info
「a.サインインとサインアウトのコードを追加バックエンドクラスの任意の場所に次の 3 つのメソッドを追加します。」を補足します。

公式で追加するように書かれているコードを、
private init() {
と書かれた行の上に追記してください。

インデントは private init() { に合わせてください。
:::


:::note info
「b.認証ハブのリスナーを追加」を補足します。
下記のコードブロックの中で「// ここに追記してください。」の箇所に追記してください。
:::

```swift
    private init() {
      // initialize amplify
      do {
        try Amplify.add(plugin: AWSCognitoAuthPlugin())
        try Amplify.configure()
        print("Initialized Amplify");
      } catch {
        print("Could not initialize Amplify: \(error)")
      }
        // ここに追記してください。
    }
}
```

:::note info
「c.ユーザーインターフェイスコードを更新する」を補足します。
SignInButtonビューとSignOutButtonビューは「ContentView.swift」の最後に追記してください。
:::


:::note info
「e.ビルドとテスト」を補足します。
最初は、Sign up を選択すると思いますが、メールアドレスの入力の際に「@」が入力できない場合は、Shift + 2 で入力できます。
:::


# 6/7 「APIとデータベースを追加する」を実施する。
下記のリンクのページ内容を実施します。

https://aws.amazon.com/jp/getting-started/hands-on/build-ios-app-amplify/module-four/?e=gs2020&p=build-a-react-app-three

### GraphQL API サービスおよびデータベースを作成する
amplify add api コマンドを実行すると質問が開始されます。
下記に、質問と回答を記載しておきます。

```
? Select from one of the below mentioned services:
# GraphQL

? Here is the GraphQL API that we will create. Select a setting to edit or continue
# Continue

? Choose a schema template:
Single object with fields (e.g., “Todo” with ID, name, description)

? Do you want to edit the schema now?
# Y
```

テキストエディタが開きますので、**全体を公式に従って置き換えて保存**してください。


### クライアントサイドコードを生成する
特に補足はありません。
公式の動画がとても分かりやすいですね。

### API サービスおよびデータベースをデプロイする
amplify push コマンドを実行すると、質問が開始されます。
質問と回答を記載しておきます。公式とは異なる質問になっています。

```
? Do you want to generate code for your newly created GraphQL API
# Y

? Enter the file name pattern of graphql queries, mutations and subscriptions
# デフォルトのまま Enter キーを押してください。

? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions
# Yes

? Enter maximum statement depth [increase from default if your schema is deeply nested]
# デフォルトのまま Enter キーを押してください。

? Enter the file name for the generated code
# デフォルトのまま Enter キーを押してください。
```

### API クライアントライブラリを Xcode プロジェクトを追加する
特に補足はありません。

### 実行時に Amplify ライブラリを初期化する
特に補足はありません。

### GraphQL データモデルとアプリモデル間のブリッジングを追加する

:::note info
補足します。コードブロック中の「// ここに追記してください。」の位置に追記してください。
:::

```swift
// the data class to represents Notes
class Note : Identifiable, ObservableObject {
    var id : String
    var name : String
    var description : String?
    var imageName : String?
    @Published var image : Image?

    init(id: String, name: String, description: String? = nil, image: String? = nil ) {
        self.id = id
        self.name = name
        self.description = description
        self.imageName = image
    }
    // ここに追記してください。   
}
```


### API CRUD メソッドをBackend クラスに追加する

:::note info
「Backend.swift ファイルを開き、Backend クラスの末尾に次のスニペットを追加します。」を補足します。「クラスの末尾に」ということですので、「Backend.swift」の最後の } の一行上に追記してください。
:::


### メモを追加するための編集ボタンを追加する
:::note info
「a.ContentView 構造体に、ユーザーインターフェイスにバインドされた状態変数を追加します。」を補足します。追加した後の構造体は以下のようになります。
:::

```swift
struct ContentView: View {
    @ObservedObject private var userData: UserData = .shared
    @State var showCreateNote = false
    @State var name : String        = "New Note"
    @State var description : String = "This is a new note"
    @State var image : String       = "image"

    var body: some View {

        ZStack {
            if (userData.isSignedIn) {
                NavigationView {
                    List {
                        ForEach(userData.notes) { note in
                            ListRow(note: note)
                        }
                    }
                    .navigationBarTitle(Text("Notes"))
                    .navigationBarItems(leading: SignOutButton())
                }
            } else {
                SignInButton()
            }
        }
    }
}
```

:::note info
「b.ファイルの任意の場所に View 構造体を追加して、ユーザーが新しいメモを作成できるようにします。」を補足します。コードの末尾に追加してください。
:::


### 「スワイプして削除」の操作を追加する
特に補足はありません。

### 構築およびテストする
特に補足はありません。


# 7/7 「画像保存機能を追加する」を実施する。
下記のリンクのページ内容を実施します。

https://aws.amazon.com/jp/getting-started/hands-on/build-ios-app-amplify/module-five/?e=gs2020&p=build-a-react-app-four

### ストレージサービスを作成する
amplify add storage コマンドを実行すると質問が開始されます。
質問と回答を記載しておきます。

```
? Select from one of the below mentioned services:
# Content (Images, audio, video, etc.)

? Provide a friendly name for your resource that will be used to label this category in the project:
# image

? Provide bucket name:
# デフォルトのまま Enter キーを押します。

? Who should have access:
# Auth users only

? What kind of access do you want for Authenticated users?
# create/update, read, delete を space キー で全て選択して Enter キーを押してください。

? Do you want to add a Lambda Trigger for your S3 Bucket?
# N
```

### ストレージサービスをデプロイする
特に補足はありません。

### Amplify ストレージライブラリを Xcode プロジェクトに追加する
特に補足はありません。

### Amplify ストレージプラグインを実行時に初期化する
特に補足はありません。

### 画像の CRUD メソッドを Backend クラスに追加する

:::note info
「クラスの任意の場所に」ということですので、「Backend.swift」の最後の } の一行上に追記してください。
:::


### API からデータを取得するときに画像を読み込む
:::note info
公式ページにも書かれていますが、8行目から17行目までを、ContentView.swiftの「convenience init」の中に追記します。
:::

### UI コードを追加して画像をキャプチャする
:::note info
補足します。「CaptureImageView.swift」は、「ContentView.swift」と同じディレクトリに作成します。
:::


### メモが作成されたら画像を保存する
:::note info
補足します。公式ページに書かれているコードは「ContentView.swift」に2箇所に分けて追記します。
:::

```swift
// at the start of the Content View struct 
@State var image : UIImage? // replace the previous declaration of image
@State var showCaptureImageView = false
```
上記のコードは、「ContentView.swift」の「AddNoteView」内の

```swift
@State var image : String       = "image"
```
の行を置き換える形で追記します。



一方、

```swift
Section(header: Text("PICTURE")) {
    VStack {
        Button(action: {
            self.showCaptureImageView.toggle()
        }) {
            Text("Choose photo")
        }.sheet(isPresented: $showCaptureImageView) {
            CaptureImageView(isShown: self.$showCaptureImageView, image: self.$image)
        }
        if (image != nil ) {
            HStack {
                Spacer()
                Image(uiImage: image!)
                    .resizable()
                    .frame(width: 250, height: 200)
                    .clipShape(Circle())
                    .overlay(Circle().stroke(Color.white, lineWidth: 4))
                    .shadow(radius: 10)
                Spacer()
            }
        }
    }
}
```

上記のコードは、「ContentView.swift」で

```swift
Section(header: Text("PICTURE")) {
                TextField("Name", text: $image)
            }
```
の行を置き換える形で追記します。



次の「Create Note（メモの作成）セクションを変更して、画像とメモを保存します。」の部分は、「ContentView.swift」内の下記のコードを置き換える形で追記します。

```swift
            Section {
                Button(action: {
                    self.isPresented = false
                    let noteData = NoteData(id : UUID().uuidString,
                                            name: self.$name.wrappedValue,
                                            description: self.$description.wrappedValue)
                    let note = Note(from: noteData)

                    // asynchronously store the note (and assume it will succeed)
                    Backend.shared.createNote(note: note)

                    // add the new note in our userdata, this will refresh UI
                    self.userData.notes.append(note)
                }) {
                    Text("Create this note")
                }
            }
```


### 構築およびテストする
問題なく「Run（実行）」できましたでしょうか？


### 複数のプロジェクト間でバックエンドを共有する
私は他のプロジェクトを作成しておりませんので、ここは省略します。


### バックエンドを削除する
特に補足はありません。



# 終わりに
公式ページが更新されるまでの短い命の記事かもしれませんが、
どなたかのお役に立てたなら嬉しいです！
ありがとうございました。

# 参考にした情報
- [1] [公式ページ](https://aws.amazon.com/jp/getting-started/hands-on/build-ios-app-amplify/)
　参考にしたというか、このページをサポートする目的で本記事を執筆しました。
- [2] [[Xcode 12]アプリの起動について変更になった部分まとめ](https://dev.classmethod.jp/articles/xcode12_change_appdelegate/)
- [3] [UnityでiOSビルドするとエラーが出る](https://nn-hokuson.hatenablog.com/entry/2016/12/24/113002)