#28 模仿 iOS Clock App - 2 :Alarm

模仿 iOS Clock App系列:

繼上一篇完成World Clock功能後,這次完成鬧鐘的功能。除了睡眠時間調整和鈴聲的功能,其它都盡量還原呈現。

實現功能

  • 鬧鐘的新增,修改,刪除和儲存。
  • 編輯鬧鐘的欄位,包含鬧鐘時間與名稱、週日期重複設定,鈴聲設定。
  • 利用UserNotifications實現鬧鐘功能(目前有立即觸發兩次的現象的debug)。
  • 利用UIResponder與NotificationCenter實現自動點選textField與觸發keyboard展開且textField向上移動
  • 利用textFieldShouldReturn按鍵盤return結束編輯

練習目地

  • UIResponder
  • NotificationCenter
  • UserNotifications

週日期是否重複的功能

利用weekdaySymbols建立週日期的陣列,格式顯示成Monday…。這樣就不用自己手打星期一到星期日

let weekdaySymbols: [String] = {
let formatter = DateFormatter()
guard let weekdaySymbols = formatter.weekdaySymbols else { return [""] }
return weekdaySymbols
}()

weekdaySymbols建立repeatDays,要同時記錄週日期與是否重複,

var repeatDays: [RepeatDay] = {
var repeatDays = [RepeatDay]()
for weekdaySymbol in weekdaySymbols {
let repeatDay = RepeatDay(weekDay: weekdaySymbol, isRepeat: true)
repeatDays.append(repeatDay)
}
return repeatDays
}()
struct RepeatDay: Codable {
var weekDay: String
var isRepeat: Bool
}

選擇重複的天數將顯示checkmark

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "\(RepeatTableViewCell.self)", for: indexPath) as! RepeatTableViewCell
let row = indexPath.row
cell.weekDayButton.text = "Every \(String(describing: repeatDays[row].weekDay))"
//如果已選擇將顯示checkmark if repeatDays[row].isRepeat {
cell.accessoryType = .checkmark
cell.selectionStyle = .none
}
return cell
}

點選表格將開關該週日期是否重複

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)

if cell?.accessoryType == .checkmark {
cell?.accessoryType = .none
repeatDays[indexPath.row].isRepeat = false

} else {
cell?.accessoryType = .checkmark
repeatDays[indexPath.row].isRepeat = true
}
}

鬧鐘名稱編輯

進入畫面時,textField為firstUIResonder,keyboard將自動展開。

textField.becomeFirstResponder()

keyboard展開後,textField往上移動,避免被keyboard遮到。

利用NotificationCenter通知系統鍵盤展開的行為

@objc func handleKeyboardDidShowNotificaion() {
UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut) {
self.textField.transform = CGAffineTransform(translationX: 0, y: -100)
}.startAnimation()
}
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardDidShowNotificaion), name: UIResponder.keyboardDidShowNotification, object: nil)

利用textFieldShouldReturn按鍵盤返回鍵便能結束編輯

指定textField的delegate

textField.delegate = self

按return鍵後執行unwindTSegue

extension LabelViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
performSegue(withIdentifier: "unwindToEditAlarm", sender: nil)
return true
}

本地推播通知

本地推播通知使用的觸發條件,主要有三種:

  1. UNTimeIntervalNotificationTrigger 間隔多久,發送一次通知。
  2. UNCalendarNotificationTrigger 指定時間點,發送一次通知。
  3. UNLocationNotificationTrigger 進入某個範圍或離開某個範圍,發送一次通知。

本鬧鐘功能使用的是UNCalendarNotificationTrigger

向系統請求授權

在 AppDelegate.swift內,app完成啟動後向系統請求授權。APP初次使用便會收到授權請求。

先import UserNotifications的API

import UserNotifications

接著didFinishLaunchingWithOptions的函式內建立授權申請的程式。

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, error in
if granted {
print("Success")
} else {
print("Failed")
}
}

return true
}

options裡包含需要授權的形式,這裡包含.alert, .sound 還有.badge可以授權。

註冊本地推播實現鬧鐘通知功能

宣告UNMutableNotificationContent()輸入推播的title與內文,當然包含鬧鐘的鈴聲。

func createNotifiction(time: Date, label: String, sound: String, weekdaySet: [Int]) {

let content = UNMutableNotificationContent()
content.title = "K Clock"
content.body = label
content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "\(sound).mp3"))

註冊鬧鐘

利用dataComponets.weekday輸入數值可帶入工作日,如果輸入3則代表Tuesday,以此類推。所以利用for-in可以為單一鬧鐘定義重複的設定。

for weekday in weekdaySet {
var dataComponets = Calendar.current.dateComponents([.hour, .minute], from: time)
dataComponets.weekday = weekday
let trigger = UNCalendarNotificationTrigger(dateMatching: dataComponets, repeats: false)
let request = UNNotificationRequest(identifier: "set\(alarmIndex)", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)

alarmIndex += 1
}
}

允許APP開啟時一樣收到推播

如果做到目前的設定,只有回到home可以收到推播,如果要在app開啟時仍然收到通知則要設定以下程式。

func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.sound, .banner, .list, .badge])
}

成果展示

--

--