어려운 기술이 아닌데 내가 너무 혼자 고생했나 싶기도 하지만 그래도 무사히 구현해낸게 뿌듯함 + 나중에 잊어버리지 않기 위해 정리하려고 올리는 글!
먼저 아이폰의 푸시 알림은 크게 두 종류가 있어요.
서버를 사용하여 보내는 서버 푸시인 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 해야합니다.
푸시 알림을 보낼 ViewController에서 다시 한 번 UserNotifications를 import 해줍니다.
import UserNotifications
동일하게 UNUserNotificationCenter 객체를 받아오고
let notificationCenter = UNUserNotificationCenter.current()
Notification을 보내는 메서드를 작성해줍니다.
먼저 Notification의 구조를 살펴보면 크게 content
, trigger
, requeset
3가지로 나뉘어있습니다.
content는 알림의 내용입니다. UNMutableNotificationContent 클래스를 사용해서 생성할 수 있습니다. data의 유형은 title, subtitle, body, badge, sound 등등 다양합니다. 저는 간단하게 title
과 body
만을 사용해서 만들어보았습니다.
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
일 때도 알림을 처리해주기 위해서는 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 할 지 판단을 잘못하는 바람에 며칠을 고생했던 기억이 있어요🥲 그 부분 잊지말고 구현할 때 잘 결정하시길..!
정리 너무 감사합니다
로컬알림 기본개념 잡는데 많은 도움이 되었어요 :)