SwiftUI - 로컬 알림(Local Notification)

Marble·2025년 3월 8일

진행중인 프로젝트에서 알림 기능을 구현할 일이 있어서 이번 글에서는 알림 기능 방법 중 하나인 로컬 알림(Local Notification)을 구현하는 방법에 대해 알아볼 예정입니다. 그전에 먼저 알림 기능 방법인 로컬 알림과 푸시 알림에 대해 알아보겠습니다.

로컬 알림(Local Notification)

로컬 알림은 앱 내부에서 사용자에게 보내는 알림입니다. 특정 이벤트나 일정 시간 후에 보낼 수 있으며, 위치를 기반으로 특정 장소에 도착했을 때도 보낼 수 있습니다. 네트워크 연결 없이 기기 내에서 동작하므로 푸시 토큰이 필요 없으며, 빠르게 설정하고 사용할 수 있습니다. iOS 시스템에서 직접 알림을 처리하기 때문에 배터리 소모가 적은 편이며 백그라운드 또는 종료된 상태에서도 알림을 표시할 수 있습니다.

앱에서 보내다 보니 미리 예약해둔 시간이 아닌 실시간 알림 전송은 불가능합니다.

푸시 알림(Push Notification)

푸시 알림은 서버에서 사용자의 기기로 원격으로 보내는 알림입니다. iOS에서는 APNs (Apple Push Notification Service)를 통해 동작하며, 로컬 알림과 마찬가지로 앱이 백그라운드 또는 종료된 상태에서도 사용자에게 메시지 전송 가능합니다. 로컬 알림과 다른 점으로는 네트워크 연결이 필요하며, 보통 Firebase Cloud Messaging(FCM) 또는 자체 서버를 통해 관리됩니다.

서버에서 사용자의 기기로 알림을 보내기 위해서는 푸시 토큰이 필요한데 사용자 기기가 초기화되거나 앱을 재설치하면 새로운 푸시 토큰을 받아야 합니다. 또한 서버를 사용하다보니 인터넷 연결이 끊긴 경우 알림을 받을 수 없으며, 푸시 트래픽이 많거나 서버가 지연되면 알림이 늦게 도착할 수 있습니다.

특징

둘의 특징을 정리하면 다음과 같습니다.

항목로컬 알림 (Local Notification)푸시 알림 (Push Notification)
전송 지연 시간✅ 즉시 실행 (ms 단위)⚠️ 서버 & 네트워크 지연 발생 (수 초 ~ 수 분)
CPU & 메모리 사용✅ 앱 내부에서 실행 → 영향 거의 없음⚠️ 백그라운드에서 실행되며, 시스템 리소스 사용
네트워크 사용량❌ 네트워크 불필요⚠️ 서버 요청 및 데이터 패킷 사용
배터리 영향✅ 낮음 (기기 내부 실행)⚠️ 높음 (APNs & 네트워크 필요)
알림 트리거 방식⏳ 타이머, 위치, 이벤트 기반🌍 서버에서 원격 트리거
백그라운드 동작✅ 가능✅ 가능 (하지만 서버 연결 필요)
실시간 알림 가능❌ 예약된 시간에만 가능✅ 가능 (예: 채팅, 이메일)
사용자별 맞춤 메시지 가능 여부❌ 불가능 (모든 사용자 동일)✅ 가능 (서버에서 개별 처리)

상황별 추천 방식

위 특징들을 고려했을 때 상황별 추천 알림 방식은 다음과 같습니다.

사용 사례추천 알림 방식설명
⏳ 일정 & 타이머✅ 로컬 알림네트워크 없이 즉시 알림 표시
📍 위치 기반 알림✅ 로컬 알림특정 지역 도착 시 알림 (ex. 리마인더)
🎮 앱 내부 이벤트 (게임, 목표 도달 등)✅ 로컬 알림앱에서 바로 트리거 가능
📩 새로운 메시지 수신 (채팅, 이메일 등)✅ 푸시 알림서버에서 변경 사항을 전달해야 함
🏦 거래 내역, 금융 알림✅ 푸시 알림서버에서 유저 상태를 업데이트해야 함
📢 광고 & 마케팅 알림✅ 푸시 알림서버에서 유저 타겟팅 가능

제가 하는 프로젝트는 특정 시간이 되면 정해진 형식의 알림을 보내면 되기 때문에 로컬 알림을 사용하기로 했습니다. iOS에서는 로컬 알림 및 푸시 알림을 처리하기 위해 UserNotification라는 프레임워크를 제공해줘서 이를 사용해 볼 예정입니다.

