LoginSignup
9
4

More than 3 years have passed since last update.

【Swift】AirtableとMoyaを使ったAPI通信

Posted at

はじめに

VUI関係の登壇に伴い、スマートスピーカーとiOSアプリ何かで組み合わせたものを簡単に作りたいと思い、Airtableを見つけました。
アプリに取り入れようと思ったところ、Airtableの使い方に関する記事が少なかったため備忘録も兼ねて記事にしました。

・Moya: https://github.com/Moya/Moya (Airtableとの通信用に利用)

・Airtableとは
簡単操作でウェブ上にデータベースを作成して視覚的に操作もできる「Airtable」を使ってみた

何を作るか

自分が大好きなファミレス、デニーズで何曜日にどこで何を食べたかを記録するアプリ
記録したものを参考にスマートスピーカーがメニューをおすすめ(今回の記事では紹介割愛)

Airtable準備

今回は初回の準備からTable作成・ドキュメント確認まで説明を記載致します。

Airtable公式サイトで右上のSign upを押下し必要事項を入力し登録を行う

スクリーンショット 2020-02-16 16.23.17.png

登録後再度サイトを開くとBasesが開かれる。
Workspaceが既にありますが、使い分けるためにWorkspaceを新しく作成したい場合はAdd a workspaceを押下すると新しいWorkspaceが作成できます、(作らなくても可)

下記添付画像のような画面になったら Add a base を押下
今回はスマートスピーカー側のAirtableのテストも兼ねており、初期データを楽にいれたかったためcsvを用意していました。
そのため import a spreadsheetを押下
(CSVを用意していない場合は Start from scratch からぽちぽちTable作れます)

スクリーンショット 2020-02-16 16.27.42.png

Choose a .CSV file を押下し、下記のcsvを選択

スクリーンショット 2020-02-16 16.27.59.png

Dennys.csv
type,name,week,status
ご飯,とろ〜り卵とチーズのオムライス,日,普通
肉,大盛りカットステーキ,月,空いてる
肉,トロけるお肉のビーフシチュー,火,空いてる
パスタ,熟成卵黄と4種チーズのカルボナーラ,水,普通
ご飯,オマール香るエビドリア,木,普通
デザート,デビルズブラウニーサンデー,金,空いてない
デザート,キャラメルハニーパンケーキ,土,空いてない

CSVをいれたあとにTable名を変更したり、項目に間違いがないか確認
(私は店名の項目の入れ忘れに気付いてCSV用意したのに結局ここの画面から項目増やしました…)
スクリーンショット 2020-02-16 16.41.14.png

テーブルの用意ができたらAPIを呼び出すためのドキュメントを確認します。
右上のHELP を押下しAPI documentation押下

スクリーンショット 2020-02-16 16.51.58.png

APIの使用例がとても分かりやすく書いてあります。
Airtableアカウント画面でAPIkeyを作成していない方は、AUTHENTICATIONの項目のaccountを押下しアカウント画面でAPIkeyを作成してください。

スクリーンショット 2020-02-16 16.56.20.png

今回は用途として、記録をするアプリのためCreate recordsを押下しレコードを登録するための情報を確認します。
スクリーンショット 2020-02-16 17.03.49.png

requestに必要な情報やサンプルresponseもあるため、ここを見るところまできたらアプリに組み込む準備はばっちりです。

Xcode準備

Cocoapodsを利用してMoyaをいれる。

pod 'Moya', '~> 13.0'

APIの定義

YOUR_TABLEは自身のTableURL(Create records 押下時右側記載のURL)
YOUR_API_KEYは自身のAPIkeyをいれます。

RecordDennysAPI.swift
enum RecordDennysAPI {
    case PostRecordDennys(type: String, name: String, week: String, status: String, store: String)
}

extension RecordDennysAPI: TargetType {

    // ベースURL
    var baseURL: URL {
        return URL(string: "https://api.airtable.com/v0/YOUR_TABLE")!
    }

    // パス
    var path: String {
        switch self {
        case .PostRecordDennys:
            return ""
        }
    }

    // メソッド
    var method: Moya.Method {
        return .post
    }

    // スタブデータ
    var sampleData: Data {
        var path = ""
        switch self {
        case .PostRecordDennys:
            path = Bundle.main.path(forResource: "SuccessRecordDennys", ofType: "json")!
        }
        return FileHandle(forReadingAtPath: path)!.readDataToEndOfFile()
    }

    // リクエストパラメータ
    var task: Task {
        switch self {
        case .PostRecordDennys(let type,let name,let week,let status,let store):
            let request = PostDennysRecordsRequest(records: [PostDennysRecordsRequest.Fields(fields: PostDennysRecordsRequest.Fields.FieldsElement(type: type, name: name, week: week, status: status, store: store))])
            return .requestJSONEncodable(request)

        }
    }

    // ヘッダー
    var headers: [String : String]? {
        return ["Content-Type": "application/json",
                "Authorization": "Bearer YOUR_API_KEY"]
    }
}

