我們今天來教大家做新增以及編輯頁面的教學
有部分與 MainView 相同的設計我就不特別講解
今天專注於新的東西!
import 套件的部分和 MainView 一樣需要的套件
import UIKit
import UserNotifications
import RealmSwift
//編輯頁面的刪除按鈕
@IBOutlet weak var btnDelete: UIButton!
//顯示新增和編輯頁面的選項:重複鬧鐘、鬧鐘名稱等的 tableView
@IBOutlet weak var tableView: UITableView!
//讓使用者修改鬧鐘時間的 DatePicker
@IBOutlet weak var dpkData: UIDatePicker!
//確認目前是否在編輯介面,並讀取資料庫
var alarmToEdit: AlarmData?
//儲存在重複頁面中選擇要重複的日子並儲存進陣列中
var repeatDays: [Bool] = Array(repeating: false, count: 7)
var selectedSound: String = "預設"
var alarmName: String = ""
//設定代理,記得要設定 weak var
weak var delegate: AlarmUpdateDelegate?
//用來儲存是否開啟貪睡按鈕的 Bool 值
var isSnoozeOn: Bool = true
func setUI() {
// 用來設定 DatePicker 為中文的上午以及下午 identifier
dpkData.locale = Locale(identifier: "zh_TW")
title = alarmToEdit == nil ? "加入鬧鐘" : "編輯鬧鐘"
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "儲存", style: .plain,
target: self,
action: #selector(doneTapped))
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "取消",
style: .plain,
target: self,
action: #selector(cancelTapped))
//當為編輯模式時就取消預設的 isHidden 並顯示出來
btnDelete.isHidden = alarmToEdit == nil
btnDelete.setTitle("刪除鬧鐘", for: .normal)
btnDelete.setTitleColor(.red, for: .normal)
btnDelete.addTarget(self, action: #selector(deleteTapped), for: .touchUpInside)
}
在前面設定好 setUi 時我們同樣要設定按鈕事件
大部分寫法跟之前教過的一樣就讓各位複習一下啦!
// 儲存按鈕事件
@objc func doneTapped() {
if let alarmToEdit = alarmToEdit {
updateAlarm(alarmToEdit)
} else {
saveNewAlarm()
}
}
// 取消按鈕事件
@objc func cancelTapped() {
self.dismiss(animated: true, completion: nil)
}
// 刪除鬧鐘按鈕事件
@objc func deleteTapped() {
guard let alarmToEdit = alarmToEdit else { return }
let realm = try! Realm()
try! realm.write {
realm.delete(alarmToEdit)
}
delegate?.didDeleteAlarm()
self.dismiss(animated: true, completion: nil)
}
記得最後要將 setUi 包進去 ViewDidLoad 裡然後要設定 tableView 的代理
以及註冊 tableViewCell 喔!
最下面我們要設一個常數去顯示如果是編輯模式的話,要直接顯示目前這個鬧鐘 cell 的資料來做更改!
setUI()
tableView.delegate = self
tableView.dataSource = self
tableView.register(UINib(nibName: "AddAlarmTableViewCell", bundle: nil), forCellReuseIdentifier: "AddAlarmTableViewCell")
if let alarm = alarmToEdit, !alarm.isInvalidated {
populateFields(with: alarm)
}
首先我們要先來做排程通知的部分,我們用 NotificationCenter 去做通知彈出
sound 的部分是進階,我會在後面教學!
// 排程鬧鐘通知
func scheduleNotification(for alarm: AlarmData) {
let center = UNUserNotificationCenter.current()
let content = UNMutableNotificationContent()
content.title = alarm.name
content.body = "\(alarm.alarmTime)到了!"
content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "\(alarm.sound).mp3"))
let dateComponents = Calendar.current.dateComponents([.hour, .minute], from: formatStringToDate(alarm.alarmTime) ?? Date())
if alarm.repeatDays.contains(true) {
for (index, isSelected) in alarm.repeatDays.enumerated() where isSelected {
var triggerDateComponents = dateComponents
triggerDateComponents.weekday = index + 1
let trigger = UNCalendarNotificationTrigger(dateMatching: triggerDateComponents, repeats: true)
let request = UNNotificationRequest(identifier: "\(alarm.creatTime)_\(index)", content: content, trigger: trigger)
center.add(request) { error in
if let error = error {
print("通知排程失敗: \(error)")
} else {
print("通知已排程: \(request.identifier)")
}
}
}
} else {
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
let request = UNNotificationRequest(identifier: alarm.creatTime, content: content, trigger: trigger)
center.add(request)
}
}
接下來我們要設定鬧鐘新增跟編輯後的 儲存 以及 更新 資訊
// 儲存新的鬧鐘
func saveNewAlarm() {
let realm = try! Realm()
let newAlarm = AlarmData(
alarmTime: formatDate(dpkData.date),
creatTime: getSystemTime(),
name: alarmName,
repeatDays: repeatDays,
sound: selectedSound,
isSnoozeOn: isSnoozeOn
)
try! realm.write {
realm.add(newAlarm)
}
print("新增鬧鐘:", newAlarm)
scheduleNotification(for: newAlarm)
delegate?.didAddNewAlarm()
self.dismiss(animated: true, completion: nil)
}
// 更新已存在的鬧鐘
func updateAlarm(_ alarm: AlarmData) {
let realm = try! Realm()
try! realm.write {
alarm.alarmTime = formatDate(dpkData.date)
alarm.name = alarmName
let repeatList = List<Bool>()
repeatDays.forEach { repeatList.append($0) }
alarm.repeatDays = repeatList
alarm.sound = selectedSound
alarm.isSnoozeOn = isSnoozeOn
}
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [alarm.creatTime])
scheduleNotification(for: alarm)
delegate?.didEditAlarm()
self.dismiss(animated: true, completion: nil)
}
// 根據 AlarmData 填充 UI 欄位
func populateFields(with alarm: AlarmData) {
dpkData.date = formatStringToDate(alarm.alarmTime) ?? Date()
repeatDays = Array(alarm.repeatDays)
selectedSound = alarm.sound
alarmName = alarm.name
isSnoozeOn = alarm.isSnoozeOn
}
同樣的我們要設定 tableView 的內容
所以我們先統整我們的需求!
以上共有四個 cell 我們希望分行顯示在 tableView 上
這時候我們就需要用到 swich-case 的語法啦!
首先我們一樣的先設好 extension !
extension AddAlarmViewController: UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate {
...
}
//我們需要四個 cell 分行顯示
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 4 }
// 返回每個 cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "AddAlarmTableViewCell", for: indexPath) as! AddAlarmTableViewCell
cell.selectionStyle = .none
cell.lbValue?.isHidden = true
cell.txfRename?.isHidden = true
cell.swSnooze?.isHidden = true
cell.txfRename?.placeholder = "鬧鐘"
switch indexPath.row {
case 0: // 重複
cell.lbTitle.text = "重複"
cell.lbValue?.isHidden = false
let days = ["週日", "週一", "週二", "週三", "週四", "週五", "週六"]
let selected = repeatDays.enumerated().compactMap { $0.element ? days[$0.offset] : nil }
if repeatDays == [false, true, true, true, true, true, false] {
cell.lbValue.text = "平日"
} else if repeatDays == [true, false, false, false, false, false, true] {
cell.lbValue.text = "週末"
} else if repeatDays.allSatisfy({ $0 }) {
cell.lbValue.text = "每天"
} else {
cell.lbValue.text = selected.isEmpty ? "永不" : selected.joined(separator: "、")
}
cell.accessoryType = .disclosureIndicator
case 1: // 標籤
cell.lbTitle.text = "標籤"
cell.txfRename?.isHidden = false
cell.txfRename.text = alarmName
cell.txfRename.delegate = self
case 2: // 提示聲
cell.lbTitle.text = "提示聲"
cell.lbValue?.isHidden = false
cell.lbValue.text = selectedSound
cell.accessoryType = .disclosureIndicator
case 3: // 稍後提醒
cell.lbTitle.text = "稍後提醒"
cell.swSnooze?.isHidden = false
cell.swSnooze.isOn = isSnoozeOn
cell.swSnooze?.addTarget(self, action: #selector(snoozeSwitchChanged(_:)), for: .valueChanged)
default: break
}
return cell
}
//重複天數以及鈴聲要在 cell 被點選時 push 出 repeatView
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if indexPath.row == 0 {
let repeatVC = RepeatViewController(nibName: "RepeatViewController", bundle: nil)
repeatVC.selectedDays = repeatDays
repeatVC.delegate = self
self.navigationController?.pushViewController(repeatVC, animated: true)
} else if indexPath.row == 2 {
let soundVC = SoundViewController(nibName: "SoundViewController", bundle: nil)
soundVC.currentSelectedSound = selectedSound
soundVC.delegate = self
self.navigationController?.pushViewController(soundVC, animated: true)
}
}
// placeholder 設定
func textFieldDidEndEditing(_ textField: UITextField) {
alarmName = textField.text ?? "鬧鐘"
}
}
最後就是重複天數和鈴聲兩個頁面的代理別忘記設定!
如果沒有要寫鈴聲的可以不用設定沒關係
extension AddAlarmViewController: RepeatViewControllerDelegate {
func didUpdateRepeatDays(_ days: [Bool]) {
self.repeatDays = days
tableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
}
}
extension AddAlarmViewController: SoundViewControllerDelegate {
func didSelectSound(_ soundName: String) {
selectedSound = soundName
tableView.reloadRows(at: [IndexPath(row: 2, section: 0)], with: .automatic)
}
}
protocol AlarmUpdateDelegate: AnyObject {
func didAddNewAlarm()
func didEditAlarm()
func didDeleteAlarm()
}