AWS AmplifyによるiOSアプリ開発入門（Part2）

## 概要

iOSアプリからS3にデータをアップロードする部分を実装します。
また、認証部分はCognitoを使い、S3バケットの作成はAWS Amplifyを使って行います。

## 前提条件

[AWS AmplifyによるiOSアプリ開発入門（Part1）](https://qiita.com/papi_tokei/items/55d48a1a9a9e56d7cbd4)が完了していること
Part1で作成したXcodeプロジェクトをそのまま使用します

## AWS Amplifyを使ってS3バケットを作成

```shell

# Xcodeのルートプロジェクトに移動
$ cd ~/Downloads/amplify-learn

# S3バケットを作成
$ amplify add storage
# サービスを聞かれるので、「Content」を選択
# ラベル名を聞かれるので、任意のラベル名を入力（今回はデフォルトの設定のまま）
# S3のバケット名を聞かれるので、任意の名前を入力（今回はデフォルトの設定のまま）
# 誰がアクセス出来るかを聞かれるので、「Auth users only」を選択
# 認証ユーザーの権限を聞かれるので、「read/write」を選択

# S3バケットを作成するようにクラウド側に命令
$ amplify push
# 続けていいか聞かれるので、trueを選択
```

上記コマンドを実行すると、S3バケットが作成されます
また、IDプールで設定されている認証されたロールに、作成したバケットへのアクセス権限が
付与されていることが確認できると思います
→つまり、Cognitoで認証されたユーザは今回作成したS3バケットにアクセスできる権限が自動的に付与されれるということ

## S3アップロード機能の実装

まずは、必要となるSDKのインストールを行います
Podfileを以下のように編集してください

```:Podfile
target 'amplify-learn' do
  use_frameworks!

  # Pods for amplify-learn
  pod 'AWSMobileClient', '~> 2.7.0'
  pod 'AWSAuthUI', '~> 2.7.0'
  pod 'AWSUserPoolsSignIn', '~> 2.7.0' 
  pod 'AWSS3', '~> 2.7.0' # S3用のSDKを追加
end
```

```shell

# Xcodeのルートプロジェクトに移動
$ cd ~/Downloads/amplify-learn

# 必要なSDKを追加
$ pod install --repo-update
```

次に、ボタンをクリックするとデータをアップロードする機能を追加します
amplify-learn.xcworkspaceを開きます
Main.storyboardに「S3アップロードボタン」を追加し、ViewController.swiftに対してアクション接続を追加してください
connectionはAction、NameをuploadDataToS3として設定します

ViewController.swiftを以下のように修正してください
**transferUtility.uploadData関数の引数であるバケット名は適宜変更してください**

```swift:ViewController.swift
import UIKit
import AWSMobileClient

// 以下を追加
import AWSS3

class ViewController: UIViewController {

    @IBAction func pushLogoutButton(_ sender: Any) {
        // サインアウト処理
        AWSMobileClient.sharedInstance().signOut()
        
        // サインイン画面を表示
        AWSMobileClient.sharedInstance().showSignIn(navigationController: self.navigationController!, { (userState, error) in
            if(error == nil){       //Successful signin
                DispatchQueue.main.async {
                    print("Logged In")
                }
            }
        })
    }
    
    // 以下の関数内の処理を追加
    @IBAction func uploadDataToS3(_ sender: Any) {
        let expression = AWSS3TransferUtilityUploadExpression()
        expression.progressBlock = {(task, progress) in
            DispatchQueue.main.async(execute: {
                // Do something e.g. Update a progress bar.
            })
        }
        
        var completionHandler: AWSS3TransferUtilityUploadCompletionHandlerBlock?
        completionHandler = { (task, error) -> Void in
            DispatchQueue.main.async(execute: {
                // Do something e.g. Alert a user for transfer completion.
                // On failed uploads, `error` contains the error object.
            })
        }
        
        let data: Data = ("hello world").data(using: .utf8)! // Data to be uploaded
        let transferUtility = AWSS3TransferUtility.default()
        
        let format:DateFormatter = DateFormatter()
        format.dateFormat = "yyyyMMdd-hhmmss"
        let dateString:String = format.string(from: Date())
        
        transferUtility.uploadData(data,
           bucket: "YOUR_BUCKET_NAME",
           key: "uploads/"+dateString+".txt",
           contentType: "text/plain",
           expression: expression,
           completionHandler: completionHandler).continueWith {
            (task) -> AnyObject? in
            if let error = task.error {
                print("Error: \(error.localizedDescription)")
            }
            
            if let _ = task.result {
                // Do something with uploadTask.
            }
            return nil;
        }
    }
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        AWSMobileClient.sharedInstance().initialize { (userState, error) in
            if let userState = userState {
                switch(userState){
                case .signedIn:
                    DispatchQueue.main.async {
                        print("Logged In")
                    }
                case .signedOut:
                    AWSMobileClient.sharedInstance().showSignIn(navigationController: self.navigationController!, { (userState, error) in
                        if(error == nil){       //Successful signin
                            DispatchQueue.main.async {
                                print("Logged In")
                            }
                        }
                    })
                default:
                    AWSMobileClient.sharedInstance().signOut()
                }
            } else if let error = error {
                print(error.localizedDescription)
            }
        }
    }
}
```

上記コードの詳細な説明は行いませんが、重要な関数は以下です。
この部分で、認証周りのデフォルトの設定を取得してくれますので、
認証周りを意識せずにS3アップロードを行うことが可能です。
すでにサインイン済みであればデータをS3にアップロードできますし、サインアウト状態では、アップロードできません。

```swift
let transferUtility = AWSS3TransferUtility.default()
```


最終的な画面は以下の図のようになるかと思います

<img width="423" alt="iPhone_XR_-_12_1 2.png" src="https://qiita-image-store.s3.amazonaws.com/0/192873/28cb1410-c8db-cd65-c412-36f9a1d11ce7.png">

S3アップロードボタンをクリックすると、S3にデータが送信され保存されていることが確認できると思います。
※もし、サインイン画面が表示された場合は、Part1で作成したユーザでサインインを行ってください


## まとめ

AWS Amplifyを使用したS3バケットの作成方法について説明しました。
もちろん、AWS CLIからもバケットを作成することは出来ますが、AWS Amplifyを用いると、
プロジェクトごとにバックエンドをまとめることが可能になりますので便利です。
つまり、`amplify delete`とすると、CognitoもS3バケットもまとめて削除できます。

また、iOS SDKを使用して、データをS3に保存する部分も説明しました。
AWSMobileClientを使えば、バックエンドの処理をすべて任せることが出来るので、
S3アップロードの実装で認証周りを気にする必要はありません。

次はApp Syncとの連携について書く予定ですが、少し時間がかかるかもですorz