ํธ์/๋ก์ปฌ ์๋ฆผ ๊ฐ์ด๋. ์ด ๋ฌธ์๋ฅผ ์ฝ๊ณ UserNotifications ์ฝ๋๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
UserNotifications๋ ๋ก์ปฌ ๋ฐ ์๊ฒฉ ์๋ฆผ์ ๊ด๋ฆฌํ๋ ํ๋ ์์ํฌ์ ๋๋ค. ์๋ฆผ ์์ฝ, ์ปค์คํ UI, ์ก์ ๋ฒํผ ๋ฑ์ ์ง์ํฉ๋๋ค.
import UserNotificationsfunc requestPermission() async throws -> Bool {
let center = UNUserNotificationCenter.current()
let granted = try await center.requestAuthorization(options: [
.alert,
.badge,
.sound,
.criticalAlert, // ๊ธด๊ธ ์๋ฆผ (๋ณ๋ ์น์ธ ํ์)
.provisional // ์กฐ์ฉํ ์๋ฆผ (๊ถํ ์์ด ๊ฐ๋ฅ)
])
return granted
}
// ํ์ฌ ๊ถํ ์ํ ํ์ธ
func checkPermission() async -> UNAuthorizationStatus {
let settings = await UNUserNotificationCenter.current().notificationSettings()
return settings.authorizationStatus
}func scheduleNotification() async throws {
let content = UNMutableNotificationContent()
content.title = "์๋ฆผ ์ ๋ชฉ"
content.subtitle = "๋ถ์ ๋ชฉ"
content.body = "์๋ฆผ ๋ด์ฉ์
๋๋ค."
content.sound = .default
content.badge = 1
// ํธ๋ฆฌ๊ฑฐ: 5์ด ํ
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
let request = UNNotificationRequest(
identifier: UUID().uuidString,
content: content,
trigger: trigger
)
try await UNUserNotificationCenter.current().add(request)
}// ์๊ฐ ๊ฐ๊ฒฉ (์ด)
let timeTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 60, repeats: true)
// ํน์ ๋ ์ง/์๊ฐ
var dateComponents = DateComponents()
dateComponents.hour = 9
dateComponents.minute = 0
let calendarTrigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
// ์์น ๊ธฐ๋ฐ
let center = CLLocationCoordinate2D(latitude: 37.5665, longitude: 126.9780)
let region = CLCircularRegion(center: center, radius: 100, identifier: "office")
region.notifyOnEntry = true
let locationTrigger = UNLocationNotificationTrigger(region: region, repeats: false)import SwiftUI
import UserNotifications
// MARK: - Notification Manager
@Observable
class NotificationManager {
var isAuthorized = false
var pendingNotifications: [UNNotificationRequest] = []
private let center = UNUserNotificationCenter.current()
func requestPermission() async {
do {
isAuthorized = try await center.requestAuthorization(options: [.alert, .badge, .sound])
await setupCategories()
} catch {
print("๊ถํ ์์ฒญ ์คํจ: \(error)")
}
}
func checkStatus() async {
let settings = await center.notificationSettings()
isAuthorized = settings.authorizationStatus == .authorized
}
// ์นดํ
๊ณ ๋ฆฌ ๋ฐ ์ก์
์ค์
private func setupCategories() async {
let completeAction = UNNotificationAction(
identifier: "COMPLETE",
title: "์๋ฃ",
options: [.foreground]
)
let snoozeAction = UNNotificationAction(
identifier: "SNOOZE",
title: "10๋ถ ๋ค ์๋ฆผ",
options: []
)
let taskCategory = UNNotificationCategory(
identifier: "TASK_REMINDER",
actions: [completeAction, snoozeAction],
intentIdentifiers: [],
options: [.customDismissAction]
)
center.setNotificationCategories([taskCategory])
}
// ์๋ฆผ ์์ฝ
func scheduleReminder(title: String, body: String, date: Date) async throws {
let content = UNMutableNotificationContent()
content.title = title
content.body = body
content.sound = .default
content.categoryIdentifier = "TASK_REMINDER"
content.userInfo = ["taskId": UUID().uuidString]
let components = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: date)
let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: false)
let request = UNNotificationRequest(
identifier: UUID().uuidString,
content: content,
trigger: trigger
)
try await center.add(request)
await fetchPending()
}
// ๋งค์ผ ๋ฐ๋ณต ์๋ฆผ
func scheduleDailyReminder(title: String, body: String, hour: Int, minute: Int) async throws {
let content = UNMutableNotificationContent()
content.title = title
content.body = body
content.sound = .default
var dateComponents = DateComponents()
dateComponents.hour = hour
dateComponents.minute = minute
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let request = UNNotificationRequest(
identifier: "daily-\(hour)-\(minute)",
content: content,
trigger: trigger
)
try await center.add(request)
}
// ๋๊ธฐ ์ค์ธ ์๋ฆผ ์กฐํ
func fetchPending() async {
pendingNotifications = await center.pendingNotificationRequests()
}
// ์๋ฆผ ์ทจ์
func cancel(identifier: String) {
center.removePendingNotificationRequests(withIdentifiers: [identifier])
}
func cancelAll() {
center.removeAllPendingNotificationRequests()
}
// ๋ฐฐ์ง ์ด๊ธฐํ
func clearBadge() async {
try? await center.setBadgeCount(0)
}
}
// MARK: - View
struct NotificationDemoView: View {
@State private var manager = NotificationManager()
@State private var reminderTitle = ""
@State private var reminderDate = Date().addingTimeInterval(60)
var body: some View {
NavigationStack {
Form {
// ๊ถํ ์น์
Section("๊ถํ") {
HStack {
Text("์๋ฆผ ๊ถํ")
Spacer()
Text(manager.isAuthorized ? "ํ์ฉ๋จ" : "๊ฑฐ๋ถ๋จ")
.foregroundStyle(manager.isAuthorized ? .green : .red)
}
if !manager.isAuthorized {
Button("๊ถํ ์์ฒญ") {
Task { await manager.requestPermission() }
}
}
}
// ์๋ฆผ ์์ฝ
Section("์ ์๋ฆผ") {
TextField("์ ๋ชฉ", text: $reminderTitle)
DatePicker("์๊ฐ", selection: $reminderDate, displayedComponents: [.date, .hourAndMinute])
Button("์๋ฆผ ์์ฝ") {
Task {
try? await manager.scheduleReminder(
title: reminderTitle,
body: "์์ฝ๋ ์๋ฆผ์
๋๋ค",
date: reminderDate
)
reminderTitle = ""
}
}
.disabled(reminderTitle.isEmpty)
}
// ๋๊ธฐ ์ค์ธ ์๋ฆผ
Section("์์ฝ๋ ์๋ฆผ (\(manager.pendingNotifications.count))") {
ForEach(manager.pendingNotifications, id: \.identifier) { request in
VStack(alignment: .leading) {
Text(request.content.title)
.font(.headline)
if let trigger = request.trigger as? UNCalendarNotificationTrigger,
let nextDate = trigger.nextTriggerDate() {
Text(nextDate, style: .relative)
.font(.caption)
.foregroundStyle(.secondary)
}
}
.swipeActions {
Button("์ญ์ ", role: .destructive) {
manager.cancel(identifier: request.identifier)
Task { await manager.fetchPending() }
}
}
}
if !manager.pendingNotifications.isEmpty {
Button("๋ชจ๋ ์ทจ์", role: .destructive) {
manager.cancelAll()
Task { await manager.fetchPending() }
}
}
}
}
.navigationTitle("์๋ฆผ")
.task {
await manager.checkStatus()
await manager.fetchPending()
}
}
}
}
// MARK: - AppDelegate์์ ์๋ฆผ ์ฒ๋ฆฌ
class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UNUserNotificationCenter.current().delegate = self
return true
}
// ์ฑ์ด foreground์ผ ๋ ์๋ฆผ ํ์
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
return [.banner, .badge, .sound]
}
// ์๋ฆผ ํญ ๋๋ ์ก์
๋ฒํผ ์ฒ๋ฆฌ
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async {
let userInfo = response.notification.request.content.userInfo
let actionId = response.actionIdentifier
switch actionId {
case "COMPLETE":
// ์๋ฃ ์ฒ๋ฆฌ
if let taskId = userInfo["taskId"] as? String {
print("Task completed: \(taskId)")
}
case "SNOOZE":
// 10๋ถ ๋ค ๋ค์ ์๋ฆผ
let content = response.notification.request.content.mutableCopy() as! UNMutableNotificationContent
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 600, repeats: false)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
try? await center.add(request)
default:
break
}
}
}func scheduleWithImage(imageURL: URL) async throws {
let content = UNMutableNotificationContent()
content.title = "์ฌ์ง ์๋ฆผ"
content.body = "์ ์ฌ์ง์ด ๋์ฐฉํ์ต๋๋ค"
let attachment = try UNNotificationAttachment(identifier: "image", url: imageURL, options: nil)
content.attachments = [attachment]
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
try await UNUserNotificationCenter.current().add(request)
}// NotificationViewController.swift (Extension Target)
import UIKit
import UserNotifications
import UserNotificationsUI
class NotificationViewController: UIViewController, UNNotificationContentExtension {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var imageView: UIImageView!
func didReceive(_ notification: UNNotification) {
let content = notification.request.content
titleLabel.text = content.title
if let attachment = content.attachments.first,
attachment.url.startAccessingSecurityScopedResource() {
imageView.image = UIImage(contentsOfFile: attachment.url.path)
attachment.url.stopAccessingSecurityScopedResource()
}
}
}// AppDelegate
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
print("Device Token: \(token)")
// ์๋ฒ๋ก ํ ํฐ ์ ์ก
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("APNs ๋ฑ๋ก ์คํจ: \(error)")
}
// ๋ฑ๋ก ์์ฒญ
UIApplication.shared.registerForRemoteNotifications()-
๊ถํ ์์ฒญ ํ์ด๋ฐ
- ์ฑ ์ฒซ ์คํ ์ ๋ฐ๋ก ์์ฒญ โ
- ์๋ฆผ์ด ํ์ํ ๊ธฐ๋ฅ ์ฌ์ฉ ์ง์ ์์ฒญ โ
-
์๋ฆผ ์๋ณ์
- ๊ฐ์ ์๋ณ์๋ก ๋ฑ๋กํ๋ฉด ๊ธฐ์กด ์๋ฆผ ๋ฎ์ด์
- ์ ๋ฐ์ดํธ ๊ฐ๋ฅํ ์๋ฆผ์ ํ์ฉ
-
๋ฐฐ์ง ๊ด๋ฆฌ
// ๋ฐฐ์ง ์ค์ try await center.setBadgeCount(5) // ๋ฐฐ์ง ์ด๊ธฐํ (์ฑ ์ด ๋) try await center.setBadgeCount(0)
-
์๋ฎฌ๋ ์ดํฐ ์ ํ
- ์๊ฒฉ ํธ์ ์๋ฆผ์ ์ค์ ๊ธฐ๊ธฐ์์๋ง ํ ์คํธ ๊ฐ๋ฅ
- ๋ก์ปฌ ์๋ฆผ์ ์๋ฎฌ๋ ์ดํฐ์์ ๊ฐ๋ฅ