『約翰紅茶公司訂飲料app』2.0
POST,儲存網路資料
上次做完第一個訂飲料app版本後,就調整了一些功能,也花時間了解一些似懂非懂的地方。
UI介面架構圖
習慣先拉好介面,想好要做的功能,比較不會亂掉。
- tabbar[0]=menu的部分,menu上方可以透過segment切換三種基底的茶。
這邊是透過把菜單分為三個api下載下來的,上篇有寫到。
- tabar[1]=訂飲料的部分。
- tabbar[2]=顯示訂購資料跟修改訂購資料。
新增一個Swift file去自訂所有array的型別:
//菜單型別struct Drink : Codable {let brand : Stringlet name : Stringlet price : Stringlet mPrice : String?let city : Stringlet detail : Stringlet recommendIce : Stringlet recommendSugar : String}//訂購的飲料跟價錢struct Order: Codable {var orderDrink: Stringvar orderPrice : Int}//顯示cell頁面的資訊struct OrderData:Codable {//定義有data參數的型別var data : List}//上傳內容struct List:Codable {var date:Stringvar name:Stringvar drink:Stringvar size:Stringvar price:Intvar sugar:Stringvar ice:String}struct ForDownloadData:Codable {//刪除時用的var fordata : Download}//因上傳到表單後型別全部變成String所以再設一個下載網路資料跟修改的型別,才不會解碼失敗struct Download:Codable {var date:Stringvar name:Stringvar drink:Stringvar size:Stringvar price:Stringvar sugar:Stringvar ice:String}//刪除及修改sheetDB資料用的struct EditOrder:Encodable {var editdata:List}struct TeaChoicesData {var name: Stringvar drink: Stringvar price: Stringvar size: Stringvar sugar: SugarLevelvar ice: IceLevelvar date: Stringvar bubble:Stringinit() {name = ""drink = ""price = ""size = ""sugar = .regularice = .regulardate = ""bubble = ""}}enum SugarLevel:String{case regular = "正常", lessSuger = "少糖", halfSuger = "半糖", quarterSuger = "微糖", sugerFree = "無糖"}enum IceLevel:String{case regular = "正常", moreIce = "少冰", easyIce = "微冰", iceFree = "去冰", completelyiceFree = "完全去冰", hot = "熱飲"}
設定編輯訂單頁面
把outle拉好以利後續動作。
宣告變數
@IBOutlet weak var priceLabel: UILabel!@IBOutlet weak var bubbleSwitch: UISwitch!@IBOutlet weak var size: UISegmentedControl!@IBOutlet weak var ice: UISegmentedControl!@IBOutlet weak var sugar: UISegmentedControl!@IBOutlet weak var drinkButton: UIButton!@IBOutlet weak var nameTextField: UITextField!var orders:[Order]=[]var drinks:[Drink]=[]var orderList:List!var download:Download?var teaorder = TeaChoicesData()var selectedDrink:Order!//此變數設定選到的飲料跟價格
此菜單是新的api,有所有飲料的menu
func dowmLoadMenu(){//下載菜單的資訊if let url = URL(string:”https://sheetdb.io/api/v1/n4oty7ptgbfj7") {let task = URLSession.shared.dataTask(with: url) { [self] (data, response, error) inlet decoder = JSONDecoder()if let data = data {do{let drinkData = try decoder.decode([Drink].self, from: data)for i in 0…drinkData.count — 1 {//陣列從0開始故減1let priceInt:Int = Int(drinkData[i].mPrice!)!//在這邊將價格轉型以利後面價錢計算let brand = drinkData[i].brandlet drink = drinkData[i].nameif brand == “約翰紅茶公司” {let drinkName = “\(drink)”let oneOrder = Order(orderDrink: “\(drinkName)”,orderPrice: priceInt)self.orders.append(oneOrder)}}//下載完菜單後,把預設的第一個order指定給selectedDrink,就可以拿到名稱跟價格selectedDrink = orders[0]"//updateUI(with: selectedDrink)"發現這是一個錯誤,不能寫在這,這個function,顯示到下一頁時飲料名稱跟價格會跑掉!!想了好久!!!}catch{print(error)}}//print(self.orders.count)}task.resume()}
要將菜單資料function寫在viewDidLoad中,才會在一開始就抓到網路資料顯示!
override func viewDidLoad(){super.viewDidLoad()dowmLoadMenu() }
選到對應的飲料跟價格
在上面下載菜單資訊的function中下載完菜單後,把預設的第一個order指定給selectedDrink,就可以拿到名稱跟價格
selectedDrink = orders[0]updateUI(with: selectedDrink)
更新飲料選到第幾杯跟價錢
//更新飲料選到第幾杯跟價錢func updateUI(with selectedDrink:Order) {DispatchQueue.main.async { [self] indrinkButton.setTitle(selectedDrink.orderDrink, for: .normal)priceLabel.text = “\(selectedDrink.orderPrice)”}}
控制飲料選項(下載網路menu資料)
@IBAction func drinkButton(_ sender: UIButton) {//控制“僅中杯”let name = [“錫金紅茶”,”夢幻紅茶”,”珍珠那提”,”抹茶那提”,”煮濃那提”,”生乳紅茶”,”糖檸紅茶”,”玉釀紅茶”,”冰淇淋紅茶”]//控制”僅去冰”限制let iceFree = [“錫金紅茶”,”夢幻紅茶”]//控制“僅少冰”let easyIce = [“煮濃那提”,”生乳紅茶”,”生乳抹茶”]//控制“僅微糖”let quarterSugar = “玉釀紅茶”//控制”僅無糖”限制let sugarFree = [“錫金紅茶”,”夢幻紅茶”]updateSizeSegmentedControl()let controller = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)for drink in orders {let action = UIAlertAction(title: drink.orderDrink, style: .default) { [self] (_) insender.setTitleColor(UIColor.black, for: .normal)selectedDrink = drinksender.setTitle(selectedDrink.orderDrink, for: .normal)self.priceLabel.text = “\(selectedDrink.orderPrice)”//點選飲料priceLabel顯示對印的價格if name.contains(drink.orderDrink) {self.size.setEnabled(false, forSegmentAt: 1)self.priceLabel.text = “\(selectedDrink.orderPrice)”}else{self.size.setEnabled(true, forSegmentAt: 1)}//若選到這些飲料只有中杯,大杯的segment選項會失效if easyIce.contains(drink.orderDrink) {self.ice.setEnabled(false, forSegmentAt: 0)self.ice.setEnabled(false, forSegmentAt: 2)self.ice.setEnabled(false, forSegmentAt: 3)self.ice.setEnabled(false, forSegmentAt: 4)self.priceLabel.text = “\(selectedDrink.orderPrice)”}else{self.ice.setEnabled(true, forSegmentAt: 0)self.ice.setEnabled(true, forSegmentAt: 2)self.ice.setEnabled(true, forSegmentAt: 3)self.ice.setEnabled(true, forSegmentAt: 4)}//若選到這些飲料只有少冰,其他的segment選項會失效if iceFree.contains(drink.orderDrink) {self.ice.setEnabled(false, forSegmentAt: 0)self.ice.setEnabled(false, forSegmentAt: 2)self.ice.setEnabled(false, forSegmentAt: 1)self.ice.setEnabled(false, forSegmentAt: 4)self.priceLabel.text = “\(selectedDrink.orderPrice)”}else{self.ice.setEnabled(true, forSegmentAt: 0)self.ice.setEnabled(true, forSegmentAt: 2)self.ice.setEnabled(true, forSegmentAt: 1)self.ice.setEnabled(true, forSegmentAt: 4)}//若選到這些飲料只有去冰,其他的segment選項會失效if quarterSugar.contains(drink.orderDrink) {self.sugar.setEnabled(false, forSegmentAt: 0)self.sugar.setEnabled(false, forSegmentAt: 2)self.sugar.setEnabled(false, forSegmentAt: 1)self.sugar.setEnabled(false, forSegmentAt: 4)self.priceLabel.text = “\(selectedDrink.orderPrice)”}else{self.sugar.setEnabled(true, forSegmentAt: 0)self.sugar.setEnabled(true, forSegmentAt: 2)self.sugar.setEnabled(true, forSegmentAt: 1)self.sugar.setEnabled(true, forSegmentAt: 4)}//若選到這些飲料只有微糖,其他的segment選項會失效if sugarFree.contains(drink.orderDrink) {self.sugar.setEnabled(false, forSegmentAt: 0)self.sugar.setEnabled(false, forSegmentAt: 2)self.sugar.setEnabled(false, forSegmentAt: 3)self.sugar.setEnabled(false, forSegmentAt: 1)self.priceLabel.text = “\(selectedDrink.orderPrice)”}else{self.sugar.setEnabled(true, forSegmentAt: 0)self.sugar.setEnabled(true, forSegmentAt: 2)self.sugar.setEnabled(true, forSegmentAt: 3)self.sugar.setEnabled(true, forSegmentAt: 1) }//若選到這些飲料只有無糖,其他的segment選項會失效}controller.addAction(action)}let cancelAction = UIAlertAction(title: “取消”, style: .cancel, handler: nil)controller.addAction(cancelAction)present(controller, animated: true, completion: nil)}
用setEnabled(false, forSegmentAt: 0)去讓選項隱藏跟顯示,segmentControl可能有更好的寫法~之後可以再改進。
在選擇飲料的button有個 updateSizeSegmentedControl()
為了讓使用者換飲料時更新為中杯
//如果換飲料就更新容量為中杯func updateSizeSegmentedControl(){size.selectedSegmentIndex = 0}
控制飲料容量價格,如果選大杯會是售價加五元。加珍珠為價格加五元,也會顯示加珍珠的字樣在名字欄位(但這部分可能還會再修改)。
@IBAction func sizeSegmentControl(_ sender: UISegmentedControl) {if size.selectedSegmentIndex == 1 {selectedDrink.orderPrice += 5}else if size.selectedSegmentIndex == 0{selectedDrink.orderPrice -= 5}DispatchQueue.main.async { [self] inupdateUI(with: selectedDrink)}}@IBAction func bubbleSwitch(_ sender: UISwitch) {if bubbleSwitch.isOn == true{selectedDrink.orderPrice += 5nameTextField.text! += “(加珍珠)”}else if bubbleSwitch.isOn == false{selectedDrink.orderPrice -= 5nameTextField.text = “”}DispatchQueue.main.async { [self] inupdateUI(with: selectedDrink)}}@IBAction func bubbleSwitch(_ sender: UISwitch) {if bubbleSwitch.isOn == true{selectedDrink.orderPrice += 5nameTextField.text! += "(加珍珠)"}else if bubbleSwitch.isOn == false{selectedDrink.orderPrice -= 5nameTextField.text = ""}DispatchQueue.main.async { [self] inupdateUI(with: selectedDrink)}}
提示訊息的function(會寫在送出訂單的button中)
按下確認鍵後會先檢查姓名欄位使否為空值,如果是,就會呼叫showAlert的Function:
func showAlert(){if nameTextField.text?.isEmpty == true {let controller = UIAlertController(title: “請填入名字”, message: nil, preferredStyle: .alert)controller.addAction(UIAlertAction(title: “ok”, style: .default, handler: nil))present(controller, animated: true, completion: nil)return}else{if drinkButton.titleColor(for: .normal) == UIColor.black{
//利用選項的顏色判斷有沒有選擇飲料tabBarController?.selectedIndex = 2//點選按鈕若成功會跳轉到tabbar第三頁}else{let controller = UIAlertController(title: “請選擇飲料”, message: nil, preferredStyle: .alert)controller.addAction(UIAlertAction(title: “ok”, style: .default, handler: nil))present(controller, animated: true, completion: nil)return}}}
資料儲存及上傳
透過網頁了解寫起來會順很多,比較知道寫的意義在哪邊。
不懂的時候怎麼寫都怪XD
sheetDB 預設值是 Get!
編輯頁面的最後是按送出訂單button將資料送出(上傳至網路表單)
這邊寫了兩個function,一個是post function讓資料可以POST,另一個是儲存新資料最後利用post 這個function上傳。
//傳送訂單資料至sheetDBfunc post(orderData:List){//上傳資料對應的datalet url = URL(string: “https://sheetdb.io/api/v1/u2r459ric9akb")//設定上傳資料的網址var urlRequest = URLRequest(url: url!)urlRequest.httpMethod = “POST”//上傳所以使用POSTurlRequest.setValue(“application/json”, forHTTPHeaderField: “Content-Type”)
//設定HTTPHeader的content-type為jsonlet order = OrderData(data: orderData)//要編碼的資料let jsonEncoder = JSONEncoder()//使用jsonEncoder將資料編碼上傳if let encoderData = try? jsonEncoder.encode(order){let task = URLSession.shared.uploadTask(with: urlRequest, from: encoderData) { (retData, res, err) in//(retData, res, err)是會回傳字典告訴結果let decoder = JSONDecoder()//解碼字典if let retData = retData, let dic = try? decoder.decode([String:Int].self, from: retData), dic[“created”] == 1 {//[String:Int]=回傳字典的型別 [“created”] == 1,如果有新增成功,”created”的值會是新增的筆數。print(“ok”)}else{print(“error”)}}task.resume()}}
把不懂的地方理解會更好看懂!
func post(orderData:List)
function內的參數,上傳的資料是List型別(型別名稱要和網路上的名稱一模一樣)
urlRequest.setValue(“application/json”, forHTTPHeaderField: “Content-Type”)
//設定HTTPHeader的content-type為json
設置Content-Type header為application/json,告知伺服器我要送哪種形式的body過去。Content-Type 表頭欄位,是指相關 表示 的 媒體類型 (Media Type),用於定義資料格式 (data format),以供接收者以相應的方式處理。
儲存新資料後上傳
func keepData(){//將要上傳的資料存在data裡let time = Date()let formatter = DateFormatter()formatter.dateFormat = “yyyy-MM-dd HH:mm:ss”//讓時間轉型let dateData = formatter.string(from: time)let nameData = nameTextField.text!let order = drinkButton.title(for: .normal)!let priceData:Int = selectedDrink.orderPricelet sugarData = sugar.titleForSegment(at: sugar.selectedSegmentIndex) ?? “”let sizeData = size.titleForSegment(at: size.selectedSegmentIndex) ?? “”let icedata = ice.titleForSegment(at: ice.selectedSegmentIndex) ?? “”let newData = List(date: dateData, name: nameData, drink: order, size: sizeData, price: priceData, sugar: sugarData, ice: icedata)post(orderData: newData)}
送出資料
@IBAction func sentButton(_ sender: UIButton) {showAlert()keepData()}
訂飲料頁面總算寫完了,雖然還有些地方需要做修改~
Github: