學習目標

  • 通知的形式:
橫幅通知:
- 當有新的通知時,會以橫幅的形式出現在螢幕頂端。
- 這個橫幅包括 App 的名稱、小圖標,以及一個可選的訊息(包含標題和內容)。

音效和Badge:
- 通知可能伴隨著聲音,並且會在App圖標上顯示或更新 Badge。
- Badge 通常是一個小紅點,上面有數字,顯示新的或未讀的項目數量。
  • Local Notifications (本地通知)是一個即使用戶沒在用 App 時,也能跟他們互動、提醒重要事項的好方法。
  • 利用 UserNotifications 框架,可以提醒用戶注意特定事件,並讓他們對這些事件做出回應。
  • 這類通知可以是自己設定的本地通知,也可以是從伺服器推送的
1. 使用本地通知:
- 用來告知用戶新的或更新的資訊,或是提醒他們某些事項。

2. 設定和安排本地通知:
- 設定時間和內容,以安排通知在特定時刻發送。

3. 創建可操作的通知:
- 設計帶有自訂行動的通知,讓用戶可以直接在通知上進行互動。

4. 處理接收到的通知:
- 當 App 在前台或背景運行時,如何處理接收到的通知。

基礎概念

  • Actionable Notification(可操作通知)
1. 方便快速: 
- 除了顯示資訊外,還會有按鍵或輸入框讓你可以直接在通知上回應,

2. 增加互動:
- 讓用戶與 App 間的互動不僅限於打開 App。

3. 多樣用途:
- 從快速回訊息到確認行程,都能透過這種通知輕鬆搞定。
  • Badge
1. 清楚提醒: 
- 讓使用者一目了然地知道有新的通知或未處理的事項。

2. 直觀顯示:
- 紅色圓形標章很容易吸引注意,並直接展示重要的數量資訊。

3. 提高互動:
- 鼓勵使用者打開App,查看新的內容或回應。
  • Banner(橫幅通知)
1. 立即可見:
- 由於它從螢幕頂部滑下,用戶可以立即看到通知內容。

2. 不干擾使用:
- 橫幅通知不會完全阻擋螢幕,允許用戶繼續目前的操作。

3. 訊息展示:
- 通常顯示簡短的通知文本,有時還包括應用標誌或其他相關圖標。
  • Local Notification
「Local Notification」是指只在用戶的裝置上設置和觸發的通知,不涉及從伺服器遠程發送。
對比之下,「Remote Notification」是從伺服器發送給裝置的通知。

1. 裝置自主:
- 不需要網路連接,裝置自己根據設定時間觸發通知。

2. 用途廣泛:
- 可用於提醒、日程安排、鬧鐘等多種場景。

3. 用戶體驗:
- 提高用戶參與度,及時提醒用戶重要訊息。
  • Remote Notification
1. 來源不同:
- Remote Notification 來自伺服器,本地通知則在裝置本身產生。

2. 需要網路連接:
- 由於Remote Notification來自伺服器,因此接收這種通知需要裝置連接到網路。

3. 更動態互動:
- Remote Notification適合用於實時更新或提供即時反饋給用戶,如新訊息通知或App更新提醒。
  • Notification Category
「Notification Category」是指定義 App 所支援的通知類型和任何相關的操作。

- EX: 如果 APp 是一個待辦事項管理器,可能會有「提醒類別」的通知,包括如「標記為完成」或
「延後提醒」等動作。

1. 自訂類型:
- 可以根據App的功能需求,自行設定不同的通知類型。

2. 附加操作:
- 每種通知類型都可以包含用戶可以互動的操作,例如點擊按鈕或回覆訊息。

3. 增強互動:
- 這與「可操作通知」相關,後者允許用戶直接在通知上執行特定操作。

1.Local and Remote Notifications

  • 本地通知:
1. 在用戶的手機上設定,依時間、日期或地點觸發。
2. 適合提醒用戶任務或活動,如待辦事項或日曆提醒。
  • 遠程通知:
1. 在伺服器上製作,用來將資訊傳送到用戶手機。
2. 透過 Apple 的推送通知服務(APNs)發送,用於推送新消息或更新。
  • 主要差異:
1. 來源不同:
- 本地通知是手機上直接製作,遠程通知則由伺服器發出。

2. 用戶感受相似:
- 無論是本地還是遠程,通知的外觀和感覺對用戶來說都差不多。

3. 開發方式一致:
- 無論本地還是遠程,都是用 UserNotifications 框架來管理。

2.Best Practices

3.Requesting Permission

  • 基本步驟
1. 獲得授權:
- iOS 要求App必須得到用戶授權才能發送通知。

2. 初次請求:
- 最初時,App可透過系統界面請求權限,與其他權限請求方式相同。

3. 後續調整:
- 用戶可在「設定」中隨時調整通知權限。
  • 處理授權結果
通過完成處理程序(closure)來處理授權結果,其中包含授權狀態和可能的錯誤。
  • 進階選項
1. 臨時通知:
- 加入 .provisional 選項,可先行發送非打擾式的通知至通知中心,
等待用戶選擇是否啟用完整通知。

2. 標準請求:
- 如果不使用 .provisional,系統會提示用戶選擇是否接收通知。這種提示只會出現一次。
  • 注意事項
1. 首次請求後:
- 用戶必須在「設定」中手動更新通知權限。

2. 適時說明:
- 在適當的時機(如設定鬧鐘時)向用戶說明為何需要通知權限,有助於增加理解和接受度。

3–1.Scheduling Local Notifications

  • 例如,安排一個包含訊息、播放聲音並在5分鐘後發送給用戶的本地通知的步驟:
1. 設定通知內容:聲音、標記、標題和主體

2. 設定通知觸發:5分鐘後發送,不重複

3. 提交通知請求:創建 UNNotificationRequest,並提交

4.Handling and Responding to Notifications

UNNotificationResponse,可以確定哪個通知被操作,並相應地處理它。例如,日曆App會在用戶回應事件通知時,導航用戶到該事件的詳細畫面。
設置代理
UNNotificationResponse

5.Actionable Notifications

5–2.Actions

  • 創建動作
  • 例子
處理這項任務的地方可以是 AppDelegate 中的 didFinishLaunchingWithOptions 方法
創建通知內容(UNNotificationContent)時,提供了上面使用的相同類別識別符
  • 在 App 中的註冊與處理
需要在代理方法 userNotificationCenter(_:didReceive:withCompletionHandler:) 中處理不同的動作
  • 範例

5–3.Text Input Actions

  • 使用 UNTextInputNotificationAction 類別創建的動作:
當用戶選擇這樣的動作時,會出現一個文本框和一個按鈕,讓他們輸入並提交文字。
  • 在 App 中實現和處理文字輸入通知的步驟

6.Foreground Notification Handling

completionHandler 被調用並傳遞了顯示通知的選項(例如顯示警告和播放聲音)

Practice — Alarm

  • 鬧鐘 App ,允許一次設定一個鬧鐘。
1. 創建鬧鐘:
- 實現用戶設定鬧鐘的功能。

2. 獲取通知授權:
- 向用戶請求發送通知的授權。

3. 安排本地通知:
- 設置並安排鬧鐘相關的本地通知。

1.Get Permission and Set Up the Notification Actions and Category

  • 建立鬧鐘結構體與基本功能:
1. 導入 UserNotifications 框架。
2. 建立一個 Alarm struct,包含 schedule(completion:) 和 unschedule() 方法。
  • 請求通知權限:
1. 建立 authorizeIfNeeded(completion:),用來在第一次設定鬧鐘時請求用戶授權通知。
2. 根據不同的授權狀態(已授權、未決定、被拒絕)來決定是否完成設置。
  • 建立通知類別動作Id
Alarm 中,新增 notificationCategoryIdsnoozeActionID
  • 完成通知的設置:
1. 在 AppDelegate 的 application(_:didFinishLaunchingWithOptions:) 
中設置自定義的貪睡動作和通知類別。

2. 使 AppDelegate 遵循 UNUserNotificationCenterDelegate 協議,並設置通知中心的代理。

2.Create and Schedule Notifications

  • 確認通知授權
1. 在安排通知前,需要確認 App 是否有發送通知的權限。

2. 這是透過呼叫 authorizeIfNeeded(completion:) 來完成。

3. 如果授權未被允許(granted 為 false),則使用 completion(false) 結束這個函數。
- 由於授權可能在後台進行,請在主線程上進行此調用。
  • 建立通知內容
 一旦授權成功,就可以建立通知的內容。這包括設定通知的標題、內文、聲音和分類標識符。
