L-j-h-c / TIL

CS, Swift, Java, C++, 개발 관련 공부한 내용 정리
12 stars 0 forks source link

[Swift] Local Notification #28

Closed L-j-h-c closed 2 years ago

L-j-h-c commented 2 years ago

Local Notifcation이란 무엇일까?

Local Notifcation(이하 로컬 알림)은 앱 내부에서 생성한 특정 메시지를 전달 유저에게 전달하는 알림이며, 사용자의 관심을 끄는 방법 중의 하나이다.

알림이 도착하면 App Icon의 뱃지에 특정 문자열을 표기할 수 있으며, 이러한 알림들을 알림 센터에 남길 수 있다.

로컬 알림의 가장 큰 특징은 정해진 시점, 정해진 위치, 정해진 시간 간격에 맞게 미리 알림을 Custom해 두고, 그 상황이 실제로 일어나면 알림이 유저에게 전달된다는 것이다.

따라서 시점이 언제가 될지 알 수 없고, 내용을 미리 작성해 둘 수 없는 경우에는 APNs(애플 푸시 알림 서비스)와 같은 Remote Notifcation을 이용해야 한다.

본격적으로 유저에게 알림의 권한을 요청하고 알림에 대한 요청서를 작성하는 방법에 대해 알아보기 전에, 앱의 알림에 전반적으로 관여하는 객체인 UNUserNotificationCenter를 살펴보자.

UNUserNotificationCenter 참고

정의 : 앱 또는 앱의 extension에서 알림에 연관된 활동들을 관리하는 중앙 객체이다.

class UNUserNotificationCenter : [NSObject]

UNUserNotifcationCenter도 NotificationCenter와 비슷하게 .current()라는 메서드를 통해 앱 전반의 알림에 관여하는 싱글턴 객체에 접근할 수 있다.

UNUserNotifcationCenter에 존재하는 다양한 메서드와 프로퍼티들은 다음과 같은 용도에 따라 분류되어 있다.

여러가지 유용한 메서드들이 존재하나, 우선 기본적인 로컬 알림을 보내는 과정을 따라가면서 익혀보자.

유저에게 알림 권한 요청하기 - 참고

로컬 또는 리모트 알림은 app의 아이콘에 badge를 표시하고, 알림의 소리를 재생하며, alert를 띄우는 것에 대한 권한을 허가받아야 유저의 기기에 알림을 띄울 수 있다. 따라서 UNNotifcationCenter에는 이러한 권한의 허가와 관련된 방법들, 즉 authorization을 managing하는 방법들이 존재한다.

우선 로컬 알림은 앱 전반적인 Cycle의 관리를 받기 때문에 appDelegate에서 몇 가지 작업을 해줘야 한다. (뷰 컨트롤러에서 해줘도 되지만 손이 많이 간다)

AppDelegate 우선 appDelegate에서 UNUserNotifcationCenterDelegate 메서드를 대리하기 위해 딜리게이트를 self로 채택해준다.

그런 다음 requestAuthorization() 메서드를 통해 특정 옵션을 가진 알림의 권한을 요청할 수 있다. options에는 허가를 요청하는 옵션들을 넣어주면 되고, 컴플리션 핸들러로 각각의 권한 허가 상태, 에러의 발생에 따른 앱의 행동을 정의할 수 있다. requestAuthorization 메서드는 첫 실행 시에 유저의 응답을 저장해 놓고, 다음 번에 다시 호출되어도 알림 권한에 대한 Alert를 띄우지 않는다.

func requestAuthorization(options: [UNAuthorizationOptions] = [], 
        completionHandler: @escaping ([Bool], [Error]?) -> [Void])
image
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        UNUserNotificationCenter.current().delegate = self

        let authorizationOptions = UNAuthorizationOptions(arrayLiteral: [.alert, .badge, .sound])
        userNotificationCenter?.requestAuthorization(options: authorizationOptions, completionHandler: { granted, error in
            if let error = error {
                print("ERROR: " + error.localizedDescription)
            }
        })

        return true
    }
}

extension AppDelegate: UNUserNotificationCenterDelegate {

}

위와 같이 하면 유저는 앱의 첫 실행 시에 무조건 알림을 허용할지에 대해 결정해야 한다. 유저는 이 앱에서 어떠한 형태의 알림이 오는지에 대한 정보를 직관적으로 알 수 없기 때문에, 이를 보완하기 위해 Provisional Authorization 방식을 사용할 수 있다.

image

위와 같이 Provisional Authorization은 Alert가 아니라 알림의 형태로 권한을 요청하게 되고, 유저는 keep을 선택하여 이러한 알림을 계속 받겠다는 의사를 앱에 전달할 수 있다. Provisional Authorization은 소리, 배너와 같은 형태로 유저를 방해하지 않으면서도 알림 센터에 흔적을 남기기 때문에, 유저는 충분히 고민한 이후에 의사를 결정할 수 있다.

provisional Authorization을 사용하는 방법은 단순히 아래와 같이 options를 수정하는 것으로 충분하다.

        let authorizationOptions = UNAuthorizationOptions(arrayLiteral: [.alert, .badge, .sound, .provisional])

Authorizations Status에 따라 알림 Customizing하기

위에서 requestAuthorization 메서드는 앱 실행 시에 한 번만 권한을 요청하기 때문에, 유저가 거절한 경우 다시 권한 허가를 유도할 수 없다. 이럴 때 유저의 알림 권한 허가를 유도하는 방법으로 getNotificationSettings() 메서드가 있다. 이 메서드는 알림 권한이 없는 경우에 대한 handling을 할 수 있게 해줄 뿐만 아니라, .authorized나 .prvisional 같은 유저의 authorization status에 따라 알림을 커스터마이징 할 수 있게 해준다.

