#6 得正訂飲料 App|Part 1: 串接 Airtable API

前陣子一直在網路上看到得正這間飲料店,朋友也說好喝,有一次剛好看到並買來喝喝看,結果真的很不錯!於是決定來挑戰看看,練習串接後台 API,新增讀取後台資料。

◾️功能應用

◾️ 檢視飲料 menu,將 menu 存在 Airtable,透過 API 讀取 JSON 。

◽️新增訂購的飲料 (可選擇甜度、冰塊、大小及配料)

◽️訂單上傳至Airtable

◽️訂單可修改編輯或刪除

以Airtable 為後台,串接 API 抓取 JSON 資料

1.註冊並建立飲料Menu資料庫

→ 將需要的欄位資料建立在飲料Menu中:

2.連結API頁面

選取要查看的Workspace

→ 根據 AUTHENTICATION 頁面說明,建議使用personal access tokens,在/create/tokens產生Secret API Token,以利後續串接API。

→ 填寫好token的名稱、選擇需要的scope與access,然後點選Creat token。

→ 記得另存以下token,因為只會顯示一次。

🌟🌟🌟備註🌟🌟🌟
官方說明API key將於2024年1月底不再支援使用,建議使用 personal access tokens 和 OAuth access tokens。
https://airtable.com/developers/web/api/authentication

3.以Postman檢查Airtable後台功能是否正常

→ 將Authorization頁面的API 的網址貼在欄位1中,並選擇GET取得資料。
→ 欄位2的Type 選 “Bearer Token”
→ 將另存的token貼在欄位3中

→ 接著就可以在下方見到Json格式的資料

程式撰寫

🔸透過Codable將JSON物件轉換成自訂的型別

import Foundation

struct MenuResponse: Codable {
let records: [Record]
}
struct Record: Codable {
let id: String
let fields: Field
}

struct Field: Codable {
let classification: String
let name: String
let medium: Int
let large: String
}

(Menu)TableViewCell

→ Table View Cell設定ID,style選擇custom

→ 將要顯示於cell中的元件拉@IBOutlet 在Table View Cell

(Menu)TableViewController

→ 定義抓資料的fetchMenu

    //定義抓資料的fetchMenu
func fetchMenu() {
let urlString = "https://api.airtable.com/v0/appK8yMkSmXCX0Hg6/Menu"
if let url = URL(string: urlString) {
var request = URLRequest(url: url)
request.setValue("Bearer patKwiqVg9pn00EPC.e03c8fc0b41480f7ce9b57d84039979edc288edf570304bbee3c89bb00a3f2a9", forHTTPHeaderField: "Authorization")
//{}的程式是closure,資料下載完成時會執行{}的程式,傳入data(抓到的資料),response(後台回傳抓資料的結果),error(錯誤資訊)三個參數。
URLSession.shared.dataTask(with: request) { data, response , error in
if let data {
let decoder = JSONDecoder()
do {
//透過decode將Data轉成對應的物件內容
let menuResponse = try decoder.decode(MenuResponse.self, from: data)
//需在main thread執行UI相關的程式
DispatchQueue.main.async {
self.allDrinks = menuResponse.records
for drink in self.allDrinks {
switch drink.fields.classification {
case "#Original TEA 原茶系列":
self.teaDrinks.append(drink)
case "#Classic MILK TEA 經典奶茶":
self.milkteaDrinks.append(drink)
case "#Double FRUIT 雙重水果":
self.fruitDrinks.append(drink)
case "#Fresh MILK 鮮奶系列":
self.milkDrinks.append(drink)
case "#Cheese MILK FOAM 芝士奶蓋":
self.cheeseDrinks.append(drink)
default:
break
}
}

//載完資料後filteredDrinks先出現的是原萃系列的品項
self.filteredDrinks = self.teaDrinks
//如果沒有reload data,表格不會更新,只會看到一片空白
self.tableView.reloadData()
}
print("get data")
} catch {
print(error)
}
} else {
print("下載失敗")
}
}.resume()
print("function dataTask執行完會先回傳task,然後呼叫task的resume啟動它")
}
}

→ 建立array存放資料

    //宣告儲存圖片的drinksPicture
let drinksPicture = ["tea", "milk tea", "fruit", "milk", "cheese"]

//宣告變數儲存對應的影片
var filteredDrinks = [Record]()
var allDrinks = [Record]()
var teaDrinks = [Record]()
var milkteaDrinks = [Record]()
var fruitDrinks = [Record]()
var milkDrinks = [Record]()
var cheeseDrinks = [Record]()

//宣告index變數儲存並判斷對應的內容
var index = 0

→ segmentedControl拉@IBOutlet與@IBAction,當點選不同的segment時,會更新表格對應的內容,顯示不同類別的飲料品項。

→ 表格顯示資料

  • 表格有幾段 (此次只有一段)
    //表格有幾段
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
  • 每一段表格有幾列
    //每一段表格有幾列
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredDrinks.count
}
  • 回傳哪一個cell,可從參數 indexPath 得到 section & row
    //回傳哪一個cell,可從參數 indexPath 得到 section & row
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MenuTableViewCell", for: indexPath) as! MenuTableViewCell
let drink = filteredDrinks[indexPath.row]
//顯示飲料名稱
cell.nameLabel.text = drink.fields.name
//顯示中杯飲料價位,mPrice型別為Int
cell.mPrice.text = "$\(drink.fields.medium)"
//顯示中杯飲料價位,lPrice型別為String
cell.lPrice.text = "$"+drink.fields.large

return cell
}

→ 建立與更新畫面有關的函式,後面可以不用一直重複寫

func updateUI() {
//根據index顯示對應的圖片
drinkImageView.image = UIImage(named: drinksPicture[index])
//當前的PageControl會根據index而變換
drinkPageControl.currentPage = index
//SegmentedControl會根據index而變換
typeSegmentedControl.selectedSegmentIndex = index
}

→ 進入畫面時:

override func viewDidLoad() {
super.viewDidLoad()

fetchMenu()
updateUI()

//segmentedControl正常情況下的字是黑色
typeSegmentedControl.setTitleTextAttributes([.foregroundColor: UIColor.black], for: .normal)
//segmentedControl被選中的時候字是白色
typeSegmentedControl.setTitleTextAttributes([.foregroundColor: UIColor.white], for: .selected)

//固定row的高度
tableView.rowHeight = 85
}

Part2

努力產出中!

Reference

--

--