- 如果閉包的布爾參數為 true,則創建通知內容並指定通知的類別標識符
  • 建立通知觸發器
1. 通知需要一個觸發器來確定何時發送。這通常是基於「日期和時間」的設定。

2. 使用 UNCalendarNotificationTrigger 並以「鬧鐘」設定的日期作為觸發時機。
  • 建立通知請求
使用剛建立的「內容」和「觸發器」來建立一個通知請求。
每個請求都需要一個id,用來追蹤並管理通知。
  • 安排通知
1. 將「通知請求」加入到 UNUserNotificationCenter。

2. 如果過程中出現錯誤,則print錯誤訊息並用 completion(false) 結束;
若成功則使用 completion(true),同樣將完成調用放在主線程上。
  • 取消安排的通知
透過 removePendingNotificationRequests(withIdentifiers:) 取消已安排的通知。
這需要使用之前設定的id。
  • 初始化鬧鐘實體
 為 Alarm struc 新增一個初始化方法,允許通過參數設定「日期」和「通知識別符」。
如果識別符未提供,則自動生成一個唯一識別符。

3.Set Up the UI

  • 設置鬧鐘後,Label 會顯示選定的時間和日期,並禁用 Date Picker,將Button的標題從「Set Alarm」改為「Remove Alarm」。
只有三個UI元件:Label、Date Picker 和 Button。
  • 處理鬧鐘通知:
1. 當鬧鐘被設置或取消時,需要發送通知來更新其他物件。

2. 為 Notification.Name 擴展 alarmUpdated 。
  • 追蹤和管理已設定的鬧鐘:
1. 創建 scheduled 屬性來追蹤已設定的鬧鐘。

2. 為 scheduled 屬性實作 getter 和 setter 方法,利用 JSON 編碼和解碼存取鬧鐘資訊。

3. 使用 alarmURL 屬性來讀取和寫入文件系統。
  • 設置和取消鬧鐘:
1. 在 schedule(completion:) 中,如果成功將鬧鐘加入 UNUserNotificationCenter,
則設置 Alarm.scheduled。

2. 在 unschedule() 方法中,當取消鬧鐘時設置 scheduled 為 nil。
  • 更新 UI 方法:
1. 創建 updateUI() 來根據鬧鐘是否被設定來更新 UI。

2. 如果鬧鐘存在,顯示鬧鐘時間,禁用Date Picker,並更改按鈕標題為「移除鬧鐘」。

3. 如果鬧鐘不存在,更改標籤為「設置鬧鐘」,啟用 Date Picker,並將按鈕標題設置為「設置鬧鐘」。
  • 流程:
1. 自定義通知名稱: 
- 透過擴展 Notification.Name,新增自定義的通知名稱 .alarmUpdated,用於表示
鬧鐘設置或取消時的事件。

2. 發送通知:
- 當 Alarm 的 scheduled 被設置(新增或移除鬧鐘)時,會使用 NotificationCenter
發送一個通知。這代表任何對鬧鐘的更改都會觸發這個通知。

3. 註冊通知觀察者:
- 在 ViewController 中,使用 NotificationCente 註冊為 .alarmUpdated 通知的觀察者。
這表示當 .alarmUpdated 通知被發送時,ViewController 的 updateUI 方法會被呼叫。

4. 處理通知:
- 一旦 .alarmUpdated 通知被發送,ViewController 的 updateUI 方法會被觸發,
更新用戶界面以反映鬧鐘的最新狀態。
將 ViewController 加為 Notification.Name.alarmUpdated 通知的觀察者, 使用 updateUI 作為選擇器。
自定義通知名稱
設置鬧鐘,如果時間完成就移除該資料
  • 設置鬧鐘按鈕:
在 setAlarmButtonTapped(_:) 中,檢查 Alarm.scheduled 是否存在。
- 如果存在,執行 alarm.unschedule()。
- 如果不存在,創建新的 Alarm 並用日期選擇器的日期安排它。
  • 處理通知權限:
1, alarm.schedule 方法包含一個閉包,用於處理未授予通知權限的情況。

2. 如果未授予權限,從閉包中呼叫 presentNeedAuthorizationAlert() 方法。

3. presentNeedAuthorizationAlert() 會展示一個警告,提示用戶前往設定更改通知權限。
拒絕權限 > 警告 > 至Setting開啟權限

