我经常通过链接调用本地应用,比如支付宝的扫码支付,向特定用户发送短信。甚至一些隐藏的功能也以链接作为入口,例如Jellow现在的综合搜索。IOS上有两种实现调用的方法,分别是URL Scheme和Universal Links。本文实现了一个简单的Swift项目,实现并介绍这两种功能。
本文需要基本的Swift基础,接触过SwiftUI,大概就是官方例子大致看过的水平即可。
URL Scheme
简介
这种链接是scheme://data的形式,IOS会根据本地安装的应用搜索scheme,找到后询问你是否要通过相关应用打开该链接。很常用的一个链接是微信的扫码weixin://scanqrcode,通过这个链接直接打开微信的扫码。当然,首先需要本地安装了微信。它让类似捷径等一些自动化软件的功能大大增加。
有部分的Scheme是被保留的,可以通过这些保留的Scheme调用一些系统功能,例如短信、FaceTime,这里介绍了有哪些以及具体怎么使用这些内容,有兴趣可以尝试一下。
如果想要了解应用对应的scheme是什么,解压缩ipa,然后在Info.plist文件的CFBundleURLSchemes字段就是设置的scheme。但要了解scanqrcode之类的具体应用,就麻烦的多,网上如果没有现成的内容就需要反编译去找了。
展示收到的内容
我试了试用SwiftUI来写这个小东西,是个很简单的过程。
新建一个项目以后,在ContentView.swift里面加上基本的显示UI即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| import SwiftUI
class LinkData: ObservableObject { @Published var links :[Link] init() { self.links = [Link]() } func append(link: Link) { self.links.append(link) } }
struct Link { var time: String var data: String var fromApp: String var linkType: LinkType
enum LinkType: String { case urlScheme = "URL Scheme" case universal = "Universal link" } }
struct NavigateList: View { @ObservedObject var linkData: LinkData
var body: some View { VStack { NavigationView { List{ ForEach(linkData.links, id: \.time) { link in Row(link: link) } } .navigationBarTitle("链接列表") } } } }
struct Row: View { var link: Link
var body: some View { VStack { HStack { Text(link.fromApp) Spacer() Text(link.time) } HStack { Text(link.linkType.rawValue) Spacer() Text(link.data) } } .padding() } }
struct ContentView: View { @ObservedObject var linkData: LinkData
var body: some View { NavigateList(linkData: linkData) } }
|
这里可以加一个按钮试一试添加是否成功,那么就把NavigateList加一点东西。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| struct NavigateList: View { @ObservedObject var linkData: LinkData
var body: some View { VStack { NavigationView { List{ ForEach(linkData.links, id: \.time) { link in Row(link: link) } } .navigationBarTitle("Deep links") } Button(action: { self.linkData.append( link: Link( time: "now",data: "data", fromApp: "Demo App", linkType: .urlScheme ) ) }) { Image(systemName: "square.and.arrow.up") } } } }
|
按一下按钮,我们就假装收到了一条链接。
响应调用
应用需要决定自己响应什么scheme,这个在XCode > Project > Info > URL Types里面设置。
按照下面的图完成设置就行了。

这里完成设置以后,通过测试机子的Safari,地址栏填写geoffscheme://就已经能打开应用了。
获取链接内容
这边通过修改SceneDelegate的方式接收数据,这里可能有两种情况。
不过首先,先把URL转换成显示用的Link的方法作为SceneDelegate的类方法写好。
1 2 3 4 5 6 7 8 9 10 11
| func parseUrl(_ urlContext: UIOpenURLContext) -> Link { let fromApp = urlContext.options.sourceApplication ?? "Unknown" let url = urlContext.url.absoluteString let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyy-MM-dd HH:mm:ss"
return Link( time: dateFormatter.string(from: Date()), data: url, fromApp: fromApp, linkType: .urlScheme ) }
|
第一种情况,如果应用没有打开,也就是后台也没有该应用,那么链接会调用scene(_:willConnectTo:options)方法。所以就给SceneDelegate添加一个属性和一个方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| var rootView: ContentView?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { self.rootView = ContentView(linkData: LinkData())
if let urlContext = connectionOptions.urlContexts.first { self.rootView?.linkData.append(link: parseUrl(urlContext)) }
if let windowScene = scene as? UIWindowScene { let window = UIWindow(windowScene: windowScene) window.rootViewController = UIHostingController(rootView: self.rootView) self.window = window window.makeKeyAndVisible() } }
|
另一种情况,如果应用被打开了或者在后台,那么链接会调用scene(_:openURLContexts)方法。所以再添加一个方法。
1 2 3 4 5
| func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) { if let urlContext = URLContexts.first { self.rootView?.linkData.append(link: parseUrl(urlContext)) } }
|
然后通过在Safari中键入geoffscheme://demo或者一些别的geoffscheme://开头的内容,应用都可以被正确唤起并处理链接的内容。
Universal Links
这种链接是https://yourwebsite的形式,它需要有一个支持HTTPS的域名并进行相关的配置。由于其安全性高,支付宝扫码支付、购买转跳等都是用的这种方式。
需要注意的是,这个方法需要一个加入苹果开发者的账号,所以这个内容还属于年费$99付费解锁。如果手头没有这样的账号的话,其实自己玩的应用URL Scheme也够用了。
设置AASA
以我的网站为例,如果我的应用要通过https://geofftools.cn调用,那么我需要提供一个这样的文件:
1 2 3 4 5 6 7 8 9 10 11
| { "applinks": { "apps": [], "details": [ { "appID": "teamid.geoff.myapp", "paths": ["*"], } ] } }
|
这个文件可以放在两个位置,https://geofftools.cn/apple-app-site-association或者 https://geofftools.cn/.well-known/apple-app-site-association。
其中Apps按要求留空(官方要求),appID是TeamId和BundleId合起来,path的路径里可以用* ? NOT。
响应调用
应用需要设置响应的网站,这个在XCode > Project > Capability > Associated Domains里面设置。没有加入付费的苹果开发者的话,这就没办法设置了。
这边填<service>:<fully qualified domain>,例如applinks:geofftools.cn。
获取链接内容
和上面的操作类似,这里添加这样一个类方法。
1 2 3 4 5
| func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { if let urlContext = userActivity.webpageURL.first { self.rootView?.linkData.append(link: parseUrl(urlContext)) } }
|
对比
Universal Link更复杂也有更多的应用,URL Scheme则很容易配置也能满足基本需求,区别做成表格的话大概是这样一些不同。
|
Safari二次确认 |
需要本地安装 |
调用争抢 |
需要后端配合 |
需要加入开发者 |
| Universal Link |
√ |
× |
× |
√ |
√ |
| URL Scheme |
√ |
√ |
√ |
× |
× |
如果考虑安全性和易用性,肯定是Universal Link更合适,他调用应用不必须经过Safari,调用的名称也不会被其他应用占用。但如果只是做个小彩蛋,或者自己实现一些简单的功能,URL Scheme也是完全可以使用的选择。