【iOS】#18 可不可飲料訂購APP Part3: 刪除訂購、新增群組、Loading

SheetDB DELETE 刪除資料、NotificationCenter userInfo傳值、Loading 畫面 與 所有功能 Demo

--

本篇提及功能有:
SheetDB DELETE 刪除資料
NotificationCenter 回傳資料
實作 Loading 畫面

APP 新增功能

  • 刪除單筆訂單:使用者輸入驗證碼刪除資料
  • 簡易登入:使用者輸入名稱、自訂驗證碼,選擇訂購群組以登入
  • 新增組別:若要發起飲料團訂,可自行新增群組
  • Loading 畫面:當 API 抓資料時,呈現 Loading 畫面

增加了以上功能,以下主要會記錄實作粗體功能時值得注意的部分

APP 實作

SheetDB 刪除資料

當使用者想要刪除訂單時,需要 call API 直接 DELETE,把資料刪掉

根據 SheetDB 的文件可以發現,要給它要刪除的欄位名稱以及數值,它就會把符合條件的資料整行(row)刪除

雖然可以給它 limit=1 參數,來限制只能刪掉一筆資料,但如此一來還是可能誤刪(它會優先刪除位於資料前面的那筆)

所以我設計在登入時使用者需要設定屬於自己的驗證碼,訂購飲料時會自訂在該筆飲料中紀錄驗證碼

藉由多一層簡易防呆,來預防誤刪的情況

url 的部分在 sheetID 後面加上 /orderer/{value}?limit=1

代表符合 orderer 欄位為這個名稱的資料,刪除掉一筆

NotificationCenter 回傳資料

在程式中有些地方有使用到 NotificationCenter.default.addObserver 註冊觀察者來通知程式執行一些 Function

假設 A 註冊觀察者,B 傳送通知回去時,可以在 userInfo 參數上帶資料回去

let editCodeDic: [String : String] = ["editCode" : self.editCode, "orderer": self.orderer]NotificationCenter.default.post(name:  NSNotification.Name("deleteAction"), object: nil, userInfo: editCodeDic)

但由於 userInfo 的型態是 [AnyHashable: Any] ,所以要把資訊包在字典裡面回傳

然後在 A 的地方,除了註冊觀察者外,還要對回傳來的 userInfo 做處理

// 註冊觀察者
NotificationCenter.default.addObserver(self, selector: #selector(deleteAction), name: NSNotification.Name("deleteAction"), object: nil)
// 針對回傳的 userInfo 指定處理的 function
NotificationCenter.default.addObserver(forName: NSNotification.Name("deleteAction"), object: nil, queue: nil, using: catchNotiInfo)
func catchNotiInfo(notification: Notification) {
guard let editCode = notification.userInfo!["editCode"],
let orderer = notification.userInfo!["orderer"] else {
return
}
currentCellEditCode = (editCode as! String)
currentCellOrderer = (orderer as! String)
}

Loading 畫面

因為 Loading 畫面在抓網路資料時,都需要出來擋一下,這樣使用者才不會覺得 APP 當掉

所以直接用一個 UIViewController 來製作 Loading 畫面,當有需要的時候 present 出來,用完再 dismiss 掉

上面設定了一個 label 元件以及 imageView 元件

在 imageView 上我做了一個 GIF 動畫放在上面,實際程式碼的解說可以看 Peter 這一篇~

為了讓 LoadingViewController 可以方便呼叫,所以使用 extension 的寫法,讓每個 ViewController 都有能力呼叫畫面出來

預設的 present 過場是從下方跑上來的卡片,如果要全屏並且直接 fade in 顯示的話要把 modalPresentationStyle、modalTransitionStyle 分別設為 .overCurrentContent, .crossDissolve

controller.modalPresentationStyle = .overCurrentContextcontroller.modalTransitionStyle = .crossDissolve

再來是 dismiss Loading 畫面,我的方式是先去找最上層的 View Controller ,判斷是否為 Loading View Controller 再把他移除掉

找上層的方式是用 presentedViewController,因為 ViewController 是一層一層疊上去。presentedViewController 是找這個 View Controller present 的 View Controller ,一層一層往上找,找到 nil 為止,代表這個 View Controller 沒有 present 東西,是最上層的 View Controller

// 拿到最上層 VC
func topMostViewController() -> UIViewController {
if self.presentedViewController == nil {
return self
}
return self.presentedViewController!.topMostViewController()
}

不過這個方式要注意的是,確認呼叫 Loading View Controller 的要是最上層的 View Controller

我自己遇到的 Bug 是如果 Fetch 資料寫在 viewWillAppear 階段時,此時因為這個 View Controller 還沒出來,所以呼叫 Loading 的會是之前那個 View Controller

這樣就無法追溯到 Loading View Controller,最上層的會判定為要出來的這個 view。所以改寫在 viewDidAppear 時,即可解決這個問題

後記

終於做完囉!這次也是做了頗久,想說如果真的要被使用的話,應該不能太烙才行 XD

不過到了今天上課才發現還有一些寫法可以用,像是表格 edit style, remove, delegate 傳值等,往後可以練習一下!

DEMO

GitHub

因為沒有使用 autoLayout 的緣故,如果要使用 APP 可以設定成 iPhone 11,才確保不會跑版~

--

--