1012 새싹 68회차 정리 - Remote Notification
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")
}
}