辦公室最愛的活動『訂飲料』

作業:#20 訂飲料 App,上傳資料到後台part 1

我們出力寫App,Peter出錢請喝飲料!

很認真的想要開始寫一個比較完整的專案,就從這個訂飲料作業開始吧!(完成了App還有飲料喝XD),這個App其實蠻實用的,辦公室最愛喝飲料了啊,我想我辦公室一年胖一公斤的小資女生活,一定跟喝飲料脫不了關係(笑)。

ㄧ、萬事起頭難,從簡單的先開始

一開始還沒有方向要怎麼做,於是先做了一個飲料Menu的頁面:

使用功能:

1.scroll view delegate — 分頁和圖片

2.page control 的小圓點顯示目前在第幾頁

新增一個class,命名為IntroScrollViewController,寫入程式碼:

import UIKitclass IntroScrollViewController: UIViewController , UIScrollViewDelegate{@IBOutlet var drinksImageView: [UIImageView]!@IBOutlet var drinksScrollView: UIScrollView!@IBOutlet var drinksPageControl: UIPageControl!@IBOutlet var drinksView: UIView!override func viewDidLoad() {super.viewDidLoad()drinksScrollView.delegate = selfdrinksPageControl.numberOfPages = 13drinksPageControl.currentPage = 0}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {let currentPage = Int(drinksScrollView.contentOffset.x / drinksScrollView.frame.size.width)drinksPageControl.currentPage = currentPage}}

詳細的Scroll view的做法可參考這篇:

另外Page control的做法可參考這篇:

參考文章:

二、設計Storyboard畫面與UI元件的設定

HappyDrinksApp Storyboard
訂購飲料的頁面

是說我自以為是的拉了auto layout,導致cell裡的文字及picker被壓縮到,但因為想要先研究程式碼,所以這部分之後再處理囉。

飲料清單的說明:

1.先將飲料的品項及價格存成txt檔。

可不可menu的txt檔

2.再將txt檔拖曳至專案內,記得要選擇Copy與Target,Copy是會複製一份檔案到專案,不會因為拖曳的檔案刪除就無法使用,而Target是在生成App時,會將檔案加進去。

將txt檔拖曳至專案內

新增一個Swift file去自訂所有array的型別:

//  TeaData.swift//  HappyOrderDrinks//  Created by Julia Wang on 2019/8/20.//  Copyright © 2019 Julia Wang. All rights reserved.//import Foundationstruct TeaChoicesData {var name: Stringvar drinks: Stringvar price: Stringvar size: Stringvar sugar: SugarLevelvar ice: IceLevelvar message: Stringvar tapioca:Stringinit() {name = ""drinks = ""price = ""size = ""sugar = .regularice = .regularmessage = ""tapioca = ""}}struct DrinksList {var name: Stringvar price: Int}enum SugarLevel:String{case regular = "正常", lessSuger = "少糖", halfSuger = "半糖", quarterSuger = "微糖", sugerFree = "無糖"}enum IceLevel:String{case regular = "正常", moreIce = "少冰", easyIce = "微冰", iceFree = "去冰", completelyiceFree = "完全去冰", hot = "熱飲"}

新增一個class,命名為OrderDrinksTableViewController,先宣告變數:

var teaorder = TeaChoicesData ()var drinksData : [DrinksList] = []var teaIndex = 0

該拉的IBOulet及IBAction也請記得拉一拉喲~然後要記得有沒有拉好,不然會出現這個我向小王子求救的問題:

在畫面載入的時候讀取飲料清單內的品項及價格:

viewDidLoad載入飲料清單及價格

Function:getTeaMenu()及updatePriceUI()

1.Function:getTeaMenu()

呼叫Bundle.main.url()來開啟txt檔案,在IOS中URL所代表的是位置,可以是設備上的檔案位置,也可以是網路上的檔案位置,當路徑的檔案不存在時會回傳nil,forResource是要讀取的檔案名稱,withExtension就是副檔名。

