목차
개발환경
Xcode는 버전 12.2이고 macOS는 Catalina입니다.
Firebase Dynamic Link(동적 링크)란?
Firebase Dynamic Link는 해당 링크를 클릭시 해당 앱이 설치되어 있는 경우 앱을 실행하고 설치되어 있지 않는 경우 앱스토어로 이동하여 앱 설치하게 도와줍니다. 반면 custom scheme은 앱이 설치되어 있는 경우에만 앱을 실행할 수 있습니다.
Firebase 콘솔에서 앱을 추가
App Store ID와 팀 ID를 추가합니다. 이것을 추가해야 Firebase Dynamic Link가 제대로 작동합니다.
Firebase console에서 동적 링크 생성
왼쪽 메뉴에서 Dynamic Links로 들어가 URL 프리픽스를 추가합니다.
도메인을 입력합니다. 개인적으로 소유하는 도메인이 없다면 구글이 제공하는 도메인을 입력합니다. *.page.link 형식인데 * 부분은 임의로 지정할 수 있습니다.
새 동적 링크를 클릭합니다.
URL 프리픽스 다음부분은 그냥 놔두거나 아니면 적절한 단어로 수정합니다. 저는 openEvent로 변경하겠습니다.
다음 딥 링크 URL을 입력합니다. 여기에 매개변수를 만들어 값을 앱에 전달할 수 있습니다. 이곳에 지정하는 url은 실제로 웹상에 존재하는 도메인이 아니어도 괜찮습니다.
iOS 링크 동작 정의에서는 iOS앱에서 딥 링크 열기를 선택한 다음 해당 앱을 지정합니다.
Android용 링크 동작 정의는 default값을 사용하겠습니다.
마지막으로 만들기 버튼을 클릭하면 링크가 생성됩니다.
생성된 url은 복사하여 테스트할 수 있습니다. 예를 들어 Gmail로 자신에게 이메일을 보낸 다음 폰의 Gmail앱에서 해당 링크를 클릭해서 테스트할 수 있습니다.
앱에서 동적으로 링크를 생성
앱에서 동적으로 링크를 생성하는 예제 코드입니다.
let link = URL(string: "https://plusapps.com/?eventId=1")
let referralLink = DynamicLinkComponents(link: link!, domainURIPrefix: "https://firebasedynamiclinktest2.page.link")
// iOS 설정
referralLink?.iOSParameters = DynamicLinkIOSParameters(bundleID: "com.plusapps.FirebaseDynamicLinkTest2")
referralLink?.iOSParameters?.minimumAppVersion = "1.0.1"
referralLink?.iOSParameters?.appStoreID = "1440705745" //나중에 수정하세요
// Android 설정
referralLink?.androidParameters = DynamicLinkAndroidParameters(packageName: "com.plusapps.firebasedynamiclinktest2")
referralLink?.androidParameters?.minimumVersion = 811
// 단축 URL 생성
referralLink?.shorten { (shortURL, warnings, error) in
if let error = error {
print(error.localizedDescription)
return
}
print(shortURL)
//SMS 전송
guard MFMessageComposeViewController.canSendText() else {
print("SMS services are not available")
return
}
let composeViewController = MFMessageComposeViewController()
//composeViewController.messageComposeDelegate = self
composeViewController.recipients = ["01033555940"]
composeViewController.body = shortURL?.absoluteString ?? ""
self.present(composeViewController, animated: true, completion: nil)
}
앱에서 Firebase Dynamic Link를 수신하기 위한 설정
Associated Domains에 "applinks:<URL 프리픽스>"를 입력합니다. Associated Domains가 보이지 않으면 왼쪽 상단 "+ Capability"를 클릭한 다음 "Associated Domains"를 더블클릭합니다.
예전에는 AppDelegate에서 동적 링크를 수신했는데 이번에 변경되었는지 SceneDelegate에서만 수신 가능합니다. 이와 관련해서 잘 아시는 분은 댓글 부탁드립니다. SceneDelegate가 없는 프로젝트의 경우 SceneDelegate를 추가해야 합니다. 추가 방법은 다음 섹션에 설명하겠습니다.
SceneDelegate에 아래 동적 링크를 수신하기 위한 코드를 추가하세요.
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
//앱이 종료된 경우(폰에서 해당 앱을 swipe up) 동적 링크를 클릭시 콜백 함수가 호출되지 않는 이슈 수정
for userActivity in connectionOptions.userActivities {
if let incomingURL = userActivity.webpageURL{
print("Incoming URL is \(incomingURL)")
let linkHandled = DynamicLinks.dynamicLinks().handleUniversalLink(incomingURL) { (dynamicLink, error) in
guard error == nil else{
print("Found an error \(error!.localizedDescription)")
return
}
if dynamicLink == dynamicLink{
self.handelIncomingDynamicLink(_dynamicLink: dynamicLink!)
}
}
print(linkHandled)
break
}
}
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard let _ = (scene as? UIWindowScene) else { return }
}
...
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
// 동적 링크를 수신하기 위한 코드
func handelIncomingDynamicLink(_dynamicLink: DynamicLink) {
guard let url = _dynamicLink.url else {
print("That is weird. my dynamic link object has no url")
return
}
print("plusapps SceneDelegate your incoming link perameter is \(url.absoluteString)")
_dynamicLink.matchType
//앱이 처음 실행될 때에는 Notification이 등록되지 않아서 동적 링크처리를 못하므로
//Constants.firebaseDynamicLink를 사용하여 처리
Constants.firebaseDynamicLink = url.absoluteString
NotificationCenter.default.post(name: Notification.Name(rawValue: "clickFirebaseDynamicLink"), object: url.absoluteString)
}
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
if let incomingURL = userActivity.webpageURL{
print("Incoming URL is \(incomingURL)")
let linkHandled = DynamicLinks.dynamicLinks().handleUniversalLink(incomingURL) { (dynamicLink, error) in
guard error == nil else{
print("Found an error \(error!.localizedDescription)")
return
}
if dynamicLink == dynamicLink{
self.handelIncomingDynamicLink(_dynamicLink: dynamicLink!)
}
}
print(linkHandled)
}
}
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
if let url = URLContexts.first?.url{
print("url:- \(url)")
if let dynamicLink = DynamicLinks.dynamicLinks().dynamicLink(fromCustomSchemeURL: url){
self.handelIncomingDynamicLink(_dynamicLink: dynamicLink)
//return true
} else{
// maybe handel Google and firebase
print("False")
}
}
}
}
Xcode의 로그를 보면 동적 링크를 정상적으로 수신합니다. SceneDelegate에서 url과 매개변수 처리를 할 수 있습니다.
동적링크는 다음과 같이 수신 처리합니다.
override func viewDidLoad() {
super.viewDidLoad()
...
//20.02.05 박정규
//firebase dynamic link 수신 처리
handleFirebaseDynamicLink()
addNotificationObserver()
}
deinit {
removeNotificationObserver()
}
private func handleFirebaseDynamicLink() {
if let urlString = Constants.firebaseDynamicLink {
wkWebView.load(URLRequest(url: URL(string: urlString)!))
Constants.firebaseDynamicLink = nil
}
}
private func addNotificationObserver() {
NotificationCenter.default.addObserver(self, selector: #selector(onNotificationReceived(notification:)), name: Notification.Name(rawValue: "clickFirebaseDynamicLink"), object: nil)
}
private func removeNotificationObserver() {
NotificationCenter.default.removeObserver(self, name: Notification.Name(rawValue: "clickFirebaseDynamicLink"), object: nil)
}
@objc func onNotificationReceived(notification: Notification) {
if let urlString = notification.object as? String {
wkWebView.load(URLRequest(url: URL(string: urlString)!))
}
}
기존 프로젝트에 SceneDelegate를 추가하는 방법
info.plist파일을 "Source Code"로 엽니다.
"UIApplicationSceneManifest" 키를 추가합니다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
...
SceneDelegate를 생성하고 아래 코드를 붙여넣습니다.
//
// SceneDelegate.swift
// FirebaseDynamicLinkTest2
//
// Created by Jeonggyu Park on 2020/12/15.
//
import UIKit
import Firebase
import FirebaseDynamicLinks
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
//앱이 종료된 경우(폰에서 해당 앱을 swipe up) 동적 링크를 클릭시 콜백 함수가 호출되지 않는 이슈 수정
for userActivity in connectionOptions.userActivities {
if let incomingURL = userActivity.webpageURL{
print("Incoming URL is \(incomingURL)")
let linkHandled = DynamicLinks.dynamicLinks().handleUniversalLink(incomingURL) { (dynamicLink, error) in
guard error == nil else{
print("Found an error \(error!.localizedDescription)")
return
}
if dynamicLink == dynamicLink{
self.handelIncomingDynamicLink(_dynamicLink: dynamicLink!)
}
}
print(linkHandled)
break
}
}
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard let _ = (scene as? UIWindowScene) else { return }
}
func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
func sceneWillResignActive(_ scene: UIScene) {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
// 동적 링크 수신
func handelIncomingDynamicLink(_dynamicLink: DynamicLink) {
guard let url = _dynamicLink.url else {
print("That is weird. my dynamic link object has no url")
return
}
print("SceneDelegate your incoming link perameter is \(url.absoluteString)")
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false), let queryItems = components.queryItems else {
return
}
for queryItem in queryItems {
print("Parameter:- \(queryItem.name) has a value:- \(queryItem.value ?? "") ")
}
_dynamicLink.matchType
}
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
if let incomingURL = userActivity.webpageURL{
print("Incoming URL is \(incomingURL)")
let linkHandled = DynamicLinks.dynamicLinks().handleUniversalLink(incomingURL) { (dynamicLink, error) in
guard error == nil else{
print("Found an error \(error!.localizedDescription)")
return
}
if dynamicLink == dynamicLink{
self.handelIncomingDynamicLink(_dynamicLink: dynamicLink!)
}
}
print(linkHandled)
}
}
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
if let url = URLContexts.first?.url{
print("url:- \(url)")
if let dynamicLink = DynamicLinks.dynamicLinks().dynamicLink(fromCustomSchemeURL: url){
self.handelIncomingDynamicLink(_dynamicLink: dynamicLink)
//return true
} else{
// maybe handel Google and firebase
print("False")
}
}
}
}
AppDelegate에 다음 코드를 추가합니다.
func application(_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
이상입니다. 감사합니다.