#16 天氣涼了,來杯飲料吧(?

這次的App主要是練習API串接,將網路上的資料下載,以及資料上傳,串接的API這次選擇的是AirTable,對於不喝手搖飲的我來說,這APP真的是困難重重啊,最後選擇了之前朋友常買的上宇林當作這次APP的主角

本次APP主要有以下這幾個重點功能

  1. 下載Airtable的飲品資料
  2. 上傳訂單資料到API
  3. 可以修改、刪除訂單資料

對於畫面設計有困難的我,最後選擇就是用最陽春的畫面來實現功能,以下是顯示畫面

有兩個Tab Bar,一個是訂單選擇,一個是訂單查詢

訂購畫面非常簡單,使用TableView並且選擇static cell的方式製作,輸入訂購人、電話,系列選擇的部分是用scroll view 搭配stack view,並且把Label放進去之後製作而成,下面再放一個page controller讓畫面看起來更像分頁,下方Lable的部分會即時顯示選擇的飲料,冰塊甜度的部分,這次選則使用SegmentController來實現功能,加料的部分則是使用switch

訂單的畫面則是使用動態的TableView製作,只需從AirTable下載訂單資料就可以了。

訂購功能:

  1. 必須輸入姓名電話才可以訂購、否則會有警示
  2. 熱飲部分僅在11月~2月提供,如果在以外的時間會出現警示

這是airtable資料的部分,總共有四個資料,品項名稱(String)、系列(String)、價格(Int)、是否為熱飲(Bool)

訂購的資料則是 性名(String)、電話(String)、品項(String)、單品價格(Int)、加料價格(Int)、總價(Int)、冰塊(String?)、甜度(String)、加的料([String]?),其中冰塊跟加的料為Optional,因為熱飲不可以選擇冰塊,料也可以選擇不加

以下是操作畫面:

程式碼的部分:

建立Item 的struct,其格式要符合airtable的API,因為只需要下載,因此只需遵循Decodable的Protocol,

建立一個CutomerOrder的struct,也是符合airtable的API,因為需要上傳跟下載訂單list,因此遵循Codable的Protocol,而其中id的部分設定為String?,是因為一開始上傳訂單的時候並不知道id,因此此時id為nil,上傳成功之後airtable為自動幫我們建立id,而在之後的修改以及刪除訂單都需要id

我另外設計一個customerSelected 的 struct,其實是有點多此一舉,畢竟資料內容跟剛剛的struct都一樣,但我自己把他拉出來整理成一個我自己認為比較清楚的資料,這樣也比較不容易亂掉,作法就是把下載的訂單資料儲存在這個struct裡面,或是先使用這個struct做資料儲存,最後再轉成要上傳的資料型別

再來就是一些資料細項,如冰塊、甜度、以及加料的內容

最後是資料處理的struct,裡面放的是五個function,包含下載item、上傳訂單、下載訂單、刪除訂單、修改訂單,其實這五個寫法都很相似,需要注意的是header的部分、httpMethod的部分,以及最重要的是格式的部分,在修改訂單的時候我沒注意要上傳的是整個資料,誤以為跟上傳訂單一樣單筆單筆的上傳即可,因此弄了快一整天debug。

差異如下:

let data = try encoder.encode(customerSelectedItemDetail)
// 上傳訂單,上面encode放的資料是單筆的訂單資料
let updateOrder = CustomerOrder(records: [customerSelectedItemDetail])
request.httpBody = try encoder.encode(updateOrder)
// 修改訂單,上面encode的資料,需要先把單筆資料放進records的Array裡面,再編碼

OrderViewController的部分,如下:

ScrollView與StackView

自己的習慣是最上面放變數名稱,再來拉IBOutlet,以及共用的function建立,View完了之後再拉IBAction,其中顯示品項跟金額的部分會一直出現因此直接做成function,比較困難的是scrollView搭配stackView的部分,一開始我是使用設定Label bound的部分,想說只要Label邊界大小跟stackView 或 scrollView一樣即可,但至始至終Label的大小都跟字的長度一樣,沒辦法改變外框,把label貼到stack裡面的時候無法一個Label一頁,最後還是使用widthAnchor的方式解決,其實這部份的設定一直我都認為很不直覺啊,最後只需要連動pageController跟stackView之間即可,不過畢竟stack不是真的一頁一頁,因此是用x軸的長度*pageController的index來判斷頁數。

PickerView

