はじめに
VUI関係の登壇に伴い、スマートスピーカーとiOSアプリ何かで組み合わせたものを簡単に作りたいと思い、Airtableを見つけました。
アプリに取り入れようと思ったところ、Airtableの使い方に関する記事が少なかったため備忘録も兼ねて記事にしました。
・Moya: https://github.com/Moya/Moya (Airtableとの通信用に利用)
・Airtableとは
簡単操作でウェブ上にデータベースを作成して視覚的に操作もできる「Airtable」を使ってみた
何を作るか
自分が大好きなファミレス、デニーズで何曜日にどこで何を食べたかを記録するアプリ
記録したものを参考にスマートスピーカーがメニューをおすすめ(今回の記事では紹介割愛)
Airtable準備
今回は初回の準備からTable作成・ドキュメント確認まで説明を記載致します。
Airtable公式サイトで右上のSign upを押下し必要事項を入力し登録を行う

登録後再度サイトを開くとBasesが開かれる。
Workspaceが既にありますが、使い分けるためにWorkspaceを新しく作成したい場合はAdd a workspaceを押下すると新しいWorkspaceが作成できます、(作らなくても可)
下記添付画像のような画面になったら Add a base を押下
今回はスマートスピーカー側のAirtableのテストも兼ねており、初期データを楽にいれたかったためcsvを用意していました。
そのため import a spreadsheetを押下
(CSVを用意していない場合は Start from scratch からぽちぽちTable作れます)

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

type,name,week,status
ご飯,とろ〜り卵とチーズのオムライス,日,普通
肉,大盛りカットステーキ,月,空いてる
肉,トロけるお肉のビーフシチュー,火,空いてる
パスタ,熟成卵黄と4種チーズのカルボナーラ,水,普通
ご飯,オマール香るエビドリア,木,普通
デザート,デビルズブラウニーサンデー,金,空いてない
デザート,キャラメルハニーパンケーキ,土,空いてない
CSVをいれたあとにTable名を変更したり、項目に間違いがないか確認
(私は店名の項目の入れ忘れに気付いてCSV用意したのに結局ここの画面から項目増やしました…)
テーブルの用意ができたらAPIを呼び出すためのドキュメントを確認します。
右上のHELP を押下しAPI documentation押下

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

今回は用途として、記録をするアプリのためCreate recordsを押下しレコードを登録するための情報を確認します。
requestに必要な情報やサンプルresponseもあるため、ここを見るところまできたらアプリに組み込む準備はばっちりです。
Xcode準備
Cocoapodsを利用してMoyaをいれる。
pod 'Moya', '~> 13.0'
APIの定義
YOUR_TABLEは自身のTableURL(Create records 押下時右側記載のURL)
YOUR_API_KEYは自身のAPIkeyをいれます。
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とかででいい感じにした方が良いと思いますが…)

@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実装経験も少なかったため、良い勉強になりました。