1.登录苹果开发者账号添加和配置证书:
1.1没有证书的添加证书:
关于添加Certificates, Identifiers & Profiles网上很多教程,这里就不再赘述了。
1.2开启App项目的Associated Domains功能:
苹果开发者->Account->Overview->Certificates, Identifiers & Profiles->Identifiers->选中对应的App项目的Bundle Identifier的Identifiers->开启Associated Domains功能->新建该Identifiers最新的Profiles文件->下载该Profiles文件并在Mac上安装这个文件(双击文件安装)
2.Xcode中添加Associated Domains:
我的Xcode版本是11.3.1版本;如果没有Associated Domains需要添加该选项:

Associated Domains可以设置多个,格式是:applinks: + 服务器域名;Associated Domains必须已applinks:开头。

3.新建和配置后台服务器文件:
3.1新建文件:
文件名必须为apple-app-site-association的json格式文件,注意文件命不需要添加的后缀。
{
"applinks": {
"apps": [],
"details": [
{
"appID": "Z63D3JD9YJ.com.anniekids.baobaoyuan",
"paths": ["*", "/.well-known", "/.well-know/*", "/qq_conn/102057896"]
},
{
"appID": "com.anniekids.baobaoyuan",
"paths": ["*", "/.well-known", "/.well-know/*", "/qq_conn/102057896"]
},
{
"appID": "Z63D3JD9YJ",
"paths": ["*", "/.well-known", "/.well-know/*", "/qq_conn/102057896"]
}
]
}
}
3.2文件说明:
apps必须设置为[];
details值为一个json字典,每个应用程序一个字典。对于每个特定于应用程序的词典(上面就表示了同一个teamId下的4个不同的应用);字典中包含两个键值对:appID和paths;
appID将处理格式为的链接的应用程序的标识;命名规则:TeamID + . +Bundle Id;例如:V238977XUM.com.Demo.www
paths应用程序支持的网站各个部分,以路径字符串数组形式指定。只有这些指定的路径的链接,才能被app所处理, *符号写法代表了可识别域名下所有链接。
4.上传服务器:
上传 apple-app-site-association文件到域名的根目录或者.well-known子目录下;最新文档说明最好是在.well-known子目录下。
在浏览器中能打开
1.https://域名/apple-app-site-association
2.https://域名/.well-known/apple-app-site-association
3.https://app-site-association.cdn-apple.com/a/v1/域名
看到apple-app-site-association文件的内容,就可以在备忘录或者safari中点击相关链接打开应用,见步骤5.2。
5.测试链接:
以前,上传成功后可用苹果测试链接页,进行测试该设置是否生效,有效链接就可以进行下一步,进行跳转。最新的文档已测试链接已找不到不能使用了,且苹果对apple-app-site-association的下载和更新有了新的方法。

5.1那么如何确定苹果的CDN服务器已经对apple-app-site-association文件下载和更新?
用浏览器直接访问地址下述地址或者终端执行下述命令直接可以看到apple-app-site-association内容;表示苹果的CDN服务器已经下载或更新该文件。
// 直接浏览器访问
https://app-site-association.cdn-apple.com/a/v1/域名
// 例如访问淘宝在苹果CDN服务器上的apple-app-site-association文件内容
https://app-site-association.cdn-apple.com/a/v1/b.mashort.cn
// 终端执行命令
curl -v https://app-site-association.cdn-apple.com/a/v1/域名
需要注意的是苹果CDN服务器从所在域名的服务器更新或者添加apple-app-site-association文件是有周期的,只有在苹果的SDN更新或者添加改文件后,才能使用链接跳转应用。详情见苹果的文档。
5.2测试链接跳转
当苹果的CDN服务器已经更新下载apple-app-site-association文件后,就可以在备忘录或者safari浏览器中测试能否跳转应用了。
// "paths": ["*", "/.well-known", "/.well-know/*", "/qq_conn/102057896"]对应的跳转链接
// “*”表示直接输入域名就可以跳转,例如跳转淘宝
https://b.mashort.cn
// "/.well-known"表示域名下.well-known文件目录可以跳转
https://b.mashort.cn/.well-known
// "/.well-know/*"表示.well-known文件目录下的所有文件子目录都可以跳转
https://b.mashort.cn/.well-known/a
https://b.mashort.cn/.well-known/abc
// "/qq_conn/102057896"表示只有符合/qq_conn/102057896文件目录可以跳转
https://b.mashort.cn/qq_conn/102057896


6.App工程处理代码:
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
/// Universal Links跳转其他应用返回后回调方法:iOS13在SceneDelegate中
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
return ZBYWechatTool.instence.handleOpenUniversalLink(with: userActivity) || ZBYQQTool.instence.handleOpenUniversalLink(with: userActivity) ||
UniversalLinksTool.link(for: userActivity)
}
/// URL Schemes跳转其他应用返回后回调方法:iOS13在SceneDelegate中
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
let sendingAppID = options[.sourceApplication]
NSLog("source application = \(sendingAppID ?? "Unknown")")
return ZBYWechatTool.instence.handleOpen(with: url) || ZBYQQTool.instence.handleOpen(with: url) || UniversalLinksTool.scheme(open: url)
}
}
@available(iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
self.window = UIWindow.init(windowScene: scene as! UIWindowScene)
self.window?.rootViewController = AHLaunchScreenViewController.init()
self.window?.makeKeyAndVisible()
// 如果应用没有运行,Universal Links回调
guard let userActivity = connectionOptions.userActivities.first else { return }
UniversalLinksTool.link(for: userActivity)
// 如果应用没有运行,URL scheme回调
guard let urlContext = connectionOptions.urlContexts.first else { return }
let sendingAppID = urlContext.options.sourceApplication
NSLog("source application = \(sendingAppID ?? "Unknown")")
UniversalLinksTool.scheme(open: urlContext.url)
}
/// 应用已运行或者在后台挂起,Universal Links回调
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
ZBYWechatTool.instence.handleOpenUniversalLink(with: userActivity)
ZBYQQTool.instence.handleOpenUniversalLink(with: userActivity)
UniversalLinksTool.link(for: userActivity)
NSLog(userActivity)
}
/// 应用已运行或者在后台挂起,URL scheme回调
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else { return }
ZBYWechatTool.instence.handleOpen(with: url)
ZBYQQTool.instence.handleOpen(with: url)
UniversalLinksTool.scheme(open: url)
NSLog(url)
}
}
struct UniversalLinksTool {
@discardableResult
static func link(for userActivity: NSUserActivity) -> Bool {
// Get URL components from the incoming user activity.
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let incomingURL = userActivity.webpageURL,
let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true) else {
return false
}
// Check for specific URL components that you need.
guard let path = components.path,
let params = components.queryItems else {
return false
}
print("path = \(path)")
if let albumName = params.first(where: { $0.name == "albumname" } )?.value,
let photoIndex = params.first(where: { $0.name == "index" })?.value {
print("album = \(albumName)")
print("photoIndex = \(photoIndex)")
return true
} else {
print("Either album name or photo index missing")
return false
}
}
@discardableResult
static public func scheme(open url: URL) -> Bool {
// Process the URL.
guard let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true),
let albumPath = components.path,
let params = components.queryItems else {
print("Invalid URL or album path missing")
return false
}
if let photoIndex = params.first(where: { $0.name == "index" })?.value {
print("albumPath = \(albumPath)")
print("photoIndex = \(photoIndex)")
return true
} else {
print("Photo index missing")
return false
}
}
}
7.官方文档和遇到问题解析:
官方文档:
Supporting universal links in your app
Associated Domains Entitlement
遇到问题解析:
stackoverflow相关