Published in
23 min readOct 28, 2022
[作業]訂飲料App:五桐號
這個訂飲料App是個能更加熟悉TableView(CollectionView)、API的作業,也藉這個機會嘗試使用Airtable在雲端建置Menu、Order、Shop三個表格,讓資料可以遠端維護及更新,不囉唆,各頁面程式碼如下:
import UIKit
import Kingfisherclass MenuTableViewController: UITableViewController {
lazy var filteredDrinks = [Record]()
@IBOutlet weak var typeSegment: UISegmentedControl!
@IBOutlet weak var searchBar: UISearchBar!
var drinksAll = [Record]()
var drinks1 = [Record]()
var drinks2 = [Record]()
var drinks3 = [Record]()
var drinks4 = [Record]()
var drinks5 = [Record]()
var drinks6 = [Record]()
var index = 0
override func viewDidLoad() {
super.viewDidLoad()
fetchdrink()
}
func fetchdrink() {
if let url = URL(string: "https://api.airtable.com/v0/appWKQiePYTFRgmRM/Menu") {
var request = URLRequest(url: url)
request.setValue("Bearer keySfV0P5ClR74OTq", forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: request) { data, response , error in
if let data {
do {
let decoder = JSONDecoder()
let drinkMenu = try decoder.decode(DrinkMenu.self, from: data)
DispatchQueue.main.async {
self.drinksAll = drinkMenu.records
for drink in self.drinksAll {
switch drink.fields.type {
case "醇茶":
self.drinks1.append(drink)
case "奶茶":
self.drinks2.append(drink)
case "鮮奶":
self.drinks3.append(drink)
case "奶霜":
self.drinks4.append(drink)
case "農摘":
self.drinks5.append(drink)
case "季節限定":
self.drinks6.append(drink)
default:
break
}
}
self.filteredDrinks = self.drinks1
self.tableView.reloadData()
}
} catch {
print(error)
}
} else {
print("Error")
}
}.resume()
}
}
func search(_ searchTerm: String) {
var currentDrink = [Record]()
switch index {
case 0:
currentDrink = drinks1
case 1:
currentDrink = drinks2
case 2:
currentDrink = drinks3
case 3:
currentDrink = drinks4
case 4:
currentDrink = drinks5
case 5:
currentDrink = drinks6
default:
break
}
if searchTerm.isEmpty {
filteredDrinks = currentDrink
} else {
filteredDrinks = currentDrink.filter({ $0.fields.drinkname.contains(searchTerm)
})
}
tableView.reloadData()
}
@IBAction func changeType(_ sender: UISegmentedControl) {
index = sender.selectedSegmentIndex
switch index {
case 0:
if searchBar.text == "" {
filteredDrinks = drinks1
} else {
filteredDrinks = drinks1.filter({ $0.fields.drinkname.contains(searchBar.text!)
})
}
case 1:
if searchBar.text == "" {
filteredDrinks = drinks2
} else {
filteredDrinks = drinks2.filter({ $0.fields.drinkname.contains(searchBar.text!)
})
}
case 2:
if searchBar.text == "" {
filteredDrinks = drinks3
} else {
filteredDrinks = drinks3.filter({ $0.fields.drinkname.contains(searchBar.text!)
})
}
case 3:
if searchBar.text == "" {
filteredDrinks = drinks4
} else {
filteredDrinks = drinks4.filter({ $0.fields.drinkname.contains(searchBar.text!)
})
}
case 4:
if searchBar.text == "" {
filteredDrinks = drinks5
} else {
filteredDrinks = drinks5.filter({ $0.fields.drinkname.contains(searchBar.text!)
})
}
case 5:
if searchBar.text == "" {
filteredDrinks = drinks6
} else {
filteredDrinks = drinks6.filter({ $0.fields.drinkname.contains(searchBar.text!)
})
}
default:
break
}
tableView.reloadData()
}
// MARK: - Table view data sourceoverride func numberOfSections(in tableView: UITableView) -> Int {
return 1
}override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredDrinks.count
}override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "\(DrinkTableViewCell.self)", for: indexPath) as! DrinkTableViewCell
let drink = filteredDrinks[indexPath.row]
cell.nameLabel.text = drink.fields.drinkname
cell.ihLabel.text = ""
for i in 0..<drink.fields.ih.count {
cell.ihLabel.text! += "\t\(drink.fields.ih[i])\t"
}
cell.sizeLabel.text = ""
for i in 0..<drink.fields.size.count {
cell.sizeLabel.text! += "\t\(drink.fields.size[i])\t"
}
cell.priceLabel.text = ""
for i in 0..<drink.fields.price.count {
cell.priceLabel.text! += "\t\(drink.fields.price[i])\t"
}
cell.picImageView.kf.setImage(with: drink.fields.picture[0].url)
return cell
}
// MARK: - Navigationoverride func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let controller = segue.destination as? OrderTableViewController, let row = tableView.indexPathForSelectedRow?.row {
controller.drinkSelected = filteredDrinks[row].fields
}
}
}extension MenuTableViewController: UISearchBarDelegate {
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
let searchTerm = searchBar.text ?? ""
search(searchTerm)
searchBar.resignFirstResponder()
}
}import UIKitclass OrderListTableViewController: UITableViewController {
var orderRecord: OrderRecord!
var customerOrder: CustomerOrder!
var totalQty = 0
var totalPrice = 0
@IBOutlet weak var customerName: UITextField!
@IBOutlet weak var phoneLabel: UITextField!
@IBOutlet weak var detailLabel: UILabel!
@IBOutlet weak var uploadOrders: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
var orders = OrderRecord.loadOrders() ?? []
orders.append(orderRecord)
customerOrder = CustomerOrder(records: orders)
updateOrder()
OrderRecord.saveOrders(customerOrder.records)
}func updateOrder() {
for i in customerOrder.records.indices {
customerOrder.records[i].fields.customerName = customerName.text ?? ""
customerOrder.records[i].fields.customerPhone = phoneLabel.text ?? ""
}
totalQty = 0
totalPrice = 0
for _ in customerOrder.records {
totalQty += orderRecord.fields.qty
totalPrice += orderRecord.fields.qty * orderRecord.fields.price
}
detailLabel.text = "總共\(totalQty)杯,總金額為\(totalPrice)元"
}
@IBAction func uploadOrders(_ sender: UIButton) {
uploadOrders.isEnabled.toggle()
updateOrder()
let orderBody = customerOrder
if let url = URL(string: "https://api.airtable.com/v0/appWKQiePYTFRgmRM/Order") {
var request = URLRequest(url: url)
request.setValue("Bearer keySfV0P5ClR74OTq", forHTTPHeaderField: "Authorization")
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let encoder = JSONEncoder()
request.httpBody = try? encoder.encode(orderBody)
URLSession.shared.dataTask(with: request) { data, response , error in
if let data, let content = String(data: data, encoding: .utf8) {
print(content)
}
}.resume()
}
let controller = UIAlertController()
let okAction = UIAlertAction(title: "訂單上傳成功", style: .default)
controller.addAction(okAction)
present(controller, animated: true)
customerOrder.records = []
OrderRecord.saveOrders(customerOrder.records)
detailLabel.text! += " 已下訂!"
tableView.reloadData()
}
// MARK: - Table view data sourceoverride func numberOfSections(in tableView: UITableView) -> Int {
return 1
}override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return customerOrder.records.count
}override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "\(OrderTableViewCell.self)", for: indexPath) as! OrderTableViewCell
let record = customerOrder.records[indexPath.row]
cell.drinkNameLabel.text = record.fields.drinkName
cell.sizeLabel.text = record.fields.size
cell.qtyLabel.text = "\(record.fields.qty)"
cell.priceLabel.text = "\(record.fields.price)"
cell.addonLabel.text = "\(record.fields.addon)"
cell.iceLabel.text = record.fields.ice
cell.sugarLabel.text = record.fields.sugar
return cell
}override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
customerOrder.records.remove(at: indexPath.row)
updateOrder()
tableView.deleteRows(at: [indexPath], with: .automatic)
OrderRecord.saveOrders(customerOrder.records)
}
}import UIKit
import MapKitclass ShopListTableViewController: UITableViewController {
var shops = [ShopField]()
var shopDics = [String: [ShopField]]()
var counties = [String]()
override func viewDidLoad() {
super.viewDidLoad()
fetchShops()
}func fetchShops() {
if let url = URL(string: "https://api.airtable.com/v0/appWKQiePYTFRgmRM/ShopList") {
var request = URLRequest(url: url)
request.setValue("Bearer keySfV0P5ClR74OTq", forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: request) { data, response , error in
if let data {
do {
let decoder = JSONDecoder()
let shopList = try decoder.decode(ShopList.self, from: data)
for i in shopList.records.indices {
let shop = ShopField(shopName: shopList.records[i].fields.shopName, county: shopList.records[i].fields.county, phone: shopList.records[i].fields.phone, address: shopList.records[i].fields.address)
self.shops.append(shop)
}
self.shopDics = Dictionary(grouping: self.shops, by: { shops in
shops.county
})
self.counties = self.shopDics.keys.sorted(by: <)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print(error)
}
} else {
print("Error")
}
}.resume()
}
}
@IBAction func phoneCall(_ sender: UIButton) {
let point = sender.convert(CGPoint.zero, to: tableView)
if let indexPath = tableView.indexPathForRow(at: point) {
let section = indexPath.section
let item = sender.tag
let county = counties[section]
let shops = shopDics[county] ?? []
let shop = shops[item]
let phoneNumber = shop.phone
print(section, item, phoneNumber)
if let url = URL(string: "tel://\(phoneNumber)") {
UIApplication.shared.open(url)
}
}
}
@IBAction func mapButton(_ sender: UIButton) {
let point = sender.convert(CGPoint.zero, to: tableView)
if let indexPath = tableView.indexPathForRow(at: point) {
let section = indexPath.section
let item = sender.tag
let county = counties[section]
let shops = shopDics[county] ?? []
let shop = shops[item]
let address = shop.address
print(address)
let geoCoder = CLGeocoder()
geoCoder.geocodeAddressString(address) { placemarks, error in
guard error == nil else {
let alert = UIAlertController(title: "轉換問題", message: "地址錯誤!", preferredStyle: .alert)
let okAction = UIAlertAction(title: "確定", style: .destructive)
alert.addAction(okAction)
self.present(alert, animated: true)
return
}
guard placemarks != nil else {
let alert = UIAlertController(title: "轉換問題", message: "地址轉換經緯度失敗!", preferredStyle: .alert)
let okAction = UIAlertAction(title: "確定", style: .destructive)
alert.addAction(okAction)
self.present(alert, animated: true)
return
}
if !placemarks!.isEmpty {
let toPlacemark = placemarks!.first!
let toPin = MKPlacemark(placemark: toPlacemark)
print("經度:\(toPin.coordinate.longitude),緯度:\(toPin.coordinate.latitude)")
let destMapItem = MKMapItem(placemark: toPin)
let option = [MKLaunchOptionsDirectionsModeKey:MKLaunchOptionsDirectionsModeDriving]
destMapItem.openInMaps(launchOptions: option)
} else {
let alert = UIAlertController(title: "轉換問題", message: "沒有取得導航用的經緯度!", preferredStyle: .alert)
let okAction = UIAlertAction(title: "確定", style: .destructive)
alert.addAction(okAction)
self.present(alert, animated: true)
}
}
}
}
// MARK: - Table view data sourceoverride func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return counties.count
}override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 1
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return counties[section]
}override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ShopTableViewCell", for: indexPath) as! ShopTableViewCell
let county = counties[indexPath.section]
cell.shops = shopDics[county] ?? []
cell.collectionView.tag = indexPath.section
cell.collectionView.reloadData()
return cell
}
}