[iOS] 로컬 푸시 알림(Local Notification) 구현하기

린다·2021년 8월 2일
3

Learning iOS

목록 보기
2/13
post-thumbnail

어려운 기술이 아닌데 내가 너무 혼자 고생했나 싶기도 하지만 그래도 무사히 구현해낸게 뿌듯함 + 나중에 잊어버리지 않기 위해 정리하려고 올리는 글!

iOS Push Notifcation

먼저 아이폰의 푸시 알림은 크게 두 종류가 있어요.
서버를 사용하여 보내는 서버 푸시인 Remote Notification과 기기 자체에서 보내는 로컬 푸시 Local Notification가 있습니다. 이번에 다룰 주제는 Local Notification입니다!

~ 내 tmi ~
처음에 푸시 알림을 보내려고 간단하게 알아보니 다들 APNs를 사용해야된다고 해서 + 애플 개발자 계정이 필요하다고 해서 그날 무작정 애플 개발자 계정도 결제함...ㅋㅋㅋㅋ 그리고 반나절가량 열심히 삽질을 한 끝에 서버푸시를 구현한...^^...하여튼 그렇게 Firebase랑 연결까지 다 한 뒤에 내가 구현하려고 했던건 로컬 푸시라는걸 깨달았다...

푸시 알림 권한 요청하기

먼저 알림 관련된 기능을 구현하고자 할 때 사용해야하는 프레임워크가 있습니다.
바로 User Notifications인데 애플에서는 이 프레임워크를 이러한 상황에서 사용하라고 권고하고 있습니다.
1. 앱이 지원하는 알림의 유형을 정의할 때
2. 내 알림 유형과 관련된 커스텀 작업을 정의할 때
3. 로컬 알림을 계획하여 전달하고자 할 때
4. 이미지 전달된 알림을 처리할 때
5. 사용자가 선택한 작업에 응답할 때

이 프레임워크를 사용하여 가장 먼저 해야할 일은 푸시 알림 권한 요청입니다.
애플에서는 언제나 그렇듯이 사용자를 가장 우선시하기 때문에 사용자가 알림 기반 상호작용이 방해된다고 생각할 수 있어서 꼭 먼저 사용 권한을 얻어야 합니다!

바로 이 화면처럼요!

가장 먼저 푸시 알림 권한을 보내고자 하는 ViewController에서 UserNotifications를 import 해줍니다.

import UserNotifications

그리고 UserNotification에 속해있는 UNUserNotificationCenter을 사용해야하는데 이때 이 클래스는 사용할 때 절대 instance를 직접 생성하지말고 current()라는 타입 메서드를 사용하라고 명시돼있습니다.

current()
Returns the shared user notification center object for your app or app extension.

let notificationCenter = UNUserNotificationCenter.current()

그리고 권한을 요청하는 메서드를 작성해줍니다. UNAuthorizationOptions에는 badge, sound, alert 등의 옵션이 있습니다. 각 옵션의 의미는 크게 어렵지 않으니 패쓰하겠습니다!ㅎㅎ
UNUserNotificationCenter.current()로 객체를 반환받은 뒤 requestAuthorization 메서드를 활용합니다. options에는 위에 작성해준 authOptions가 들어가면 되고 completionHandler에서는 사용자가 권한을 허락했는지에 대한 여부와 에러에 대한 정보에 대해 상황을 처리해주면 됩니다. 이때 success는 Bool type 이라는 점!

func requestNotificationAuthorization() {
    let authOptions: UNAuthorizationOptions = [.alert, .sound, .badge]

    notificationCenter.requestAuthorization(options: authOptions) { success, error in
        if let error = error {
            print(error)
        }
    }
}

그리고 viewDidLoad()에 위 메서드를 작성해주면 사용자에게 알림 권한을 요청하는 창을 확인할 수 있습니다!

requestNotificationAuthorization()

푸시 알림 request 하기

이제 권한 허락을 받았으니 보낼 푸시 알림을 작성하고 request 해야합니다.
푸시 알림을 보낼 ViewController에서 다시 한 번 UserNotifications를 import 해줍니다.

import UserNotifications

동일하게 UNUserNotificationCenter 객체를 받아오고

let notificationCenter = UNUserNotificationCenter.current()

Notification을 보내는 메서드를 작성해줍니다.
먼저 Notification의 구조를 살펴보면 크게 content, trigger, requeset 3가지로 나뉘어있습니다.

