 Apple Lover Developer & Artist

영속적인 디자인에 현대의 공감을 채워넣는 공방입니다

 Apple/iOS Dev Challenges

[Challenge] 🛠️ iOS 앱 설계 퓨전 레시피 14부 - Local Notification Action

singularis7 2023. 4. 5. 13:40
반응형

Overview

Local Notification 이벤트를 다루어본다. 알림 이벤트가 발생했을 때 액션을 붙여보고 앱이 액션에 따라 응답할 수 있도록 프로그래밍해 본다.

knowledge

Handling Event

서로 다른 객체가 메시지를 주고받아야 한다. 한걸음 더 들어가면 Local Notification 이벤트를 수신할 수 있어야 한다. 이벤트를 앱에 전달할 수 있어야 한다. 그리고 앱이 이벤트에 응답할 수 있어야 한다. 한 단계씩 뜯어보며 코드를 구현할 수 있는 기반을 다져본다.

Local Notification 이벤트를 누가 수신해서 어떻게 앱에 전달해 줄지 고민하게 된다. Notification과 연관된 이벤트는 UNUserNotificationCenter 객체가 처리할 수 있다. 앱 객체에 이벤트를 전달할 수 있는 방법도 있다. Delegate 프로토콜을 활용해 두 객체 간 커뮤니케이션을 할 수 있다. Delegate 메서드를 통해 앱 객체가 Notification 이벤트를 전달받고 알맞은 액션 코드가 실행될 수 있도록 코딩하여 목표를 달성할 수 있을 것이다.

Actionable Notifications

애플리케이션을 실행하지 않고 사용자가 앱의 알림에 응답할 수 있도록 Actionable notification을 구현할 수 있다. Actionable notification은 사용자가 알림에 응답하여 연관된 작업을 처리할 수 있도록 쉽고 빠른 수단을 제공해 준다.

사용자 인터페이스를 살펴본다. Actionable notification은 커스텀 액션 버튼을 보여준다. 사용자가 특정 액션 버튼을 탭 하면, 각 버튼은 Notification을 dismiss 시키고 select 이벤트를 앱에 전달하여 앱이 직접 이벤트를 핸들링할 수 있도록 한다. 사용자가 actionable notification의 액션을 볼 수 있으려면 알림을 깊게 누르거나 알림의 아래쪽 방향으로 드래그하는 방식으로 컨트롤하면 된다.

예를 들어 리마인더 앱을 생각해 본다. 어느 리마인더의 알림은 사용자에게 3가지 액션을 제공한다. mark is as completed, remind again in an hour, remind again the next day가 예시이다. 특정 액션들을 노출시킴으로써 앱은 사용자가 앱을 탐색할 필요 없이 알림에 쉽게 상호작용할 수 있도록 지원할 수 있다.

Category

Actionable notification을 구현하려면 notification category를 구현해야 한다. category는 notification의 자체 정의된 타입으로 앱이 전송할 수 있으며 시작 시점에 등록할 수 있다.

각 category와 연관된 것은 액션이다. 액션은 사용자가 해당 타입의 알림을 받았을 때 수행할 행위를 담는다. 각 카테고리는 10가지의 액션까지 가질 수 있다. 그러나 공간이 부족하면 시스템은 적게 보여줄 수 있다.

Actions

UNNotificationAction 인스턴스는 전달된 알림에 응답하여 앱이 수행할 수 있는 작업을 의미한다. 앱에서 작업할 수 있도록 알림 개별 타입을 위한 커스텀 액션을 선언할 수 있다. 액션 인스턴스 그 자체는 화면에 어떻게 보일 지에 대한 정보를 갖고 있다. 사용자가 해당 액션을 탭 하면 시스템이 액션의 식별자를 앱에 전달하여 이에 상응하는 작업을 수행할 수 있도록 돕는다.

Implement

Handling Event

앱이 이벤트를 수신하고 핸들링할 수 있어야 한다. UNUserNotificationCenterDelegate 프로토콜을 활용해 AppDelegate와 UserNotificationCenter 간의 위임 관계를 생성하여 이벤트를 핸들링할 수 있다.

// MARK: Handling User Notification Event
extension AppDelegate: UNUserNotificationCenterDelegate {

    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        // Handle notification here
        // ** MUST call completion handler when finished! **

        switch response.actionIdentifier {
        case "confirm":
            print("confirm category clicked")
        default:
            break
        }

        completionHandler()
    }

}

AppDelegate는 앱 프로세스와 동일한 생명주기를 지늬기에 이벤트를 핸들링하기 좋은 지점이다. didReceive 메서드를 구현함으로써 이벤트를 핸들링할 수 있게 되었다. 위 코드는 사용자가 confirm이라는 액션을 선택했을 때 시스템에서 핸들링하는 예시를 담고 있다. 모든 코드가 끝난 후에는 반드시 completionHandler를 호출해줘야 한다.

