iOS

Notification에 Action 버튼 추가하기

Phililip
728x90

안녕하세요.

 

이번에는 Notification에 버튼 추가하는 방법(즉, Actionable Notification)에 대해 알아볼게요.

 

 


 

 

# Overview

Actionable Notification이란 아래 이미지처럼, Notification에 버튼을 추가한 것을 말합니다.

 

 

일반적인 Notification을 클릭하면 앱이 자동으로 실행되죠?

 

그런데 Actionable Notification을 클릭하면 앱이 background에서 실행되고 background에서 푸시 클릭 이벤트가 동작하게 됩니다!

 

 

이렇게 Actionable Notification을 사용하려면, 아래 사항을 준수해야 해요.

  • 앱 launch time 때 하나 이상의 notification type을 선언
  • notification type에는 적절한 notification actions을 할당
  • 등록한 모든 actions들을 처리하는 Handler 구현
  • Notification 발송 시 notification payload에 category identifier 추가

 

하나하나 알아볼게요 ^^

 

 

 

# 2. Notification Actions와 Notification type 선언

Notification Action은 반드시 앱의 launch time 때 선언되어야 합니다.

 

그리고 Notification Action은 category와 action 객체의 조합으로 이루어져 있는데요,

 

 

 

UNNotificationCategory 객체는 notification type을 정의하고,

 

UNNotificationAction 객체는 각 type에 표시할 버튼들을 정의합니다.

 

 

 

UNNotificationCategory 객체는 고유한 ID를 가지고 있고 notification이 어떻게 동작할지에 대한 options를 가지고 있습니다.

 

(밑에서 다시 얘기를 할 거지만 notification payload에 반드시 Category의 ID를 포함시켜줘야 하니 잊지 말아 주세요!!)

 

 

 

하나 이상의 UNNotificationAction 객체를 category에 할당함으로써 버튼을 추가하는 것이죠.

 

이때도 마찬가지로 UNNotificationAction 객체는 고유한 ID를 가지고 있고, 버튼 동작에 대한 options를 가지고 있습니다.

 

 

 

예를 들어서, 2개의 버튼을 추가한다고 볼게요!!

// MARK 4
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // MARK 1 : Define the custom actions.
    let acceptAction = UNNotificationAction(identifier: "meeting.acceptAction", title: "수락", options: [])
    let declineAction = UNNotificationAction(identifier: "meeting.declineAction", title: "거절", options: [.destructive])

    // MARK 2: Define the notification type
    let meetingCategory = UNNotificationCategory(
        identifier: "meetingCategory",
        actions: [acceptAction, declineAction],
        intentIdentifiers: [],
        options: [])

    // MARK 3: Register the notification type.
    let notificationCenter = UNUserNotificationCenter.current()
    notificationCenter.setNotificationCategories([meetingCategory])
    
    return true
}

 

 

MARK 1에서는 Action 객체를 생성해줬어요.

// MARK 1 : Define the custom actions.
let acceptAction = UNNotificationAction(identifier: "meeting.acceptAction", title: "수락", options: [])
let declineAction = UNNotificationAction(identifier: "meeting.declineAction", title: "거절", options: [.destructive])

 

각 파라미터를 살펴보면,

 

identifier는 Action의 ID를 의미합니다.

 

그리고 title은 button title을 의미해요.

 

마지막으로 options는 버튼 동작에 대한 option을 설정해주는데, 총 3가지가 있어요.

 

 

authenticationRequired로 설정하면 디바이스가 잠금 해제된 상태일 때만 action이 동작하도록 해줍니다.

 

destructive로 설정하면 버튼이 빨간색으로 보이게 됩니다. 

 

foreground로 설정하면 버튼을 클릭했을 때 앱이 foreground로 올라오게 됩니다.

 

 

 

MARK 2에서는 category 객체를 만들었습니다.

// MARK 2: Define the notification type
let meetingCategory = UNNotificationCategory(
    identifier: "meetingCategory",
    actions: [acceptAction, declineAction],
    intentIdentifiers: [],
    options: [])

 

 

 

meetingCategory라는 ID를 지정해주고, 위에서 만든 Action을 등록해줬어요.

 

 

 

MARK 3에서는 위에서 만든 category를 NotificationCenter에 등록해줬습니다.

// MARK 3: Register the notification type.
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.setNotificationCategories([meetingCategory])

 

 

 

그리고 notification type은 앱의 launch time 때 등록되어야 한다고 했죠??

 

그래서 MARK 4를 보시면, application(_:didFinishLaunchingWithOptions:)에서 호출하고 있는 것을 볼 수 있어요.

// MARK 4
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // MARK 1 : Define the custom actions.
    ...

    // MARK 2: Define the notification type
    ...

    // MARK 3: Register the notification type.
    ...
    
    return true
}

 

 

 