另外一個很重要的是滑動系列之後,pickerView的商品必須要回到第0個位置,如果沒這麼做,假設原本在選擇的index如果是第10個位置,之後往其他地方滑到的系列商品數超出這位置就會死當。

UpdateUI

再來就是更新主畫面,在這邊我做了一個小小設計,因為我想使用系列去分類,但Airtable似乎沒有這個功能,因此使用了Dictionary很方便的功能grouping,我只要指定要分類的property就會自動幫我們分好,無奈的是我不管怎麼sorted Dictionary都沒辦法排序,因此只要把它存在一個Array裡面去排序。

UISwitch

而加料的部分則是在switch Tag的部分先設定好價格,然後名稱則是用struct裡面已經設定好的Sting Array搭配UISwitch的位置,這邊要注意拉的順序不能搞錯,否則位置會不一樣,最後再存入toppings的陣列裡。

UIbotton

用switch判定是否有名稱跟電話,然後再用current month來判定月份搭配isWinter的布林值判定是否能點養生熱飲,如果都沒問題就可以上傳訂單

PickerView Delegate

PickerView比較麻煩的是使用它需要代理人,而這邊我沒有直接加在Controller後面遵循,而是用extension的方式,讓畫面看起來比較乾淨,值得注意的是因為這次的資料是網路下載,因此避免資料傳遞延遲,一開始如果沒有接收到資料,PickerView會因為Range錯誤死當,因此需要有一個預設值。

這樣基本上訂單的功能已經完整了,接下來就是訂單資料的下載以及修改刪除。

刪除畫面:

訂單List TableViewController:

UpdateUI:

與剛剛下載Items類似,只是改成下載OrderList,這次選擇放在ViewWillApear裡面執行

Cell:

因為訂單是動態的,因此設定一個cell的固定格式,然後在return Cell的部分就使用tableView.dequeueReusableCell的方式去執行,Table View Cell也只需要拉拉IBoOutlet即可

TraillingSwipeAction (Delete):

這是這頁的重頭戲了,滑動主要有兩個功能,一個是刪除,一個是修改訂單,刪除訂單的部分較為容易,只要把Struct已經設定好的刪除功能放進去即可,但要注意的是remove indexPath.row之後,tableView畫面也要跟著刪除並且reload Data,並且在Main Thread執行,否則會一直報錯,而在這邊有一個小小失誤就是我一開始把row數另外設一個變數,導致刪除畫面之後row的數量跟資料Array數量不一樣,一直死當,後來row數直接回傳Array的count就解決了。

修改畫面:

TraillingSwipeAction (Revise):

修改的部分則是會跳出另一個TableViewController進行修改,原本是要使用上傳訂單的Controller,後來想一想決定做一個新的,因為我不想讓電話跟名字還有品項修改,只能修改冰塊甜度以及加的料,這部分只需要命名一個Controller,把資料傳過去後並且Present出來即可

修改訂單TableViewController :

大致上Function都跟原本上傳的Function可以共用,很多都複製貼上而已,主要是判斷switch的部分,我是用一個for迴圈去判斷是否陣列有這個名字,然後用Index的位置去設定開與關,最後按下確定就可以修改。

UpdateData Protocol 與 delegate:

這部分是完全網路照抄,目的是為了實現修改完成後Present的Controller關掉後,訂單List的內容也會自己修改,原本滑掉是不會修改畫面的,必須切換Tab Bar的位置才會修改

  1. 先在修改訂單的Controller 新增一個protocol,裡面放一個function,無需任和內容,命名也隨意
  2. 在修改訂單的Controller 新增一個變數,並且讓他遵循這個protocol,這邊我直接命名delegate
  3. 在修改完成的時候,在按下alert OK鍵後的closure裡呼叫delegate的function
  4. 回到原本訂單tableView Controller,在修改資料裡面,呼叫delegate並且讓他等於self
  5. 在訂單tableViewController extension這個Class並且遵循剛剛設計的protocol
  6. 此時在extesion protocol的地方寫上你要的功能,而我把下載訂單資料的功能再複製貼進去就完成了

雖然目前還不太理解原理以及為何這樣做,但也算是成功達到我要的目的,看起來簡單的app也花了我整整快兩週完成,每個功能常常都要花一天去研究,這邊也感謝許多大大的程式碼讓我參考以及彼得潘一直幫忙解答問題。

GitHub : https://github.com/EJLO0805/DrinkOrder

參考資料:

--

--