center.getNotificationSettings { settings in
    guard (settings.authorizationStatus == .authorized) ||
          (settings.authorizationStatus == .provisional) else { return }

    if settings.alertSetting == .enabled {
        // 알림 세팅이 허용된 경우 알림도 함께 표시
    } else {
        // 그렇지 않은 경우 badge와 sound만 표시
    }
}

알림을 보내기 전 또는 알림이 왔을 때의 행동 정의하기

extension AppDelegate: UNUserNotificationCenterDelegate {
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        // 보내기 전에 노티피케이션을 핸들링할 수 있다.
        completionHandler([.banner, .list, .badge, .sound])
    }

    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        let targetScene = response.notification.request.content.userInfo["targetScene"]

        completionHandler()
    }
}

먼저 첫 번째 메서드는 알림이 나타나기 전에 호출되는 메서드로, 알림의 형태를 지정해 줄 수 있다. notification 객체에는 request가 포함되기 때문에 request의 종류에 따라 알림을 다르게 보낼 수 있다.

두 번째 메서드는 알림이 왔을 때 실행되는 메서드로, 알림의 결과에 따라 정보를 받아서 필요한 처리를 해줄 수 있다. 예를 들어 VC의 identifier를 userInfo를 통해 받는다면 이를 통해 화면전환을 구현해줄 수 있다.

로컬 알림 작성하기(UNNotificationRequest)

로컬 알림을 작성하는 방법 알림을 보내려면 UNNotificationRequest라는 객체를 작성하고, 이를 알림 센터에 추가해줘야 한다.

이 알림 요청서에는 3가지 요소가 있는데, 이를 customizing하여 원하는 알림을 만들 수 있다. identifier : 고유한 값인 UUID를 입력하는 것이 일반적이다. Content(UNMutableNotifciationContent) : 알림에 나타날 내용, 타이틀, 소리, 뱃지의 내용을 String으로 입력할 수 있다. Trigger(UNNotificationTrigger) : 센터에 등록된 알림이 어떤 시점에 trigger될 지에 대한 정보이다. Trigger는 정해진 시각 / 특정 시간 간격 / 위치에 대한 정보를 가지는 세 종류로 나뉜다.

정리하면, 로컬 알림은 미리 내용과 전달 시점 또는 조건이 정해진 알림을 센터에 등록해 놓고, 정해진 시각, 특정 시간 간격, 위치의 조건에 맞을 때 사용자에게 알림을 전달하는 기능이다.

위의 정보를 담은 request를 만든 다음에, 이를 UNNotifcationCenter에 add해주면 trigger 조건에 따라 사용자에게 알림이 전달된다.

UNUserNotificationCenter를 Extension하여 request 추가 메서드 작성하기

import Foundation
import UserNotifications

extension UNUserNotificationCenter {
    func addNotificationRequest(by date: DateComponents, id: String) {

        // content 만들기
        let content = UNMutableNotificationContent()
        content.title = "알림 제목입니다"
        content.body = "알림 바디입니다. 여기 내용이 들어갑니다."
        content.sound = .default
        content.badge = 1

        // trigger 만들기
        let trigger = UNCalendarNotificationTrigger(dateMatching: date, repeats: true)

        // request 만들기
        let request = UNNotificationRequest(identifier: id, content: content, trigger: trigger)

        self.add(request, withCompletionHandler: nil)
    }
}

위의 코드를 보면 DateComponent와 id를 받아서 request를 만들고, 이를 UNUserNotificationCenter에 add해주고 있는 모습을 확인할 수 있다. content에는 내용을 채우고, trigger에는 알림이 나타나는 시점과 identifier를 정의해준다. 그리고 이렇게 만든 request를 최종적으로 Center에 add해준다. 여기까지 하면 상기의 내용을 담고 있는 알림이 생성되어 center에 등록된다.

VC에서 알림을 등록하고 싶을 때에 request 메서드를 호출하기

private func addCalendarNoti() {

    let calendar = Calendar.current
    let newDate = calendar.date(byAdding: DateComponents(second: 5), to: .now)
    let components = calendar.dateComponents([.hour, .minute, .second], from: newDate!)
    UNUserNotificationCenter.current().addNotificationRequest(by: components, id: UUID().uuidString)
}

위 메서드에서는 현재 시간보다 5초 뒤의 시간 가진 dateComponents를 아까 만들어준 메서드에 전달하고 있다. 알림을 추가하고 싶을 때마다 호출해주면 된다.

image

알림 센터에 등록된 알림 리스트 확인하고 지우기

아래와 같은 방법으로 현재 알림 센터에 등록된 알림 리스트를 확인할 수 있다.

userNotificationCenter?.getPendingNotificationRequests(completionHandler: { requests in
    for request in requests {
        print(request.identifier)
    }
})

그리고 등록된 identifier를 파라미터로 받아서 동일한 알림을 지워주는 메서드도 존재한다.

func removePendingNotificationRequests(withIdentifiers identifiers: [[String]])
func removeAllPendingNotificationRequests()

첫 번째 메서드는 특정 알림을 지울 때, 두 번째 메서드는 모든 알림을 지울 때 사용할 수 있다.

유저가 앱을 켰을 때 badge를 제거해주기

SceneDelegate에는 Scene이 다시 활성화되면 실행되는 메서드가 존재한다. 아래의 메서드에 applicationBadgeNumber를 0으로 만들어주는 구문을 넣어주면 유저가 앱을 다시 켰을 때 badge가 사라지게 된다.

func sceneDidBecomeActive(_ scene: UIScene) {
    UIApplication.shared.applicationIconBadgeNumber = 0
}