iOS) 앱 내에서 Firebase 의 Dynamic Link(동적 링크) 생성하기

6 minute read

❓왜 앱 내에서 동적 링크를 생성할까요?

  • Firebase console 에서 만드는 동적 링크는 필요한 파라미터들로 웹에서 최초에 생성하기 때문에 전체 이벤트와 같은 유동적이지 않은 용도로 사용된다.
  • iOS 에서 FirebaseDynamicLinks 프레임워크로 링크를 만들면 특정 아이템에 대한 유동적인 링크를 생성할 수 있다.(앱 미설치 시 앱스토어로 보낼 수도 있는 등의 기능도 동일하게 추가할 수 있다.)

❓무엇을 구현하고 싶은가요?

  • 일반 카메라로 QR Code 인식 시 앱이 있다면 앱으로 연결하고 싶어요.
  • 앱이 없다면 앱스토어로 연결하고 싶어요.
  • 특정 아이템에 대해 유동적으로 링크를 생성하고 싶어요. 이를 통해 앱을 실행했을 때 특정 뷰를 보여주고 싶어요.

1️⃣ 프로젝트 세팅

Swift Package Manager

https://github.com/firebase/firebase-ios-sdk

CocoaPod install

pod 'FirebaseDynamicLinks'
  • 우선, Firebase console 에서 프로젝트를 생성해서 iOS 앱을 등록해주어야 합니다.

  • 그후에, Firebase console 에서 Dynamic LInks 섹션을 선택해서 URL 프리픽스를 생성해줍니다.

2

  • 원하는 도메인을 작성하면 추천 도메인을 설정할 수 있습니다.

3

AppDelegate 에서 FirebaseApp 초기화

import Firebase 
    
// ...
    
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    FirebaseApp.configure()

    // ...
    
    return true
}
  • 특정 파라미터를 가지는 동적 링크를 만들어보겠습니다.
  • 동적 링크를 만들려면 DynamicLinkComponents 객체를 만들고, 객체의 해당 속성을 설정하여 동적 링크 파라미터를 지정할 수 있습니다. 그런 다음 url 프로퍼티에서 long link 를 가져오거나, shorten() 메서드를 호출해서 short link 를 가져올 수 있습니다.
  • 다음은 문서에서 예시로 보여주는 코드입니다. 살펴본 후에 프로젝트에 적용해보겠습니다.
