#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
}
本地推播通知
本地推播通知使用的觸發條件,主要有三種:
- UNTimeIntervalNotificationTrigger 間隔多久,發送一次通知。
- UNCalendarNotificationTrigger 指定時間點,發送一次通知。
- 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 = weekdaylet 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])
}