iOSアプリからスプレッドシートにデータを書き込む

iOSアプリからスプレッドシートにデータを書き込みたい時が人生にはあります。その方法です。

# 大まかな流れ
iOSからスプレッドシートのデータを書くためには、スプレッドシート側にGAS（Google Apps Script）を実装する必要があります。GASでスプレッドシートをWriteするPOSTを実装し、iOSからそこ目掛けて通信する実装をすれば完成です。

# GASの実装
スプレッドシートの「シート1」シートに下記のようなデータがあるものとします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143255/667fa126-e963-b272-a29e-375c18659bf5.png" width="400">

スプレッドシートメニューの`ツール＞スクリプトエディタ`を選択します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143255/f857ab9d-4b4b-0e8f-4f85-21286af26fd5.png" width="500">


するとApps Scriptエディタが開きます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143255/9a31bc02-8aa9-9784-29ee-cf1ff72da7e1.png" width="500">

ここに処理を実装していきます。

## Writeする
スプレッドシートにデータを書き込む方法は以下の通りです。

```gs
function myFunction(){
    var targetFileID = DriveApp.getFileById("xxxxx")
    var targetFile = SpreadsheetApp.open(targetFileID)
    var targetSheet = targetFile.getSheetByName("シート1")

    var targetRow = targetSheet.getLastRow() + 1
    targetSheet.getRange(targetRow, 1, 1, 4).setValues([["次郎", 28, 30, 1]])
}
```