4.Handle the Notification

  • 當使用者在通知中做出反應(比如按下貪睡按鈕)時,因此需要在AppDelegate中加入一個方法來處理這個動作。
  • 方法定義
userNotificationCenter(_:didReceive:withCompletionHandler:):
這是一個用來處理使用者對通知反應的方法。
  • 檢查動作
判斷使用者的反應是否為"貪睡"。
這是通過檢查 response.actionIdentifier 是否等於 Alarm.snoozeActionID 來做的。
  • 創建新的貪睡鬧鐘
1. 如果使用者選擇了"貪睡",則創建一個新的鬧鐘,設定在當前時間後的9分鐘。

2. 使用Date().addingTimeInterval(9 * 60)來計算新的鬧鐘時間。
  • 安排鬧鐘
1. 調用alarm.schedule(completion:)來安排鬧鐘。

2. 最後,調用completionHandler()來結束這個方法。

4–1.當App在前景運行時的通知處理

  • 即使App正在前景運行,也希望鬧鐘通知能夠正常顯示。
  • 方法定義:
userNotificationCenter(_:willPresent:withCompletionHandler:):
這個方法用來定義當App在前景時如何處理通知。
  • 設置通知顯示選項:
1. 使用completionHandler([.alert, .sound])來確保通知可以以彈窗和聲音的形式呈現。

2. 將Alarm.scheduled設置為nil,因為通知即將顯示,用戶可以安排新的鬧鐘。
未設定 > 設定鬧鐘 > 時間到

Lab — BillManager

  • 為了提醒用戶支付即將到期的款項,需要添加「本地通知」。
1. 兩個視圖控制器:
- 主視圖(Master View):展示賬單列表。
- 詳細視圖(Detail View):用於展示單個賬單的詳情。

2. 模型對象:
- 有一個 Bill 的模型對象。

3. Bill 的擴展(Extension):
- hasReminder(是否設置提醒)、isPaid(是否已支付)和 formattedDueDate(格式化的到期日期)。

1.Request Authorization and Set Up the Notification Actions and Category

  • 新增方法至 Bill 擴展:
1. 新方法來新增、移除提醒,以及如何請求顯示通知的權限。

2. 為這些功能在 Bill 中定義基本的方法框架。
  • 基本方法架構:
1. 移除提醒方法:
- 不用加參數。

2. 排程提醒方法:
- 要改變 Bill,加入一個代表提醒日期的 Date 參數,還有一個回傳更新過的 Bill 的閉包。

3. 檢查通知授權方法:
- 私密設定,用一個回傳布林值的閉包來確認是否有顯示通知的權限。
  • 實作權限檢查:
1. 若還沒問過用戶要不要授權,就在這裡問。

2. 無論結果如何,都要用適當的布林值結束這個方法。

1. 如果尚未請求授權,則在此方法中請求授權。

2. 確保所有可能的程式碼路徑結束時,都呼叫完成閉包(completion closure),
並傳遞適當的布林值,以指示 App 是否有權安排使用者通知。
  • 設定Id:
在 Bill 加個 notificationCategoryID 的靜態變數,設個要用的字串。
  • AppDelegate 設定通知動作:
1. 在 application(_:didFinishLaunchingWithOptions:) 加入兩個通知動作:
- 一個是一小時後再提醒,另一個是標記帳單為已付款。

2. 標記為已付款的動作要設定 .authenticationRequired,這樣只有裝置主人解鎖後才能用。

3. 用這兩個動作建立一個類別,並註冊到用戶通知中心。

4. 設定通知中心的代理。

2.Remove the Reminder

  • Bill擴展中實現刪除提醒的方法。
  • 這個過程確保了提醒能夠被有效且安全地從系統中移除,同時也保持了Bill類別狀態的一致性和準確性
1. 功能目的:
- 主要目的是從使用者的通知中心中移除掉設定的提醒。

2. 檢查與取用:
- 首先會確認提醒編號(notificationID)是否設定好了。如果存在,則繼續執行下一步。


3. 移除提醒:
- 一旦獲得了notificationID,就會用這個編號去找出並刪除所有待處理的通知。
這個動作就等於取消了這個提醒。


4. 重設屬性:
- 在移除提醒之後,會將notificationID(提醒編號)和remindDate(提醒日期)設置為nil。
這代表提醒已被完全清除,並且與該賬單相關的提醒資訊也被重置。