[Create Dynamic Links on iOS Firebase Dynamic Links](https://firebase.google.com/docs/dynamic-links/ios/create#use-the-ios-builder-api)
guard let link = URL(string: "https://www.example.com/my-page") else { return }
let dynamicLinksDomainURIPrefix = "https://example.com/link"
let linkBuilder = DynamicLinkComponents(link: link, domainURIPrefix: dynamicLinksDomainURIPRefix)
linkBuilder.iOSParameters = DynamicLinkIOSParameters(bundleID: "com.example.ios")
linkBuilder.androidParameters = DynamicLinkAndroidParameters(packageName: "com.example.android")

guard let longDynamicLink = linkBuilder.url else { return }
print("The long URL is: \(longDynamicLink)")

// short link
linkBuilder.shorten() { url, warnings, error in
  guard let url = url, error != nil else { return }
  print("The short URL is: \(url)")
}

기본적으로 짧은 동적 링크는 유효한 동적 링크를 추측할 수 있는 가능성이 낮은 17자의 link suffixes 로 이루어집니다.

짧은 동적 링크를 추측해도 문제가 없는 경우에 고유한 suffix 가 필요하다면 생성할 수도 있습니다. 이는 DynamicLinkComponentsOptions 로 설정할 수 있습니다.

linkBuilder.options = DynamicLinkComponentsOptions()
linkBuilder.options.pathLength = .short
linkBuilder.shorten() { url, warnings, error in
  guard let url = url, error != nil else { return }
  print("The short URL is: \(url)")
}

// ✅ short link 만 사용하게되면 다음과 같이 실행할때마다 suffix 가 바뀝니다. 
// https://(...).page.link/xZfXTYg6WxXrzu8X8
// https://(...).page.link/Z3qzVxWzNfpwuCwq5

// ✅ DynamicLinkComponentsOptions 로 설정하면 고유한 suffix 가 생성됩니다.
// https://(...).page.link/4Yif

아래의 문서를 통해서 원하는 속성을 추가할 수 있습니다.

[Create Dynamic Links on iOS Firebase Dynamic Links](https://firebase.google.com/docs/dynamic-links/ios/create#use-the-ios-builder-api)

프로젝트에서 필요로 한 내용은 다음과 같습니다.

  • 앱 미설치 시 앱스토어로 연결할 수 있어야 한다.
  • 앱 설치 시 특정 파라미터 값을 앱에 전달할 수 있어야 한다.
  • 소셜 메타데이터는 건너띄기. (아래의 속성으로 app preview page 를 건너띄고 app 또는 app store 로 리다이렉트 될 수 있습니다.)

4

다음과 같이 속성을 추가하겠습니다.

// QR Code 이미지 생성하는 메서드.
private func setQRImage() {
    let frame = CGRect(origin: .zero, size: qrImage.frame.size)
    let qrcode = QRCodeView(frame: frame)
    generateDynamicLink(with: cardDataModel?.cardID ?? "") { dynamicLink in
    
        // 🚨
        // 짧은 동적 링크를 만들때 네트워크 요청으로 인해 completion handler 를 사용하고 있다.
        // 이 작업이 끝나기를 기다린 후에 짧은 동적 링크를 사용할 수 있기 때문에,
        // @escaping closure 를 사용하여 generateDynamicLink(with:) 메서드의 작업이 끝난 후에 QR Code 이미지 생성 작업을 하도록 하였다.

        // 👉 QRCodeView 커스텀 클래스의 QR Code 이미지를 만드는 커스텀 메서드이다.
        qrcode.generateCode(dynamicLink)
        self.qrImage.addSubview(qrcode)
    }
}

private func generateDynamicLink(with cardID: String, completion: @escaping ((String) -> Void)) {

    // ✅ 링크에 cardID 를 파라미터로 추가. -> SceneDelegate 에서 cardID 를 받아와서 사용.
    guard let link = URL(string: "https://www.example.com/my-page" + "/?id=" + cardID),
          let bundleID = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String else { return }
    let domainURLPrefix = "https://(...).page.link"
    
    // ✅ Dynamic Link parameter 설정.
    
    // link : 앱이 열게될 링크. 데스크탑 웹 브라우저 환경에서 동적링크를 열게되면 해당 링크가 열립니다.
    // domainURLPrefix : Firebase 콘솔에서 설정한 Dynamic Link URL prefix 이다.
    let linkBuilder = DynamicLinkComponents(link: link, domainURIPrefix: domainURLPrefix)
    
    linkBuilder?.iOSParameters = DynamicLinkIOSParameters(bundleID: bundleID)
    
    // ✅ Apple Connect Store 에서 확인할 수 있는 ID 이다. 앱이 설치되지 않은 경우에 사용자를 App Store 로 보내는데 사용한다.
    linkBuilder?.iOSParameters?.appStoreID = "..."
    
    // ✅ true 로 설정하여 app preview page 를 건너띄고 app 또는 app store 로 리다이렉트.
    linkBuilder?.navigationInfoParameters = DynamicLinkNavigationInfoParameters()
    linkBuilder?.navigationInfoParameters?.isForcedRedirectEnabled = true
    
    var dynamicLink: String = ""
    
    // ✅ 고유한 suffix 생성.
    linkBuilder?.options = DynamicLinkComponentsOptions()
    linkBuilder?.options?.pathLength = .short
    
    // ✅ 짧은 DynamicLink 로 변경.
    // 짧은 동적 링크를 만들기 위해서는 네트워크 요청이 필요하므로 링크를 바로 반환하는 것이 아니라 요청이 완료된 후에 호출되는 completion handler 를 사용한다.
    linkBuilder?.shorten { url, warnings, error in
        if let url {
            dynamicLink = "\(url)"
        }

        // ✅ 현재 메서드의 작업이 끝난 후 즉, 짧은 동적 링크가 만들어진 후에 completion handler 실행.
        completion(dynamicLink)
    }
    
// ✅ 긴 동적 링크를 다음과 같이 그대로 사용할 수 있다. 하지만, 프로젝트에서는 QR Code 이미지가 너무 복잡해져서 보기 좋지 않았다. 그래서 짧은 동적 코드로 생성하였다.
//        if let url = linkBuilder?.url {
//            dynamicLink = "\(url)"
//        }
}

Associated Domains 추가

  • target 에 Associated Domains 를 추가합니다.

5

  • url prefix 중 https:// 를 제외한 도메인 값을 추가합니다.
applinks:(...).page.link

6

Identifiers 에서 해당 App ID 에 Associated Domains 추가

  • Associated Domains 을 Xcode 에서 추가하게되면 Signing & Capabilities tab 이 다음과 같은 상태가 됩니다.

  • Apple Developer 에서 App ID 를 생성하고, Associated Domains Capabilites 를 추가해주면 됩니다.

8

FirebaseDynamicLinksCustomDomains key(선택사항)

Firebase console 에서 만든 URL Prefix 가 page.link 하위 도메인 대신 자체 도메인을 사용한다면 info.plist 에서 FirebaseDynamicLinksCustomDomains **라는 키를 만들고 앱의 동적 링크 URL prefix 를 설정해주어야 합니다.

진행한 프로젝트에서는 page.link 를 사용했기 때문에 별도로 설정해주지 않았습니다.

SceneDelegate 설정

아래의 문서를 확인하면 AppDelegate 에서 설정할 수 있습니다.

[iOS에서 동적 링크 수신 Firebase Dynamic Links](https://firebase.google.com/docs/dynamic-links/ios/receive?hl=ko)
  • scene(_:continue:) 메서드에서 동적 링크 수신 처리합니다.
import FirebaseDynamicLinks

func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
    if let url = userActivity.webpageURL {
        let handled = DynamicLinks.dynamicLinks().handleUniversalLink(url) { dynamicLink, error in
            // 👉 동적링크에서 파라미터를 다루는 함수. 아래에서 살펴보겠습니다.
            if let cardID = self.handleDynamicLink(dynamicLink) {
 
                // TODO: - user defaults 로 cardID 저장하고, 홈 뷰에서 존재 유무로 명함 조회. 조회 시 user defaults 삭제.
                    
            }
        }
    }
}
  • 앱이 종료된 상태(not running)에서 동적 링크를 통해 앱이 켜지는 경우도 핸들링 해주기 위해 scene(_:willConnectTo:options:) 함수도 수정하였습니다.
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let _ = (scene as? UIWindowScene) else { return }

    if let userActivity = connectionOptions.userActivities.first {
        self.scene(scene, continue: userActivity)
    }
}
  • 동적 링크에서 cardID 파라미터를 받아서 반환하는 함수입니다.
// MARK: - Extensions

extension SceneDelegate {
    private func handleDynamicLink(_ dynamicLink: DynamicLink?) -> String? {
        // 앱으로 전달되는 url 을 얻을 수 있다.(딥링크)
        guard let dynamicLink = dynamicLink,
              let link = dynamicLink.url else { return nil }
        
        // resolvingAgainstBaseURL : URL 구문을 분석하기 전에 URl 에 대해 확인하는지 여부를 제어.
        // true 이고, url parameter 에 상대적인 URl 이 포함되어 있다면 absoluteURL 메서드를 호출해서 original URl 에 대해서 확인합니다. 그렇지 않으면, 문자열 부분이 자체적으로 사용됩니다.
        let queryItems = URLComponents(url: link, resolvingAgainstBaseURL: true)?.queryItems
        
        let cardID = queryItems?.filter { $0.name == "cardID" }.first?.value
        
        return cardID
    }
}

결과

  • 앱 설치 시에 앱으로 연결.

  • 앱 미설 치에 앱스토어로 연결.

🚨 트러블 슈팅 - 동적 링크를 통해 앱이 열리지 않아요!

  • 문제 : 앱이 설치되어 있지만, 앱이 열리지 않고 App Store 로 연결해주었습니다.

Firebase 에서 동적 링크를 잘못 만들어준다고 판단하였습니다. 그래서 동적 링크가 유효한지 확인해보았습니다.

  • 다음의 URL 을 열어 iOS 앱에서 동적 링크를 사용하도록 Firebase 프로젝트가 올바르게 구성되었는지 확인할 수 있습니다.
https://your_dynamic_links_domain/apple-app-site-association

앱이 정상적으로 연결된 경우 apple-app-site-association 파일에는 앱의 앱 ID 접두사 및 번들 ID에 대한 참조가 포함됩니다. 예를 들어:

{"applinks":{"apps":[],"details":[{"appID":"1234567890.com.example.ios","paths":["NOT /_/*","/*"]}]}}

하지만, 확인해보니 다음과 같이 details 이 비어있었습니다.

{"applinks":{"apps":[],"details":[]}}
출처: [Receive Dynamic Links on iOS Firebase Dynamic Links](https://firebase.google.com/docs/dynamic-links/ios/receive#set-up-firebase-and-the-dynamic-links-sdk)

💫 해결:

그래서 Firebase iOS 앱의 설정을 다시 확인했습니다.

  • iOS 앱의 설정을 확인해보니 팀 ID 를 입력해주지 않았습니다.

11

팀 ID 를 설정해주고 아래의 URL 로 확인해보니 details 가 구성된 것을 확인할 수 있었습니다.(바로는 적용이 안되고 조금 지나야 적용이 되었었습니다.)

https://your_dynamic_links_domain/apple-app-site-association

동적 링크로 유효한 URL 임을 확인하였고, 동적 링크를 사용해보니 유효하게 작동하였습니다.

동적 링크를 적용하기 전에 먼저, 동적 링크가 유효한지 확인하는 단계가 필요할 것 같습니다.

참고:

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

Firebase DynamicLink에서 데이터 받기 - 뀔뀔(swieeft)의 개발새발기

iOS - 앱에서 Firebase Dynamic Link 생성하고 수신하기

[iOS에서 동적 링크 수신 Firebase Dynamic Links](https://firebase.google.com/docs/dynamic-links/ios/receive?hl=ko)
[Create Dynamic Links on iOS Firebase Dynamic Links](https://firebase.google.com/docs/dynamic-links/ios/create#use-the-ios-builder-api)

Categories:

Updated: