如何创建自己的 URL Scheme

Avatar of Alexander Rinass
Alexander Rinass

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

URL Scheme 就像“http://…”或“ftp://…”。这些看起来像是非常底层的概念,您对其几乎没有控制权,但实际上,您确实可以!听起来像是异国情调的话题其实一点也不奇特:我们每天都在不断地使用不同的 URL Scheme。例如,当我们点击指向启动 AppStore 的 iPhone 应用的链接时。或者当朋友向我们发送播放列表链接并在 Spotify 桌面应用中打开时。

在以下简短教程中,我们将了解自定义 URL Scheme 在 macOS 和 iOS 上的工作原理。

URL Scheme 和文档类型

任何 macOS 或 iOS 应用程序都可以将自身注册为任何 URL Scheme(如“http”或“https”)或文档类型(如“txt”文件)的处理程序。但是,除了这些经典方案之外,应用程序还可以注册自己的自定义 URL Scheme 或文档格式。

如果应用程序想要表明它支持某种文档类型或 URL Scheme,则必须适当地配置其“Info.plist”文件:CFBundleDocumentTypes 键列出了应用程序支持的文档类型,而 CFBundleURLTypes 用于支持的 URL Scheme。

在您自己的应用程序中,您可以通过 Xcode 的项目设置轻松配置此项: “Info”选项卡提供了“文档类型”和“URL 类型”这两个部分。URL Scheme 可以是任何我们喜欢的字符串(只要它保持有效的 URL 格式)。

Xcode 项目设置

这使应用程序能够使用配置的类型,例如,当使用“打开方式”从 Finder 打开文件或在 iOS 上将文档从一个应用程序传递到另一个应用程序时。

自定义 URL Scheme 的用例

通常,注册您自己的自定义 Scheme 允许您将事件直接路由到您的应用程序。当用户打开使用此 Scheme 的 URL 时。例如,让我们看看 Tower,这是我的团队制作的 Git 桌面客户端:在您的机器上打开链接“gittower://openRepo/http://github.com/jquery/jquery.git”将启动 Tower 并打开“克隆”对话框,并预先填充相应的克隆 URL

Tower Git 客户端中的自定义 URL Scheme 示例

另一个用例是为我们的用户简化 Tower 的注册流程。购买许可证后,我们的客户会收到一封包含如下链接的电子邮件:“gittower://activateLicense/CODE/NAME”

这将启动 Tower(或将其置于前台)并打开注册对话框,其中预先填充了许可证信息。这比笨拙地复制粘贴(然后发现您错过了字符或包含了不需要的字符……)要舒服得多。

在 iOS 上,用例非常相似:应用程序也利用自定义 URL Scheme 来启动应用程序,然后在应用程序内显示特定屏幕。

长话短说:自定义 URL 是深度链接到您的应用程序的好方法!

示例应用

让我们动手创建一个处理自定义 URL Scheme 的应用程序。让我们将应用程序命名为 CustomURLScheme,并让它处理一个名为(当然!)foo 的 Scheme。

本简短教程的示例代码可以 在这里找到

注册我们的自定义 URL Scheme

第一步是在项目的 Info.plist 文件中注册应用程序作为我们自定义 URL Scheme 的处理程序。

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Viewer</string>
        <key>CFBundleURLName</key>
        <string>com.example.CustomURLScheme</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>foo</string>
        </array>
    </dict>
</array>

因此,我们郑重其事地提出担任 foo URL Scheme 的“查看器”角色。

有关所有可能的配置键的更多信息和详细说明,您可以查看 Apple 的属性列表键参考

处理来自 URL Scheme 的事件

下一步是告诉我们的应用程序如何处理通过我们的 URL Scheme 传入的事件。
对于 iOS 应用程序,这就像实现以下委托一样简单

func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool

对于 macOS 应用程序,我们需要告诉 NSAppleEventManager 我们的应用程序希望接收打开 URL 的事件,并提供一个回调方法来处理该事件。

我们首先在 AppDelegate 类中创建一个具有预期签名的空方法

class AppDelegate: NSObject, NSApplicationDelegate {

  func applicationDidFinishLaunching(_ aNotification: Notification) {
  }

  func applicationWillTerminate(_ aNotification: Notification) {
  }

  func handleAppleEvent(event: NSAppleEventDescriptor, replyEvent: NSAppleEventDescriptor) {
  }
}

