본문 바로가기

Project/30MinRead

🎉 타이머가 완료된 시점에 사용자에게 알림을 주기!

 

✅ 1. 화면이 열려 있을 때 (포그라운드 상태)

화면이 켜져 있고 타이머가 끝났을 때 알림창(Alert)이나 경고음(Sound)을 주려면, stopTimer() 호출 직후에 추가

@objc func refreshValue() {
    guard let startDate = userDefaults.object(forKey: startDateKey) as? Date else { return }
    
    let elapsed = Int(Date().timeIntervalSince(startDate))
    let updatedRemaining = baseRemainingSeconds - elapsed

    if updatedRemaining > 0 {
        remainingSeconds = updatedRemaining
        updateLabel()
    } else {
        stopTimer()
        timeLabel.text = "00:00:00"
        presentTimerFinishedAlert() // ✅ 여기에 추가
        AudioServicesPlaySystemSound(SystemSoundID(1005)) // 📣 기본 경고음
    }
}

func presentTimerFinishedAlert() {
    let alert = UIAlertController(title: "타이머 종료", message: "읽기 시간이 완료되었습니다!", preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "확인", style: .default))
    present(alert, animated: true)
}

 

🛠️ AudioServicesPlaySystemSound(SystemSoundID(1005)) 이건 시스템 기본 효과음 중 하나

 

✅ 2. 앱이 백그라운드에 있을 때 (홈버튼/화면 끄기 등)

이때는 Local Notification(로컬 알림)을 등록

타이머가 끝나는 시점을 계산해서 미리 알림을 예약해놓는 방식

// ✅ 타이머 시작 시:
func scheduleLocalNotification(after seconds: Int) {
    let content = UNMutableNotificationContent()
    content.title = "타이머 종료"
    content.body = "\(readItem.title) 읽기 시간이 끝났어요!"
    content.sound = .default

    let trigger = UNTimeIntervalNotificationTrigger(timeInterval: TimeInterval(seconds), repeats: false)
    let request = UNNotificationRequest(identifier: "timer_\(readItem.id)", content: content, trigger: trigger)

    UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}


// ✅ 타이머 시작 시에 호출
func startTimer() {
    ...
    scheduleLocalNotification(after: remainingSeconds)
}


// ✅ 타이머 중단(PAUSE나 RESET) 시에는 등록된 알림을 취소
func stopTimer() {
    ...
    UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ["timer_\(readItem.id)"])
}

 

🔐 알림 권한 요청은 필수

앱에서 알림을 사용하려면 최초에 권한을 요청

import UIKit
import CoreData

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
        // 🔔 알림 권한 요청
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
            if granted {
                print("✅ 알림 권한 허용됨")
            } else {
                print("❌ 알림 권한 거부됨 또는 오류: \(error?.localizedDescription ?? "")")
            }
        }
        return true
    }
    ...

 

 

✅ NotificationPermissionManager.swift 파일 생성

앱 전체에서 깔끔하게 알림 권한을 관리할 수 있도록 유틸 클래스(Helper) 형식

이걸 활용하면 어디서든 간단하게 호출해서 알림 권한을 체크하고, 필요한 경우 설정화면으로 유도할 수 있음

import UIKit
import UserNotifications

struct NotificationPermissionManager {
    
    /// 알림 권한 상태를 확인하고, 거부된 경우 설정 화면으로 유도
    static func checkAndRequestPermission(from viewController: UIViewController) {
        UNUserNotificationCenter.current().getNotificationSettings { settings in
            switch settings.authorizationStatus {
            case .notDetermined:
                // 아직 한 번도 요청 안 함 → 요청
                UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
                    if granted {
                        print("🔔 알림 권한 허용됨")
                    } else {
                        print("❌ 알림 권한 거부됨 또는 오류: \(error?.localizedDescription ?? "")")
                    }
                }
                
            case .denied:
                // 이미 거부된 상태 → 설정화면 유도
                DispatchQueue.main.async {
                    let alert = UIAlertController(
                        title: "알림 권한이 꺼져있어요",
                        message: "설정 > 알림에서 권한을 켜주세요.",
                        preferredStyle: .alert
                    )
                    alert.addAction(UIAlertAction(title: "설정으로 가기", style: .default) { _ in
                        if let url = URL(string: UIApplication.openSettingsURLString) {
                            UIApplication.shared.open(url)
                        }
                    })
                    alert.addAction(UIAlertAction(title: "취소", style: .cancel))
                    viewController.present(alert, animated: true)
                }
                
            case .authorized, .provisional, .ephemeral:
                // 이미 권한 허용됨 → 아무것도 안 해도 됨
                break
            @unknown default:
                break
            }
        }
    }
}