SeSAC iOS 데뷔과정 2기

1012 새싹 68회차 정리 - Remote Notification

Lia's iOS 2022. 10. 12. 15:15
iOS 앱 개발자 데뷔 과정 68회차

 

 

 

 

#1. Remote Notification

- ViewController Transition : 푸시를 클릭했을 때 특정 화면으로 전환하기

특정 푸시를 클릭했을 때 특정 화면으로 전환하는 작업에 대해 알아보자.

화면 전환을 위해서는 SceneDelegate의 Window 객체를 통해 현재 사용자가 머물고 있는 앱 최상단 뷰컨트롤러가 무엇인지 파악해야 하고, 화면 계층에 맞게 화면 전환 코드를 구현해줘야 한다.

 

- 최상단 뷰컨트롤러를 판단해주는 메서드와 연산 프로퍼티 예시

extension UIViewController {
    
    // 연산 프로퍼티를 활용해 더 간단하게 사용할 수 있음 (선택사항)
    var topViewController: UIViewController? {
        return self.topViewController(currentViewController: self)
    }
    
    // 최상위 뷰컨트롤러를 판단해주는 메서드
    func topViewController(currentViewController: UIViewController) -> UIViewController {
        if let tabBarController = currentViewController as? UITabBarController, let selectedViewController = tabBarController.selectedViewController {
            return self.topViewController(currentViewController: selectedViewController)
        } else if let navigationController = currentViewController as? UINavigationController, let visibleViewController = navigationController.visibleViewController {
            return self.topViewController(currentViewController: visibleViewController)
        } else if let presentedViewController = currentViewController.presentedViewController {
            return self.topViewController(currentViewController: presentedViewController)
        } else {
            return currentViewController
        }
    }
}

 

- 최상단 뷰컨트롤러의 종류에 따라 다른 방법으로 화면 전환을 하는 코드 예시

response.notification.request.content.userInfo 의 정보를 활용하기도 하고, UserDefaults 또는 내부적으로 저장되어 있는 데이터를 활용하기도 한다.

    // 푸시 클릭: 호두과자 장바구니 담는 화면
    // 유저가 푸시를 클릭했을 때에만 수신 확인 가능
    // 특정 푸시를 클릭하면 특정 상세 화면으로 화면 전환
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        
        print("사용자가 푸시를 클릭했습니다")
        print(response.notification.request.content.body)
        print(response.notification.request.content.userInfo)

        guard let viewController = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.rootViewController?.topViewController else { return }
        
        print(viewController)
        
        if viewController is ViewController { // is as
            viewController.navigationController?.pushViewController(SettingViewController(), animated: true)
        } else if viewController is ProfileViewController {
            viewController.dismiss(animated: true)
        }
    }

 

 

 

- Foreground에서 특정 화면에 대해 푸시 받지 않기

Foreground에서도 푸시를 받을 수 있게 completionHandler를 설정한 기존의 코드에서, 사용자가 특정 화면에 머물고 있는 상태일 경우 푸시를 보여주지 않는 형태로 수정해보자.

조건문을 통해, completionHandler 메서드에서 Options을 전달하지 않는 형태로 구현할 수 있다.

    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        
        // Setting 화면에 있다면 포그라운드 푸시 띄우지 마라!
        guard let viewController = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.rootViewController?.topViewController else { return }
        
        if viewController is SettingViewController {
            completionHandler([])
        } else {
            // .banner, .list: iOS 14+
            completionHandler([.badge, .sound, .banner, .list])
        }
    }

 

 

 

- Method Swizzling

런타임 시점에 기존 메서드를 다른 메서드로 바꾸어 실행하는 것을 말하며, objective-c의 런타임 기능이다.

앱의 생명주기에 앱의 분석 기능을 통합하거나, 특정 클래스의 기능을 한번에 적용하고자 할 때 사용한다.

하지만 런타임 시점에 메서드가 바뀌기 때문에 iOS 버전에 따라 동작하지 않을 수도 있고, 런타임에서 메서드의 오류가 발생할 수 있다.

또는 Firebase 등과 같은 툴에서 이미 Method Swizzling을 하고 있을 수도 있기 때문에 미리 파악하는 것이 중요하다.

extension UIViewController {
    
    // AppDelegate에서 호출해, 모든 VC의 viewWillAppear를 변경
    // 타입 메서드 --> static도 사용 가능하며, class는 오버라이딩이 가능하다.
    class func swizzleMethod() {
        let origin = #selector(viewWillAppear)
        let change = #selector(changeViewWillAppear)
        
        guard let originMethod = class_getInstanceMethod(UIViewController.self, origin), let changeMethod = class_getInstanceMethod(UIViewController.self, change) else {
            print("함수를 찾을 수 없거나 오류 발생")
            return
        }
        
        method_exchangeImplementations(originMethod, changeMethod)
    }
    
    @objc func changeViewWillAppear() {
        print("Change ViewWillAppear SUCCEED")
    }
}