프로토콜을 채택하는 것 외에도 잊어버리면 안 되는 요소가 있다. UserNotificationCenter에 위임 객체로 AppDelegate 객체를 등록해 주는 것이다. 필자는 지난 포스팅에서 center의 제어를 controller를 통해 진행하고 있기에 다음과 같이 구현해 주었다.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.
  userNotificationCenterController.configure(with: self) userNotificationCenterController.setCategories([OrderCompleteNotificationCategory()])
    return true
}

configure 함수를 통해 위임 관계의 등록을 숨겨주었다. setCategories 메서드의 설명은 조금 뒤로 미뤄두겠다.

func configure(with object: UNUserNotificationCenterDelegate) {
    center.delegate = object
    authorizeIfNeeded()
}

Modeling

Local Notification에 category를 붙여주어 사용자 액션을 추가해 줄 수 있다. Local Notification과 category 정보를 구분하여 관리할 수 있도록 다음과 같이 추상화시켰다.

protocol NotificationCategory {

    var id: String { get }
    var actions: [UNNotificationAction] { get }
    var category: UNNotificationCategory { get }

}

extension NotificationCategory {

    var category: UNNotificationCategory {
        UNNotificationCategory(
            identifier: id,
            actions: actions,
            intentIdentifiers: []
        )
    }

}

id 프로퍼티는 category의 식별자를 의미한다. 식별자를 통해 notification을 해당 category 정보를 사용하여 actionable 하도록 만들 수 있다. actions 프로퍼티는 사용자가 선택할 수 있는 액션을 의미한다. 배열의 항목으로 displaying 방식을 지정하여 추가할 수 있다. 마지막은 프로토콜 기본 구현으로 들고 있는 category 프로퍼티이다. category 정보를 하나의 객체로 캡슐화시켜준다. notification을 actionable 하도록 옵셔널 하게 설정해 줄 수 있도록 notification 프로토콜을 설정해 주었다.

protocol NotificationRequest {

    var id: String { get }
    var category: NotificationCategory? { get }
    var content: UNNotificationContent { get }
    var trigger: UNNotificationTrigger { get }
    var request: UNNotificationRequest { get }

}

지난 포스팅에서 설명을 생량 한 category 프로퍼티가 이제야 이해되기 시작할 것이다. 어떤 notification을 actionable 하도록 만들어주려면 이 옵셔널 타입의 프로퍼티를 구현해 주면 된다. 또한 AppDelegate에서 설명을 생략했던 setCategories 메서드를 활용해 해당 카테고리의 액션을 앱이 응답할 수 있도록 구현할 수 있을 것이다.

func setCategories<Category: NotificationCategory>(_ categories: [Category]) {
    let categories = categories.map { $0.category }
    center.setNotificationCategories(Set(categories))
}

Usage

알림이 오면 사용자가 확인하는 액션을 탭 하고 시스템이 이를 처리할 수 있도록 만들고 싶다. 알림 이벤트는 OrderApp에서 주문 이후 음식의 준비가 다 되면 알림이 울리는 경우를 생각해 본다.

struct OrderCompleteNotificationCategory: NotificationCategory {

    var id: String {
        "OrderConfirmationCategory"
    }

    var actions: [UNNotificationAction] {
        [UNNotificationAction(identifier: "confirm", title: "Confirm")]
    }

}

확인 한 가지의 액션만 보여주는 알림 카테고리를 구현하였다. 이를 준비완료 알림과 엮어 actionable 하도록 만들어주면 다음과 같다.

struct OrderCompleteNotificationRequest: NotificationRequest {

    // ...

    var category: NotificationCategory? {
        OrderCompleteNotificationCategory()
    }

    var content: UNNotificationContent {
        let content = UNMutableNotificationContent()

        content.sound = .default
        content.title = title
        content.body = body

        if let categoryId = category?.id {
            content.categoryIdentifier = categoryId
        }

        return content
    }

    // ...

}

category 옵셔널 타입의 프로퍼티에 인스턴스 하나 만들어주고 content 인스턴스를 구성할 때 identifier만 등록해 주면 된다.

시스템이 해당 카테고리의 액션을 핸들링하기 위해 UserNotificationCenter에 등록해주어야 한다.

userNotificationCenterController.setCategories([
  OrderCompleteNotificationCategory()
])

가장 처음에 보았던 AppDelegate의 userNotificationCenter delegate 프로토콜의 didRecieve 메서드에서 actionIdentifier를 통해 액션을 핸들링할 수 있게 되었다.

이와 비슷한 방법으로 카카오톡 답장하기처럼 인스턴트 리플라이 기능을 구현할 수 도 있고 앱이 실행되어 있는 상태로 알림을 핸들링할 수도 있다.

마무리

ocal Notification 이벤트를 다루어보았다. 알림 이벤트가 발생했을 때 액션을 붙여보고 앱이 액션에 따라 응답할 수 있도록 프로그래밍해 보았다.

다음 시간에는 앱이 종료되어도 메모리상에서 유지된 것처럼 사용자를 속일 수 있는 state restoration에 관하여 다뤄볼 것이다. 이 편이 쉽지 않을 것 같아 걱정되지만 만반의 준비를 해서 포스팅하도록 하겠다! 🤣🤣

반응형