iT邦幫忙

2025 iThome 鐵人賽

DAY 23
0

我們今天先把昨天沒做的資料庫給補上:

import Foundation
import RealmSwift

class AlarmData: Object {
    
    @Persisted var alarmTime: String = "" // 鬧鐘要響的時間
    @Persisted var creatTime: String = "" // 創建鬧鐘的時間,方便進行排序
    @Persisted var name: String = "" // 給自己設定的鬧鐘設定名稱,比如說:睡覺、工作
    @Persisted var repeatDays: List<Bool> = List<Bool>() // 後面設定重複天數的true or false
    @Persisted var sound: String = "" // 選擇鬧鐘響鈴聲的
    @Persisted var isEnabled: Bool = true // 控制鬧鐘的開關
    @Persisted var snoozeEnabled: Bool = true // 稍後提醒
    @objc dynamic var isOn: Bool = true
    // 讓我們可以在其他.swift檔也能使用參數
    convenience init(alarmTime: String, creatTime: String, name: String, repeatDays: [Bool] = Array(repeating: false, count: 7), sound: String, snoozeEnabled: Bool = true) {
         self.init()
         self.alarmTime = alarmTime
         self.creatTime = creatTime
         self.name = name
         self.sound = sound
         self.repeatDays.append(objectsIn: repeatDays)
         self.snoozeEnabled = snoozeEnabled
    }
}

Tips:
@Persisted:Realm 的屬性標註,表示這個欄位會被存入資料庫。
repeatDays:使用 List<Bool> 來儲存 7 天的重複狀態,例如 [true, false, ...] 表示每週哪些天會重複響鈴。
creatTime:用來當作唯一識別,刪除通知時也會用到。

刪除鬧鐘

除了新增與顯示資料外,刪除鬧鐘時我們必須:

  • 取消已排程的通知
  • 刪除 Realm 中的資料
  • 更新 TableView

以下是刪除邏輯:

    // 刪除鬧鐘的功能
    // alarms是用來存放所有鬧鐘的陣列
    var alarms: [AlarmData] = []
    // 刪除指定的鬧鐘
    func deleteAlarm(_ alarm: AlarmData, at indexPath: IndexPath) {
        let realm = try! Realm()

        // 刪除鬧鐘相關的通知
        UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [alarm.creatTime])
        
        for index in 0..<7 {
            UNUserNotificationCenter.current().removePendingNotificationRequests(
                withIdentifiers: ["\(alarm.creatTime)_\(index)"]
            )
        }
        
        // 刪除Realm資料庫中的鬧鐘
        try! realm.write {
            realm.delete(alarm)
        }
        
        // 移除alarms中的鬧鐘並更新tableView
        alarms.remove(at: indexPath.row)
        tbvData.deleteRows(at: [indexPath], with: .fade)
    }

載入鬧鐘資料

每次 App 啟動或資料變動時,都要從 Realm 把鬧鐘重新抓回來:

    // 載入Realm中的鬧鐘資料
    func loadAlarms() {
        let realm = try! Realm()
        let results = realm.objects(AlarmData.self).sorted(byKeyPath: "alarmTime", ascending: true)
        alarms = Array(results)
    }

TableView 顯示

為了呈現每筆鬧鐘資料,我們在 TableView 的 Cell 中顯示:

  • 上午/下午 與時間
  • 鬧鐘名稱
  • 重複日期 (例如「平日」「假日」「每天」)
  • 開關控制