1. `DriveApp.getFileById("xxxxx") `でGoogle Drive内にある対象ファイルのIDを取得します。※1
2. `SpreadsheetApp.open(targetFileID)`で対象ファイルを取得します。
3. `targetFile.getSheetByName("シート1")`で対象ファイル内の対象シートを取得します。
4. `targetSheet.getLastRow() + 1`で現在入力済みの最終行を取得し1を足すことで次の空白行の行数を取得します。
5. `targetSheet.getRange(targetRow, 1, 1, 4).setValues([["次郎", 28, 30, 1]])`で対象シート内の対象となる行（ここでは4行目）1列目のセルを起点に、1行分4列目までに`次郎", 28, 30, 1`の値を書き込みます
6. デバッグするにはメニューバーにある`▷実行`を押します。
   * スプレッドシートの内容にアクセスする場合、[初回はアクセス権限に関する表示](https://qiita.com/kensuzuk/items/eae3396d3b9e29c439f6)が出るので対処します。

※1:ファイルIDはURLの https://docs.google.com/spreadsheets/d/xxxxx/edit#gid=0 のxxxxxにあたる部分です

実行後、スプレッドシート側に書き込みが行われていることが確認できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143255/0dd6baf7-174b-80fd-5b66-fecb0244954c.png" width="400">

## POSTを実装する

GASでWebAPIを実装できます。GASの中で`POSTならdoPost(e)`を定義し実装します。これをウェブアプリとして公開するとWebAPIが発行され外部からアクセスできるようになります。

### doPost(e)
スプレッドシートにWebAPI経由で書き込む方法は以下の通りです。

```
//POSTされるデータ形式
postData: {
    'contents' : '{"users": [{"name": "次郎", "age": "28", "count": "30", "error": "1"}]}'
}
```
前提として、GASのdoPost(e)で送られたデータは、`e.postData["contents"]`に格納されることになっています。これを踏まえ、doPost内の処理を実装していきます。

```
function doPost(e){
    var targetFileID = DriveApp.getFileById("xxxxx")
    var targetFile = SpreadsheetApp.open(targetFileID)
    var targetSheet = targetFile.getSheetByName("シート1")
    var targetRow = targetSheet.getLastRow() + 1

    var json = JSON.parse(e.postData["contents"])
    var user = json["users"][0]

    targetSheet.getRange(targetRow, 1, 1, 4).setValues([[user.name, user.age, user.count, user.error]])
}
```

1. 書き込む行（データ最下行の1つ下の行）の取得までは`Writeする`と同じです。
2. `JSON.parse(e.postData["contents"])`でPOSTされたデータそのものを取得します。
3. `users`キーのバリューとして`user`を格納した配列があり、ここではその1つ目を取得します。
4. `setValues()`でPOSTされたuserデータの書き込みを行います。
5. `デプロイ`を行い、WebAPIを発行します。
 * [こちらの手順](https://qiita.com/kensuzuk/items/486a8a63436b73aa148d)を参考にして下さい。

すると、このような形でWebAPIが取得できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143255/66b1f689-16c7-13ec-43d4-56b7b6f8217b.png" width=500>

ここではusersには1つしかデータがありませんが、
当然一度に複数人のデータを送り全部書き込ませることも可能です。

内容の修正を行った際は忘れずに[WebAPIの処理内容も更新](https://qiita.com/kensuzuk/items/486a8a63436b73aa148d#webapi%E3%82%92%E6%9B%B4%E6%96%B0%E3%81%99%E3%82%8B)してください。

# iOSの実装
ここではiOSからGASのWebAPIへのアクセスを容易にするために[Alamofire](https://github.com/Alamofire/Alamofire)と[SwiftyJSON](https://github.com/SwiftyJSON/SwiftyJSON)を用います。

```swift
import UIKit
import Alamofire
import SwiftyJSON

class ViewController: UIViewController {

    let url = "https://script.google.com/macros/s/xxx/exec" //上記で取得したWebAPI

    @IBOutlet weak var name: UITextField! 
    @IBOutlet weak var age: UITextField!
    var count: Int!
    var error: Int!
    
    override func viewDidLoad() {
        //何かしら下記2つの値を取得する処理など
        count = 45
        error = 3
    }

    @IBAction func onTap(_ sender: Any) {
        
        //POSTするデータの生成
        var user: Dictionary<String, Any> = [:]
        user["name"] = name.text //香織
        user["age"] = age.text //24
        user["count"] = self.count
        user["error"] = self.error
        
        var users: Array<Dictionary<String, Any>> = []
        users.append(user)
        
        //POST処理
        AF.request(url,
                   method: .post,
                   parameters: ["users": users],
                   encoding: JSONEncoding.default,
                   headers: nil
        ).responseJSON{ (response) in
            switch response.result {
                case .success(let elm):
                    print("success")
                    
                case .failure(let error):
                    print("error", error)
            }
        }
    }
}
```

1. `doPost(e)`で記載した各データと同じキーを用いて辞書型オブジェクトを作成し、配列に格納します。
2. `AF.request(_, method: .post)`で上で取得したWebAPIにPOSTで通信を行います。また、パラメータを辞書型で渡し、エンコードをJSON形式にします。
3. POSTを行うと、GAS側からのレスポンスが来るので受け口を用意します。
   * `.success(let elm)`のelmにはGAS側のdoPostに実装したreturnの値が入ります。必要に応じて実装してください。
   * `.failure(let error)`はPOSTに失敗した場合に実行されます。

これをシュミレータで実行し、ボタンを押すとこうなります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143255/528c2d06-ed1e-18be-432a-7c22fbfaf182.png" width=400>


# まとめ
iOSアプリからスプレッドシートにデータを書き込む方法をまとめました。[スプレッドシートのデータを読み込む方法](https://qiita.com/kensuzuk/items/748f28d7f411cf984e7f)もまとめましたので、よろしければこちらもご覧ください。

## おまけ
`doPost(e)`を実装する際、POSTデータを修正する度にGASとiOSを直しiOSシミュレータからアクセスするのは面倒です。
GAS側で下記のように実装し実行する関数を`debugDoPost`にすることで、擬似的に外部からのPOSTを実装できるので効率的です。

```
function debugDoPost(){
  const e = {
    postData: {
      'contents' : '{"users": [{"name": "次郎", "age": "28", "count": "30", "error": "1"}]}'
    }
  }
  doPost(e)
}
```