content는 알림의 내용입니다. UNMutableNotificationContent 클래스를 사용해서 생성할 수 있습니다. data의 유형은 title, subtitle, body, badge, sound 등등 다양합니다. 저는 간단하게 titlebody 만을 사용해서 만들어보았습니다.

let content = UNMutableNotificationContent()
        content.title = "오늘도 Wandoo와 함께 완주해봐요!"
        content.body = "지난 진도를 체크하고 앞으로의 계획을 함께 세워봐요 :)"

다음 트리거는 알림이 발동되는 조건입니다. 크게 시간 간격(time interval), 특정한 시간(calendar), 위치(location)을 기준으로 알림을 발동시킬 수 있습니다.
저는 매일 반복적으로 같은 시간에 알림을 보내기 위해서 UNCalendarNotificationTrigger를 사용하여 현재 알림을 request하는 뷰에 있는 timePicker로부터 시간을 받아와 trigger를 설정해주었습니다.

let trigger = UNCalendarNotificationTrigger(
            dateMatching: Calendar.current.dateComponents([.hour, .minute], from: timePicker.date), repeats: true)

마지막으로 request는 UNNotificationRequest라는 클래스를 사용합니다. 앞에서 만들었던 content와 trigger를 가져와서 사용해야하고 identifier는 여러 알림 요청들이 있을 때 그 요청들을 구분할 수 있는 구분자라고 생각하시면 됩니다! 일반적인 String으로 설정하셔도 되는데 저는 UUID를 사용하여 설정해주었습니다.

let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)

마지막으로는 이렇게 만들어준 requeset를 notification center에 추가해주기만 하면 됩니다!
처음에 객체로 받아주었던 notificationCenter를 다시 사용하여 add(request)를 해주면 끝!
저는 앱 내에서 사용자가 알림을 받겠다고 설정한 경우에만 request를 추가해주었기 때문에 조건문이 추가돼있습니다.

if UserDefaults.standard.bool(forKey: "wantAlarm") {
            notificationCenter.add(request) { (error) in
                if error != nil {
                    // handle errors
                }
            }
        }

전체 코드 구성은 이렇게 돼있구요

func sendNoti() {
        notificationCenter.removeAllPendingNotificationRequests()

        let content = UNMutableNotificationContent()
        content.title = "오늘도 Wandoo와 함께 완주해봐요!"
        content.body = "지난 진도를 체크하고 앞으로의 계획을 함께 세워봐요 :)"

        let trigger = UNCalendarNotificationTrigger(
            dateMatching: Calendar.current.dateComponents([.hour, .minute], from: timePicker.date), repeats: true)

        let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)

        if UserDefaults.standard.bool(forKey: "wantAlarm") {
            notificationCenter.add(request) { (error) in
                if error != nil {
                    // handle errors
                }
            }
        }
    }

저는 사용자가 시간 설정 후 뷰를 끌 때 request를 추가해주고 싶었기 때문에 viewWillDisappear 메서드를 오버라이딩하여 해당 메서드에 sendNoti()를 작성해주었습니다.

이렇게 매일 정해진 시간에 로컬 푸시 알림을 보낼 수 있습니다! :)

Foreground 상태 알림 처리해주기

하지만 여기까지만 진행을 한다면 앱이 현재 사용자에게 보여지는 상태일 때(앱을 켜서 사용자가 보고 있는 상황) 즉 Foreground에서는 알림이 오지 않습니다!
앱이 Foreground일 때도 알림을 처리해주기 위해서는 AppDelegate.swift

import UserNotifications

extension AppDelegate: UNUserNotificationCenterDelegate {
    // 앱이 실행 중 일때 처리하는 메서드
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.list, .banner])
    }
}

아래의 코드를 작성해주면 됩니다!
userNotificationCenter(_:willPresent:withCompletionHandler:) 해당 메서드는 앱이 foreground 상태에서 알림을 수신했을 때 발동되는 메서드입니다.

그리고 정말 마지막으로

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        UNUserNotificationCenter.current().delegate = self
        return true
    }

이렇게 didFinishLaunchingWithOptions 메서드에서 delegate만 할당을 해주면 앱이 실행 중 일때도 알림을 받을 수 있습니다!

저는 처음에 이 부분을 구현할 때 알림을 어느 view에서 request 할 지 판단을 잘못하는 바람에 며칠을 고생했던 기억이 있어요🥲 그 부분 잊지말고 구현할 때 잘 결정하시길..!

2개의 댓글

comment-user-thumbnail
2022년 2월 19일

정리 너무 감사합니다
로컬알림 기본개념 잡는데 많은 도움이 되었어요 :)

1개의 답글