然后我们从 applicationDidFinishLaunching 调用 NSAppleEventManagersetEventHandler 方法,如下所示

func applicationDidFinishLaunching(_ aNotification: Notification) {
  NSAppleEventManager.shared().setEventHandler(self, andSelector: #selector(self.handleAppleEvent(event:replyEvent:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL))
}

现在,如果您构建并运行应用程序,事件将正确传递到我们的回调方法 - 但它仍然为空并且不会执行任何操作。

回调方法将传入事件作为 event: NSAppleEventDescriptor 接收。NSAppleEventDescriptor 具有许多属性和方法。如果您只关心 URL,则以下实现将可以解决问题

func handleAppleEvent(event: NSAppleEventDescriptor, replyEvent: NSAppleEventDescriptor) {
  guard let appleEventDescription = event.paramDescriptor(forKeyword: AEKeyword(keyDirectObject)) else {
      return
  }

  guard let appleEventURLString = appleEventDescription.stringValue else {
      return
  }

  let appleEventURL = URL(string: appleEventURLString)

  print("Received Apple Event URL: \(appleEventURL)")
}

因此,macOS 的最终实现如下所示

class AppDelegate: NSObject, NSApplicationDelegate {

  func applicationDidFinishLaunching(_ aNotification: Notification) {
    NSAppleEventManager.shared().setEventHandler(self, andSelector: #selector(self.handleAppleEvent(event:replyEvent:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL))
  }

  func applicationWillTerminate(_ aNotification: Notification) {
  }

  func handleAppleEvent(event: NSAppleEventDescriptor, replyEvent: NSAppleEventDescriptor) {
    guard let appleEventDescription = event.paramDescriptor(forKeyword: AEKeyword(keyDirectObject)) else {
        return
    }

    guard let appleEventURLString = appleEventDescription.stringValue else {
        return
    }

    let appleEventURL = URL(string: appleEventURLString)

    print("Received Apple Event URL: \(appleEventURL)")
  }
}

构建并运行应用程序,它应该将接收到的 URL 打印到调试控制台。

获得 URL 后,您可以将其转换为您希望应用程序执行的操作。

将应用注册为默认处理程序

除了我们自己的 gittower Scheme 之外,Tower 还支持另外两个 Scheme:github-macsourcetree,因为这些 Scheme 用于 github.com 和 bitbucket.com 在桌面应用程序中打开克隆 URL。当然,我们不会“盲目地”覆盖其他处理程序!用户可以明确选择让 Tower 处理来自 GitHub 和 Bitbucket 的这些 URL。

这是使用 CoreServices 框架的一个有趣部分完成的,即 Launch Services API。虽然 API 是用 C 编写的,但为所需方法编写 Swift 包装器非常容易

import Foundation
import CoreServices

class LaunchServices {

  class func applicationsForURLScheme(scheme: String) -> Array<String> {
    if let applications = LSCopyAllHandlersForURLScheme(scheme as CFString) {
      return applications.takeUnretainedValue() as Array<AnyObject> as! Array<String>
    }

    return []
  }

  class func defaultApplicationForURLScheme(scheme: String) -> String? {
    if let defaultApplication = LSCopyDefaultHandlerForURLScheme(scheme as CFString) {
      return defaultApplication.takeUnretainedValue() as String
    }

    return nil
  }

  class func setDefaultApplicationForURLScheme(bundleIdentifier: String, scheme: String) -> Bool {
    let status = LSSetDefaultHandlerForURLScheme(scheme as CFString, bundleIdentifier as CFString)
    return (status == 0)
  }
}

此辅助类提供以下核心功能

  • applicationsForURLScheme – 检索已声明支持特定 URL Scheme 的应用程序包标识符列表
  • defaultApplicationForURLScheme – 返回特定 URL Scheme 的当前默认处理程序的应用程序包标识符
  • setDefaultApplicationForURLScheme – 将特定 URL Scheme 的默认处理程序设置为新的应用程序包标识符

macOS 示例项目演示了如何使用此类:它显示特定 URL Scheme 的所有应用程序列表,并预先选择了默认应用程序(不用担心:在此示例中,使用选择输入不会更改默认值;它是只读的)。

继续,创建您自己的 Scheme

自定义 URL Scheme 是深度链接到您的应用程序并触发操作的好方法。它们易于设置(尤其是在 iOS 上),并为用户提供来自其他应用程序时的便捷快捷方式。

创建您自己的 URL Scheme,玩得开心!