UserNotification

UserNotification 공식문서에서 다음과 같이 정의되어 있으며, 서버에서 사용자 기기로 사용자에게 표시되는 알림을 푸시하거나 앱에서 로컬로 알림을 생성합니다.

UserNotification에서 사용하는 주요 객체는 다음과 같습니다.

UNMutableNotificationContent

알림에 필요한 메세지와 같은 속성을 담는 콘텐츠 역할로 제목, 내용, 뱃지, 사운드 설정등을 담당합니다.

UNTimeIntervalNotificationTrigger

알림 발송 조건(트리거)을 담당하며 알림 등록으로부터 몇 초 뒤에 알림을 보낼 지 설정합니다.

UNCalendarNotificationTrigger

UNTimeIntervalNotificationTrigger과 마찬가지로 알림 발송 조건(트리거)을 담당하며 알림을 보내고자하는 특정 시간을 지정할 때 사용합니다.

UNNotificationReques

앞서 말한 콘텐츠와 트리거를 전달하기 위한 알림 요청 객체입니다.

UNUserNotificationCenter

알림 권한 요청, 알림 스케줄링, 알림 수신 처리, 배지(Badge) 관리 등을 담당합니다.

사용 방법

  1. delegate 등록
    포그라운드 알림을 표시하기 위해 delegate를 등록해줘야 합니다.

  2. 알림을 보내기 위해서는 권한을 요청해야합니다.
    UNUserNotifiactionCenter.requestAuthorization 메서드를 사용해 유저에게 권한을 요청합니다.

  3. 로컬 알림을 등록합니다.
    UNMutableNotificationContent을 생성한 후 원하는 제목과 내용을 각각 title과 body에 주입해주고 trigger와 함께 UNNotificationRequest를 생성해 등록해줍니다.
    예제 코드는 UNTimeIntervalNotificationTrigger(timeInterval: TimeInterval, repeats: Bool)를 사용해서 secondsLater초만큼 시간이 지난 후에 알림이 오도록 트리거를 구현했습니다.
    만약 원하는 시간에 알림이 오도록 구현하고 싶다면 UNCalendarNotificationTrigger(dateMatching: DateCompoents, repeats: Bool) 메서드를 사용하면 원하는 시간에 알림이 오도록 트리거를 구현할 수 있습니다.

예제 코드

// NotificationManager.Swift
import UserNotifications

class NotificationManager: NSObject, UNUserNotificationCenterDelegate {
    static let shared = NotificationManager()
    
    private override init() {}

    func requestPermission() {
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
            if let error = error {
                print("❌ 알림 권한 요청 실패: \(error)")
            } else {
                print("✅ 알림 권한 승인 여부: \(granted)")
            }
        }
    }

    func addNotification(title: String, body: String, secondsLater: TimeInterval) {
        let content = UNMutableNotificationContent()
        content.title = title
        content.body = body
        content.sound = .default

        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: secondsLater, repeats: false)
        let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)

        UNUserNotificationCenter.current().add(request) { error in
            if let error = error {
                print("❌ 알림 추가 실패: \(error)")
            } else {
                print("📢 알림이 \(secondsLater)초 후에 예약됨")
            }
        }
    }

    func removeAllNotifications() {
        UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
        print("🔔 모든 예약된 알림이 삭제됨")
    }

	// 포그라운드에서도 알림 표시 (필수)
    func registerDelegate() {
        UNUserNotificationCenter.current().delegate = self
    }

	// 포그라운드에서도 알림이 보이도록 설정
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.banner, .sound])
    }
}
// ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Button("🔔 알림 권한 요청") {
                NotificationManager.shared.requestPermission()
            }
            .padding()
            
            Button("📢 2초 후 알림 보내기") {
                NotificationManager.shared.addNotification(title: "테스트 알림", body: "2초 후 도착한 알림입니다.", secondsLater: 2)
            }
            .padding()

            Button("❌ 모든 알림 삭제") {
                NotificationManager.shared.removeAllNotifications()
            }
            .padding()
        }
        .onAppear {
            NotificationManager.shared.registerDelegate()
        }
    }
}

위처럼 모든 알림을 삭제하지 않고 원하는 알림만 삭제하고 싶을 때는 UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [])를 사용해서 알람을 등록할 때 사용한 identifier에 해당하는 알림을 제거할 수 있습니다.

실행화면을 마지막으로 글을 마치겠습니다.

profile
개발자가 되고 싶은 공돌이

0개의 댓글