エンコード・デコード用にEncodable・Decodableに準拠したstructを定義。
Create recordsで確認したrequestやresponseを確認しつつ作成。

struct  PostDennysRecordsRequest: Encodable {
    struct  Fields: Encodable {
        struct  FieldsElement: Encodable {
            let type: String
            let name: String
            let week: String
            let status: String
            let store: String
        }
        let fields: FieldsElement
    }
    let records: [Fields]
}

struct  DennysRecordsResponse: Decodable {
    struct  Fields: Decodable {
        struct  FieldsElement: Decodable {
            let type: String
            let name: String
            let week: String
            let status: String
            let store: String
        }
        let id: String
        let fields: FieldsElement
        let createdTime: String
    }
    let records: [Fields]
}

登録項目の入力準備

見た目にはあまりこだわらず、とりあえず店名と料理名以外は常に表示している下部のpickerで選ぶように実装
(本来ならinputviewとかででいい感じにした方が良いと思いますが…)

スクリーンショット 2020-02-16 17.26.44.png

ViewController.swift

    @IBOutlet weak var storeTextField: UITextField!
    @IBOutlet weak var weekTextField: UITextField!
    @IBOutlet weak var dishTypeTextField: UITextField!
    @IBOutlet weak var dishNameTextField: UITextField!
    @IBOutlet weak var stomachStatusTextField: UITextField!
    @IBOutlet weak var choicePickerView: UIPickerView!

    // picker用
    let weekList = ["日曜日","月曜日","火曜日","水曜日","木曜日","金曜日","土曜日"]
    let dishTypeList = ["肉","デザート","麺","ドリア","サイドメニュー"]
    let stomachStatusList = ["お腹いっぱい","普通","空いている"]
    var currentSelectedList = [""]
    var currentSelectedTextField = UITextField()

    override func viewDidLoad() {
        super.viewDidLoad()

        choicePickerView.delegate = self
        choicePickerView.dataSource = self

        // キーボードを非表示にするため
        weekTextField.inputView = UIView()
        dishTypeTextField.inputView = UIView()
        stomachStatusTextField.inputView = UIView()
    }

    /// 曜日TextFieldタップ時
    /// - Parameter sender: UITextField
    @IBAction func tapWeekTextField(_ sender: UITextField) {
        currentSelectedList = weekList
        currentSelectedTextField = weekTextField
        choicePickerView.reloadAllComponents()
    }

    /// 料理タイプTextFieldタップ時
    /// - Parameter sender: UITextField
    @IBAction func tapDishTextField(_ sender: UITextField) {
        currentSelectedList = dishTypeList
        currentSelectedTextField = dishTypeTextField
        choicePickerView.reloadAllComponents()
    }

    /// お腹の具合TextFieldタップ時
    /// - Parameter sender: UITextField
    @IBAction func tapStomachStatusTextField(_ sender: UITextField) {
        currentSelectedList = stomachStatusList
        currentSelectedTextField = stomachStatusTextField
        choicePickerView.reloadAllComponents()
    }

    // キーボード非表示用
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.view.endEditing(true)
    }

// pickerの設定
extension ViewController: UIPickerViewDataSource, UIPickerViewDelegate  {
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }

    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return currentSelectedList.count
    }

    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return currentSelectedList[row]
    }

    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        currentSelectedTextField.text = currentSelectedList[row]
    }
}


API通信の呼び出し

MoyaProviderのインスタンスを作成

    let provider = MoyaProvider<RecordDennysAPI>()

登録ボタンタップ時に先ほど作成したPostRecordDennys呼び出す


    /// 登録ボタンタップ時
    /// - Parameter sender: UIButton
    @IBAction func tapRegisterButton(_ sender: UIButton) {

        let dishType = dishTypeTextField.text ?? ""
        let dishName = dishNameTextField.text ?? ""
        let week = weekTextField.text ?? ""
        let stomachStatus = stomachStatusTextField.text ?? ""
        let store = storeTextField.text ?? ""

        provider.request(.PostRecordDennys(type: dishType,
                                           name: dishName,
                                           week: week,
                                           status: stomachStatus,
                                           store: store)) { (result) in

            switch result {
            case let .success(response):
                let decoder = JSONDecoder()

                do {
                    let recodeResult = try decoder.decode(DennysRecordsResponse.self, from: response.data)
                    print(recodeResult)
                    //アラート表示
                    self.alert(name: recodeResult.records[0].fields.name)
                } catch let error {
                    print(error)
                }
            case  let .failure(error):
                print(error)
                break
            }   
        }
    }

    // 登録完了アラート
    func alert(name: String) {
        let alertController = UIAlertController(title: "デニーズ記録",
                                                message: name + "の登録が完了しました",
                                                preferredStyle: .alert)
        alertController.addAction(UIAlertAction(title: "OK",
                                                style: .default,
                                                handler: nil))
        present(alertController, animated: true)
    }

以上で単純なレコードの作成が行えます。
スマートスピーカー開発のためにAirtableに触れましたが、
MoyaでのRESTAPI実装経験も少なかったため、良い勉強になりました。

参考

9
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4