# 3. Notification Payload에 Category ID 추가

위에서 Notification Payload에 Category ID를 꼭!! 추가해야 한다고 했죠??

{
    "aps" : {
        "alert" : {
            "title" : "미팅 초대",
            "body" : "일시 : 2022/04/05 오후 2시",
        },
        "category" : "meetingCategory"    ✅
    }
}

 

aps.category 필드에 UNNotificationCategory 객체의 identifier 값과 동일하게 추가해주세요.

 

 

이렇게 해서 푸시를 발송해보면,

 

 

 

버튼이 잘 나오는 것을 볼 수 있어요!!! 👍 👍 👍

 

이젠 저 버튼을 클릭했을 때의 동작을 구현해볼게요.

 

# 4. 선택된 Action 핸들링

Action이 선택되면, system은 앱을 background에서 실행시킨 뒤, userNotificationCenter(_:didReceive:withCompletionHandler:) method를 호출시킵니다.

 

 

각 Action에 대해서 따로 처리하고 싶은데 어떻게 해야 함??? 🤔

 

Notification 메시지가 클릭되면 userNotificationCenter(_:didReceive:withCompletionHandler:) method가 호출이 되면서 response도 같이 내려와요.

 

이때, response.actionIdentifier 프로퍼티에는 Action의 ID 값이 설정되기 때문에, 이를 가지고 아래처럼 별도로 처리를 해주면 됩니다.

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    switch response.actionIdentifier {
    case "meeting.acceptAction":
        print("미팅 수락!")
    case "meeting.declineAction":
        print("미팅 거절!")
    default:
        break
    }

    completionHandler()
}

(마지막에는 꼭 completionHandler()를 호출해주세요!!)

 

 

한번 테스트해볼까요???

 

 

버튼을 클릭했을 때 앱이 위로 올라오지는 않았지만, 푸시 클릭 이벤트가 제대로 들어왔고, 동작도 버튼 별로 잘 동작하는 것을 볼 수 있어요!!! 👍 👍 

 

 

 

# 5. TextInput Action 추가

UNTextInputNotificationAction을 사용하면, Notification Action에서 사용자로부터 text 입력을 받을 수 있어요.

 

위에서 설명한 UNNotificationAction 하고 동일하니 설명은 생략할게요ㅠㅠ

 

 

 

직접 TextInput Action을 추가해볼게요.

 

UNTextInputNotificationAction 객체를 생성해서 Category에 등록해주고

 

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Define the custom actions.
    let acceptAction = UNNotificationAction(identifier: "meeting.acceptAction", title: "수락", options: [])
    let declineAction = UNNotificationAction(identifier: "meeting.declineAction", title: "거절", options: [.destructive])
    let reasonForDeclineInputAction =  UNTextInputNotificationAction(    ✅
                identifier: "meeting.reasonForDeclineInputAction",
                title: "거절 사유",
                options: [.destructive, .foreground])

    // Define the notification type
    let meetingCategory = UNNotificationCategory(    ✅
        identifier: "meetingCategory",
        actions: [acceptAction, declineAction, reasonForDeclineInputAction],
        intentIdentifiers: [],
        options: [])

    // Register the notification type.
    let notificationCenter = UNUserNotificationCenter.current()
    notificationCenter.setNotificationCategories([meetingCategory])

    notificationCenter.delegate = self
    notificationCenter.requestAuthorization(options: [.alert, .sound, .badge]) { (_, _) in }
    return true
}

 

 

 

userNotificationCenter(_:didReceive:withCompletionHandler:) method에서 handler를 등록해주면 끝입니다!

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    switch response.actionIdentifier {
    case "meeting.acceptAction":
        print("미팅 수락!")
    case "meeting.declineAction":
        print("미팅 거절!")
    case "meeting.reasonForDeclineInputAction":    ✅
        if let userInput = (response as? UNTextInputNotificationResponse)?.userText {
            print("미팅 거절 사유 => \(userInput)")
        }
    default:
        break
    }

    completionHandler()
}

 

 

확인해볼까요??

 

(원래는 앱이 background에 머물고 있지만, foreground option을 줬기 때문에 앱이 foreground로 올라온 것입니다!!)

 

 

생각보다 쉽죠?? 😁 😁 😁

 

 

 

 

## 참고

- https://developer.apple.com/documentation/usernotifications/declaring_your_actionable_notification_types

 

Apple Developer Documentation

 

developer.apple.com

 


 

이번 글은 여기서 마무리.

 

 

 

반응형

'iOS' 카테고리의 다른 글

Logger, OSLogPrivacy  (0) 2022.05.02
[오픈소스] Inject  (0) 2022.04.12
[오픈소스] Bagbutik  (0) 2022.03.28
mailto scheme과 기본 메일 앱 설정  (0) 2022.03.23
TabulaData framework  (0) 2022.03.09