관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] 2. DeepLink (딥 링크) - URL Scheme, Custom URL, Foreground, Background, Not Running, URLComponents 기초 개념 본문

iOS 응용 (swift)

[iOS - swift] 2. DeepLink (딥 링크) - URL Scheme, Custom URL, Foreground, Background, Not Running, URLComponents 기초 개념

jake-kim 2021. 10. 7. 22:42

1. DeepLink (딥 링크) - 앱 푸시, APNs (Apple Push Notification service ) 개념

2. DeepLink (딥 링크) - URL Scheme, URLComponents, Foreground, Background, Not Running 기초 개념

3. DeepLink (딥 링크) - FCM(Firebase Cloud Messaging) remote 푸시 사용 방법

4. DeepLink (딥 링크) -Dynamic Link (다이나믹 링크) 사용 방법 (Firebase, 공유하기 기능)

5. DeepLink ( 링크) - URL Scheme Dynamick Link 이용한  링크 처리 방법

cf) Push Notification 처리 관련 메소드 총 정리 글은 이 포스팅 글 참고

URL Scheme의 개념

  • URL(Uniform Resource Location)의 개념: 특정 리소스의 위치를 나타내는 값
  • Scheme의 의미: URL의 접두사
    • ex) 웹 URL의 scheme은 "http"

  • ex) 사용자가 정의한 deep link URL에서의 scheme은 "my-app"

Custom URL

ex) safari 검색창에서 tel://010-1111-1111 입력 후 엔터를 누르면, 전화번호 앱에서 present되는 화면 표출

  • 위와같이 xcode에서도 따로 URL을 만들어서 해당 앱만의 리소스를 참조할 수 있는 방법 제공이 가능
    • ex) 카메라 앱이 있을 때, 특정 이미지를 열고 싶은 경우, `myphotoapp:albumname?name="albumname"`

Custom URL 등록

  • Xcode에서 Push Notification 기능 활성화

Target > Signing & Capabilities > +Capability > Push Notification

 

  • URL scheme 등록: Target > Info > URL Types에서 추가

들어오는 URL 처리

  • SceneDelegate 파일이 존재하는 경우: scene(_:openURLContexts:) 사용
    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        guard let url = URLContexts.first?.url else { return }

        print(url)
    }
  • SceneDelegate 파일이 없고 AppDelegate파일만 존재하는 경우
    • application(_:open:options:) 사용
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {

}
  • Firebase Dynamic link와 FCM을 같이 쓰는 경우에는 위 메소드 호출이 안되므로, AppDelegate의 userNotificationCenter(_:didReceive:)에서 구현
let center = UNUserNotificationCenter.current()
center.delegate = self

...

extension AppDelegate: UNUserNotificationCenterDelegate {
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async {
        
    }
}
  • safari 앱에서 테스트: "my-app://name=jake" 입력 후 이동
    • 위 URLContexts.first?.url 값은 "my-app://navigation?name=jake"

Custom URL Scheme과 일치하는 앱으로 이동

  • URLComponents를 이용하여 URL을 파싱
    • URLComponents의 생성자에 url.absoulteStgring 전달
    • URLComponents에서는 queryItem프로퍼티에 접근 가능

    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        guard let url = URLContexts.first?.url else { return }

        // 해당 scheme과 host를 가지고 있는지 파악
        guard url.scheme == "my-app", url.host == "navigation" else { return }

        // 원하는 query parameter가 있는지 파악
        let urlString = url.absoluteString
        guard urlString.contains("name") else { return }

        let components = URLComponents(string: url.absoluteString
        ...
    }
}
  • URLComponents객체는 queryItems 객체에 접근 가능

  • [String]과 같이 문자열 배열로 만들어 지는 것
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    guard let url = URLContexts.first?.url else { return }

    // 해당 scheme과 host를 가지고 있는지 파악
    guard url.scheme == "my-app", url.host == "navigation" else { return }

    // 원하는 query parameter가 있는지 파악
    let urlString = url.absoluteString
    guard urlString.contains("name") else { return }

    let components = URLComponents(string: url.absoluteString)
    let urlQueryItems = components?.queryItems ?? [] // [name=jake]

    var dictionaryData = [String: String]()
    urlQueryItems.forEach { dictionaryData[$0.name] = $0.value }

    guard let name = dictionaryData["name"] else { return }

    print("이름 = \(name)")
}
  • 만약 아래처럼 딥링크를 적용하는 경우, "my-app://navigation?name=jake&age=20"
    • urlQueryItems:[name=jake, age=20]
    • 배열로 되어있고 각 item의 값은 $0.name, $0.value로 접근 가능
      • ex) "age": urlQueryItems[1].name,  "20": urlQueryItems[1].value

Foreground에서는 푸시가 표출 안되도록 설정 방법

  • Foreground 설정: AppDelegate의 UNUserNotificationCenterDelegate에 있는 userNotificationCenter(_:willPResent:withCompletionHandler)에서 설정
  • 해당 델리게이트를 사용하기 위해서 NUUserNotificationCenter.current()로 객체 생성 후 이 객체의 delegate = self로 설정
    // AppDelegate.swift
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        registerRemoteNotification()

        return true
    }

    private func registerRemoteNotification() {
        let center = UNUserNotificationCenter.current()
        center.delegate = self
        let options: UNAuthorizationOptions = [.alert, .sound, .badge]
        center.requestAuthorization(options: options) { granted, _ in

            guard granted else { return }
            DispatchQueue.main.async {
                /// APNs에 device token 등록 요청
                UIApplication.shared.registerForRemoteNotifications()
            }
        }
    }
  • Foreground에서는 url을 파싱하여 특정 url이면 푸시를 표출하지 않도록 설정
extension AppDelegate: UNUserNotificationCenterDelegate {

    /// Foreground
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        if isShowAtForeground(url: notification) {
            completionHandler([.alert, .badge, .sound])
        }
    }

    private func isShowAtForeground(url notification: UNNotification) -> Bool {
        let userInfo = (notification.request.content.userInfo as NSDictionary?) as? [String: Any]

        // Braze 솔루션으로 예시
        // braze uri인지 확인
        guard let brazeURI = userInfo?["ab_uri"] as? String,
              let urlComponents = URLComponents(string: brazeURI) else { return false }

        let urlQueryItems = urlComponents.queryItems ?? []
        var dictionaryData = [String: String]()
        urlQueryItems.forEach { dictionaryData[$0.name] = $0.value }

        // 이름이 jake만 foreground에서 표출
        let name = dictionaryData["name"]
        return name == "jake"
    }
}
  • 위와같이 설정해주면, name이 jake인 푸시가 들어올 경우 background 상태(앱이 화면이 안보이는 경우)와, Not Running(앱이 종료된 경우)에만 푸시가 표출

* 참고

- Foreground push: https://developer.apple.com/documentation/usernotifications/unusernotificationcenterdelegate/1649518-usernotificationcenter

- Query Items: https://developer.apple.com/documentation/foundation/urlcomponents/1779966-queryitems

- Defining a Custom URL Scheme for Your App: https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app

- URL Components: https://developer.apple.com/documentation/foundation/urlcomponents

 

Comments