呼叫init(contentsOf url URL) throws來讀取檔案,因為讀檔不一定會成功,所以會加上try來處理Error Handling,如成功讀取則回傳檔案內的資料。

回傳的資料是靠\n分行的,所以可以利用components來將\n移除,移除後的資料會回傳到一個陣列(Array),再利用for in 將飲料名稱單獨取出儲存。

getTeaMenu()

2.Function:updatePriceUI()

根據teaIndex的數值去顯示價格,teaIndex一開始宣告為0,所以載入金額就從第一個價格開始顯示。

updatePriceUI()

Picker view的說明:

1.選擇Picker View後拖曳到Controller與dataSource及delegate做連結,這樣才能因為遵從UIPickerViewDelegate及UIPickerViewDataSource的Protocol而顯示資料。

連結dataSource及delegate

2.如第一步所說的顯示資料需要遵從UIPickerViewDelegate與UIPickerViewDataSource的Protocol,所以要在Controller的Class前增加繼承UIPickerViewDelegate與UIPickerViewDataSource。

UIPickerViewDelegate與UIPickerViewDataSource

3.寫Picker View的Function。

Picker View的Function

4.顯示結果:選擇飲料同時出現對應的飲料價格。

訂購飲料的頁面

是否加購白玉珍珠的Switch:

如果加購珍珠的Switch開啟,priceLabel顯示的價格再加10(加珍珠加十元喲),反則依據teaIndex選到的數值去顯示對應的價格。

白玉珍珠的Switch

三、重頭戲之上傳訂單的JSON Data到sheetDB

利用SheetDB提供的API將訂單資料傳到Google Sheets(試算表)內

這是我最害怕的部分,研究了三天三夜,練習實作一天終於完成了!

按下確認訂購傳送訂單至sheetDB成功(放煙火)!

前置作業

1.先新增一份google試算表,並自訂欄位名稱。

按下右下角的『+』
然後輸入自訂欄位名稱

2.複製試算表網址,貼到sheetDB去產生API。

複製上方網址
貼到SheetDB去Create API

3.Create API完成。

取得訂單資料

程式碼:

//訂單內容func getOrder() {guard let name = nameTextField.text, name.count > 0 else{   // 檢查姓名是否輸入return showAlertMessage(title: "忘記輸入你的名字囉!",message: "沒寫名字怎麼知道是誰點的啦XD")    // 顯示必須輸入的提示訊息}//姓名資料teaorder.name = nameprint("訂購人:\(name)")//飲料資料teaorder.drinks = drinksData[teaIndex].nameprint("飲料品項:\(teaorder.drinks)")//容量資料if sizeSegmentedControl.selectedSegmentIndex == 0 {teaorder.size = "大杯"}else {teaorder.size = "中杯"}print("容量:\(teaorder.size)")//甜度資料switch sugarSegmentedControl.selectedSegmentIndex {case 0:teaorder.sugar = .regularcase 1:teaorder.sugar = .lessSugercase 2:teaorder.sugar = .halfSugercase 3:teaorder.sugar = .quarterSugercase 4:teaorder.sugar = .sugerFreedefault:break}print("甜度:\(teaorder.sugar.rawValue)")//冰度資料switch iceSegmentedControl.selectedSegmentIndex {case 0:teaorder.ice = .regularcase 1:teaorder.ice = .moreIcecase 2:teaorder.ice = .easyIcecase 3:teaorder.ice = .iceFreecase 4:teaorder.ice = .completelyiceFreecase 5:teaorder.ice = .hotdefault:break}print("冰度:\(teaorder.ice.rawValue)")//是否加珍珠if tapiocaSwitch.isOn {teaorder.tapioca = "要加珍珠"}else {teaorder.tapioca = "不加珍珠"}print("是否加購:\(teaorder.size)")//價格資料if let price = priceLabel.text {let money = (price as NSString).substring(from: 4) //因為顯示時有加上NT. ,所以移除後上傳teaorder.price = money}print("價格:\(teaorder.price)")//留言欄if let message = messageTextField.text {teaorder.message = messageprint("備註:\(message)")}}

