자기관리 알림 기능을 위해 UNUserNotificationCenter를 사용해 로컬 푸시 알림을 구현해보려고 한다. UNUserNotificationCenter는 알림을 설정할 때 특정 날짜에 알림을 설정하는 기능이 없어 이 기능을 중점으로 다뤄보겠다.
NotificationManager는 싱글톤 구조체로 알림 등록, 반복, 삭제를 관리한다.
import UserNotifications
struct NotificationManager {
static let shared = NotificationManager()
let center = UNUserNotificationCenter.current()
initialNotification 메서드는 특정 날짜에 한 번 울리거나 반복 알림을 등록하는 트리거 역할을 한다. 알림 내용을 UNMutableNotificationContent로 설정하고 전달할 데이터를 userInfo에 담는다.
func initialNotification(categoryId: String, managementId: String, startDate: Date, alertTime: Date, repeatCycle: Int, body: String) {
let content = UNMutableNotificationContent()
content.title = "SnapPop"
content.body = body
content.sound = .default
// 알림과 함께 추가적으로 전달할 데이터를 딕셔너리 형식으로 설정. 이 데이터는 알림이 수신되었을 때 접근할 수 있다.
content.userInfo = ["categoryId": categoryId, "managementId": managementId, "startDate": startDate, "alertTime": alertTime, "repeatCycle": repeatCycle, "body": body]
startDate에서 연도, 월, 일 정보를 가져오고 alertTime에서 시간과 분을 가져온다.
var dateComponents = Calendar.current.dateComponents([.year, .month, .day], from: startDate)
dateComponents.hour = Calendar.current.component(.hour, from: alertTime)
dateComponents.minute = Calendar.current.component(.minute, from: alertTime)
UNCalendarNotificationTrigger를 생성하고 repeats를 false로 설정하여 한 번만 울리게 한다.
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
UNNotificationRequest를 생성하여 알림의 고유 식별자, 콘텐츠, 트리거를 설정한다.
let request = UNNotificationRequest(identifier: "initialNotification-\(categoryId)-\(managementId)", content: content, trigger: trigger)
repeatingNotification 메서드는 repeatCycle 값에 따라 매일 또는 매주 반복 알림을 등록하는 역할을 한다. 예를 들어, repeatCycle == 1이면 매일, repeatCycle == 7이면 매주 같은 요일에 알림이 울린다.
func repeatingNotification(categoryId: String, managementId: String, startDate: Date, alertTime: Date, repeatCycle: Int, body: String) {
let content = UNMutableNotificationContent()
content.title = "SnapPop"
content.body = body
content.sound = .default
var dateComponents = DateComponents()
if repeatCycle == 1 {
dateComponents.hour = Calendar.current.component(.hour, from: alertTime)
dateComponents.minute = Calendar.current.component(.minute, from: alertTime)
} else if repeatCycle == 7 {
dateComponents.weekday = Calendar.current.component(.weekday, from: startDate)
dateComponents.hour = Calendar.current.component(.hour, from: alertTime)
dateComponents.minute = Calendar.current.component(.minute, from: alertTime)
}
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let request = UNNotificationRequest(identifier: "repeatingNotification-\(categoryId)-\(managementId)", content: content, trigger: trigger)
center.add(request) { error in if let error = error { print("Error scheduling notification: \(error.localizedDescription)") }}
}
SceneDelegate에 UNUserNotificationCenterDelegate를 추가해 알림 권한을 요청하고 초기 알림 수신 시 userInfo 데이터를 통해 반복 알림을 등록한다.
extension SceneDelegate: UNUserNotificationCenterDelegate {
func requestNotificationAuthorization() {
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
if granted { print("Notification authorization granted") }
else { print("Notification authorization denied") }
}
}
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
let userInfo = notification.request.content.userInfo
if let repeatCycle = userInfo["repeatCycle"] as? Int, repeatCycle != 0 {
guard let categoryId = userInfo["categoryId"] as? String,
let managementID = userInfo["managementId"] as? String,
let startDate = userInfo["startDate"] as? Date,
let alertTime = userInfo["alertTime"] as? Date,
let body = userInfo["body"] as? String else { return }
NotificationManager.shared.repeatingNotification(categoryId: categoryId, managementId: managementID, startDate: startDate, alertTime: alertTime, repeatCycle: repeatCycle, body: body)
}
completionHandler([.banner, .list, .badge, .sound])
}
}