3.Schedule a Reminder

1. 加入追蹤功能:
- 為了區分每個帳單的提醒,需要在 Bill類別 中新增 notificationID 屬性,型態是String?。

2. 實作設定提醒方法:
- 由於在變更方法(mutating method)中不能使用指向自己(self)的逃逸閉包。
所以需要先複製一份自己(var updatedBill = self),再對這個副本進行必要的修改,
並在完成閉包中返回這個副本。

3. 先清除舊提醒:
- 這個方法的第一個動作是用之前寫的移除提醒功能,以刪除該帳單之前設定的任何提醒。
- 這確保使用者不會收到他們以為已經更改的提醒。

4. 檢查是否有發通知的權限:
- 先檢查是否有權限發通知。如果沒有權限,就直接結束這個方法,並回傳剛才的副本實例。

5. 設計通知的內容:
- 標題設為「Bill Reminder」,內容要包含帳單金額、收款人和到期日。
- EX:「需支付$12.00給Alexis Key,2021年4月10日到期」。
- 類別標識符使用之前註冊類別時使用的ID。
  • 建立和加入通知:
1. 使用方法中傳入的日期創建一個通知觸發器。

2. 創建新的通知ID(用UUID().uuidString),並賦值給notificationID。

3. 使用內容、觸發器和識別碼創建一個新的通知請求。

4. 如果已經有發通知的權限,就把這個請求加到用戶通知中心,並更新複製體的通知ID和提醒日期。

5. 最後呼叫完成閉包,並傳回這個更新過的副本實例。

6. 注意執行緒:所有對完成處理程序的調用都應在主執行緒上進行。

4.Call the Set Reminder Function

1. 場景:當使用者點擊「完成」按鈕時,會「呼叫設定提醒」的函式。

2. 操作步驟:
- 在 BillDetailTableViewController中,找到 prepare(for:sender:) 函式。
- 在這個函式內,尋找檢查 remindSwitch.isOn 條件的 if-else 語句。

3. 修改 if-else 語句
- 目的:
- 根據提醒開關的狀態來設置或移除提醒。
- 操作:
- 開關開啟:設置 remindDate,並呼叫「設定提醒函式」。
- 開關關閉:將 remindDate 設為 nil,並呼叫「取消提醒函式」。

3. 處理未授權情況
- 問題:
- 若未授權通知,則無法設定提醒。
- 解決方案:
- 如果 notificationID 是 nil,顯示提醒要求使用者去 iOS 設定授權。
- 建立 presentNeedAuthorizationAlert() 方法顯示警告。

4. 更新資料庫
- 時機:無論是「成功設置提醒」還是「關閉提醒」,都在完成閉包內更新資料庫。
- 實作:在設定提醒的方法的完成閉包中,以及當提醒開關設為關閉的情況下,更新資料庫。

5.Handle the Notification Response

1. 在 AppDelegate 中實現代理方法:
- 實作 userNotificationCenter(_:didReceive:withCompletionHandler:) 方法。
- 從回應裡拿到通知的ID: let notificationID = response.notification.request.identifier。

2. 用這個ID找到對應的賬單:
- 在 Database 設置 getBill(forNotificationID:) 方法,讓它根據通知ID找到相關賬單。

3. 判斷用戶選擇的操作:
- 使用 switch 或 if-else 語句來判斷用戶選擇了哪個操作。

4. 根據用戶的選擇做不同的處理:
- 如果選「稍後提醒」RemindAction,就再設一個一小時後的提醒,在完成閉包中保存更新後的賬單。
- 如果選「標為已付款」MarkAsPaidAction,就把付款日期設為現在,並保存新數據。

5. 完成通知中心的處理:
- 在 userNotificationCenter(_:didReceive:withCompletionHandler:) 結束時呼叫完成處理程序。

6. 即使app在前台也要顯示通知:
- 寫 userNotificationCenter(_:willPresent:withCompletionHandler:) 即使
App 在前台運行時,也顯示通知。
  • 測試:兩個同時間的任務通知排程

--

--

wei Tsao 學習紀錄
彼得潘的 Swift iOS / Flutter App 開發教室

Hi ! 我是wei , 先前未接觸過程式開發設計,想藉此來記錄自己的學習歷程,以利培養自己的程式邏輯 :)