AlertController提示訊息

前面取得訂單按下確認鍵後會先檢查姓名欄位使否為空值,如果是,就會呼叫showAlertMessage的Function:

guard let name = nameTextField.text, name.count > 0 else{   // 檢查姓名是否輸入return showAlertMessage(title: "忘記輸入你的名字囉!",message: "沒寫名字怎麼知道是誰點的啦XD")    // 顯示必須輸入的提示訊息}

showAlertMessage的Function程式碼:

func showAlertMessage(title: String, message: String) {let inputErrorAlert = UIAlertController(title: title, message: message, preferredStyle: .alert) //產生AlertControllerlet okAction = UIAlertAction(title: "確認", style: .default, handler: nil) // 產生確認按鍵inputErrorAlert.addAction(okAction) // 將確認按鍵加入AlertControllerself.present(inputErrorAlert, animated: true, completion: nil) // 顯示Alert}
沒輸入姓名的提示訊息

上傳訂單資料—JSON格式的Data(POST)

要注意SheetDB的POST API提供的Post功能需要什麼設定與JSON的結構,規範如下:

程式碼:

資料儲存位置url就是前面完成的Create API

『 https://sheetdb.io/api/v1/你的API ID 』

//傳送訂單資料至sheetDBfunc sendDrinksOrderToServer() {//POST的API需要知道上傳的資料是什麼格式,所以依照API Documentation的規定設定let url = URL(string: "https://sheetdb.io/api/v1/co2xognew7ev0")var urlRequest = URLRequest(url: url!)// 上傳資料所以設定為POSTurlRequest.httpMethod = "POST"urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")//post所提供的API,Value為物件的陣列(Array),所以利用Dictionary實作let confirmOrder: [String : String] = ["name": teaorder.name, "drinks": teaorder.drinks, "size": teaorder.size, "sugar": teaorder.sugar.rawValue, "ice": teaorder.ice.rawValue, "tapioca": teaorder.tapioca, "price": teaorder.price, "message": teaorder.message]//Post API 需要在物件(Object)內設定key值為data, value為一個物件的陣列(Array)let postData: [String: Any] = ["data" : confirmOrder]do {let data = try JSONSerialization.data(withJSONObject: postData, options: []) // 將Data轉為JSON格式let task = URLSession.shared.uploadTask(with: urlRequest, from: data) { (retData, res, err) in // 背景上傳資料NotificationCenter.default.post(name: Notification.Name("waitMessage"), object: nil, userInfo: ["message": true])}task.resume()}catch{}}

確認訂單的Button

以上Function都寫好之後就是拉確認訂單按鈕的IBAction,在按下按鈕後呼叫取得訂單及上傳訂單的Function。

@IBAction func confirmButton(_ sender: Any) {getOrder()sendDrinksOrderToServer()}

訂飲料App目前進度就先到這邊啦,還有很多地方需要改善,但希望先把完整的功能做出來再去細修,以上有想進一步了解的步驟歡迎一起討論,我已經盡量把我理解的意思寫出來了,但還是有很多可能會讓人看不懂的地方,畢竟我參考學長姊的文章很多地方也看不懂,看了三、四遍才慢慢理解(還是其實是我自己的問題XD?太笨??)總之還是希望大家可以互相交流討論,一起用心體會(笑)!

如果還有問題請找iOS界小王子-彼得潘蜘蛛人解惑!(名號真的好多XD)。

第二集看這裡

參考文章:

如果有值得大家參考的地方再麻煩大家幫我拍拍手喲,謝謝大家耐心閱讀🙇‍♀️

--

--

Julia Wang
彼得潘的 Swift iOS / Flutter App 開發教室

Learning Programming , Hiking , Travels , Tour , Exploring nature 『你必須要很努力,才能看起來毫不費力』