以下是程式碼:

    // 設定tableView有幾個section
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    // 設定每個section裡有多少row
    func tableView(_ tableView: UITableView,
                   numberOfRowsInSection section: Int) -> Int {
        // 根據新增的鬧鐘數量決定有多少row
        return alarms.count
    }
    /// 設定tableView每個cell要顯示的內容
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "MainTableViewCell", for: indexPath) as! MainTableViewCell
        let alarm = alarms[indexPath.row]

        // 12小時制時間顯示
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "zh_TW")
        formatter.dateFormat = "HH:mm"
        let date = formatter.date(from: alarm.alarmTime) ?? Date()
        formatter.dateFormat = "a" // 上午/下午
        let noonString = formatter.string(from: date)
        formatter.dateFormat = "hh:mm" // 時間
        let timeString = formatter.string(from: date)
        cell.lbNoon.text = noonString
        cell.lbTime.text = timeString
        // 標籤與重複內容
        let name = alarm.name.isEmpty ? "鬧鐘" : alarm.name
        let daysOfWeek = ["週日", "週一", "週二", "週三", "週四", "週五", "週六"]
        let selectedDays = alarm.repeatDays.enumerated().filter { $0.1 }.map { $0.0 }
        let repeatTitle: String
        if selectedDays.count == 2 && selectedDays.contains(0) && selectedDays.contains(6) {
            repeatTitle = "假日"
        } else if selectedDays.count == 5 && selectedDays == [1,2,3,4,5] {
            repeatTitle = "平日"
        } else if selectedDays.count == 0 {
            repeatTitle = "永不"
        } else if selectedDays.count == 7 {
            repeatTitle = "每天"
        } else {
            let selectedNames = selectedDays.map { daysOfWeek[$0] }
            repeatTitle = selectedNames.joined(separator: "、")
        }
        cell.lbClock.text = "\(name)・\(repeatTitle)"

        // 開關
        cell.swTime.isOn = alarm.isEnabled
        cell.swTime.tag = indexPath.row
        cell.swTime.addTarget(self, action: #selector(alarmSwitchChange(_:)), for: .valueChanged)
        cell.swTime.isHidden = isEditing

        // 移除箭頭
        cell.accessoryType = .none
        return cell
    }
    // 處理點選某一個row的動作
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        // 允許該section的row被選擇
        tableView.allowsSelection = true
        tableView.deselectRow(at: indexPath, animated: true)
        
        // 取得選擇的鬧鐘
        let alarm = alarms[indexPath.row]
        let editAlarmVC = AddAlarmViewController(nibName: "AddAlarmViewController", bundle: nil)
        
        // 將選中的鬧鐘資料傳遞到編輯頁面
        editAlarmVC.alarmToEdit = alarm
        editAlarmVC.selectedSound = alarm.sound
        editAlarmVC.delegate = self
        
        // 顯示編輯頁面
        let navController = UINavigationController(rootViewController: editAlarmVC)
        self.present(navController, animated: true, completion: nil)
    }
    // 處理進入編輯模式後刪除row的動作
    func tableView(_ tableView: UITableView,
                   commit editingStyle: UITableViewCell.EditingStyle,
                   forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            let alarm = alarms[indexPath.row]
            deleteAlarm(alarm, at: indexPath)
        }
    }

編輯與刪除

  • 點選 Cell:可以進入編輯畫面,修改鬧鐘設定。
  • 左滑刪除:支援 iOS 常見的「滑動刪除」手勢。
    // 左滑刪除的功能
    func tableView(_ tableView: UITableView,
    trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) ->
    UISwipeActionsConfiguration? {
    
        let deleteAction = UIContextualAction(style: .destructive, title: "刪除") {
            [weak self] (_, _, completionHandler) in
            
            guard let self = self else { return }
            let alarm = self.alarms[indexPath.row]
            self.deleteAlarm(alarm, at: indexPath)
            completionHandler(true)
        }
        
        // 設定刪除按鈕背景顏色
        deleteAction.backgroundColor = .red
        
        return UISwipeActionsConfiguration(actions: [deleteAction])
    }
}

結語

今天補上資料庫並完成基本資料操作,讓鬧鐘可以真正被儲存、載入與刪除。


上一篇
Day 22 時鐘 1
下一篇
Day 24 時鐘 3
系列文
Swift一下就會了24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言