处理SwiftUI的openURL的链接

属性字符串和Text今年进行了重大升级。由于有机会设置openURL环境值,此升级在最新的Xcode测试版中进一步扩展。

在本文中,让我们探索围绕openUrl和在SwiftUI视图中处理链接的所有内容。

两个视图处理SwiftUI中的URL:Link,以及从今年开始的Text

链接

从iOS 14(以及其他平台中的等效产品)SwiftUI应用程序可以在视图中声明Link

Link("Go to the main blog", destination: URL(string: "https://fivestars.blog/")!)

Links是伪装的按钮(我们可以应用按钮样式),专门打开URL。它们的主要目的是从小部件深入链接到应用程序,因为任何逻辑都无法在小部件上运行(从iOS 15/macOS 12开始)。

它们的使用不仅限于小部件。对于各种情况/需求,我们可以在应用程序中使用Link

// Opens URL in Safari
Link("Go to the main blog", destination: URL(string: "https://fivestars.blog/")!)
  .buttonStyle(.borderedProminent)

// Opens Settings.app
Link("Go to settings", destination: URL(string: UIApplication.openSettingsURLString)!)
  .buttonStyle(.bordered)

// Open third party app
Link("Go to app", destination: URL(string: "bangkok-metro://Sukhumvit%20Line%2FAsok")!)

文本

Text是今年获得最多新功能的SwiftUI视图之一,这主要归功于新的markdown和AttributedString支持。

通过使用以下任一新功能,我们现在可以添加Text链接:

var body: some View {
  VStack {
    Text(attributedString)
    Text("Check out [Five Stars](https://fivestars.blog)")
  }
}

var attributedString: AttributedString {
  var attributedText = AttributedString("Visit website")
  attributedText.link = URL(string: "https://fivestars.blog")
  return attributedText
}

Link类似,Text也支持非http网址。

openURL

当用户与链接交互时(在TextLink视图中),SwiftUI将访问视图openURL环境值:

extension EnvironmentValues {
  public var openURL: OpenURLAction { get }
}

OpenURLAction的定义如下:

public struct OpenURLAction {
  public func callAsFunction(_ url: URL)
  public func callAsFunction(_ url: URL, completion: @escaping (_ accepted: Bool) -> Void)
}

该视图将使用相关URL调用openURL。然后,系统将接收和处理URL,URL将打开默认设备浏览器并加载网页,或深入链接到另一个应用程序。

由于openURL是一种公共环境价值,我们也可以自己使用它,取代遗留的UIApplication调用:

struct FSView {
  @Environment(\.openURL) var openURL // 👈🏻 New way

  var body {
    ...
  }

  func onOpenURLTap(_ url: URL, completion: @escaping (_ accepted: Bool) -> Void) {
    // Old way 👇🏻
    if UIApplication.shared.canOpenURL(url) {
      UIApplication.shared.open(url)
      completion(true)
    } else {
      completion(false)
    }

    // New way 👇🏻
    openURL(url, completion: completion)
  }
}

最新消息

Xcode 13 Beta 5的新功能,openURL不仅可以读取,还可以设置:

extension EnvironmentValues {
  // public var openURL: OpenURLAction { get } 👈🏻 Previous declaration
  public var openURL: OpenURLAction // 👈🏻 New declaration
}

OpenURLAction还获得了公共初始化器:

public struct OpenURLAction {
  @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
  public init(handler: @escaping (URL) -> OpenURLAction.Result)
}

这需要返回新的OpenURLAction.Result类型的闭包,定义如下:

public struct Result {
  public static let handled: OpenURLAction.Result
  public static let discarded: OpenURLAction.Result
  public static let systemAction: OpenURLAction.Result
  public static func systemAction(_ url: URL) -> OpenURLAction.Result
}

默认行为保持不变,但这一变化使第三方开发人员能够完全控制URL处理。我们接下来看看那个。

处理URL

默认行为相当于设置以下openURL值:

Link("FIVE STARS", destination: URL(string: "https://fivestars.blog/")!)
  .environment(\.openURL, OpenURLAction { url in
    return .systemAction
  })

OpenURLAction.Result还有三个选择。接下来让我们看看那些。

.handled

.handled告诉SwiftUI,我们的应用程序逻辑成功解决了网址:

Link("FIVE STARS", destination: URL(string: "https://fivestars.blog/")!)
  .environment(\.openURL, OpenURLAction { url in
    // ...
    return .handled
  })

无论URL值如何,通过返回.handled,系统不会打开SafariSafari或触发任何深度链接。

.discarded

.discarded工作原理与.handled完全相同,但告诉SwiftUI网址无法处理。

Link("FIVE STARS", destination: URL(string: "https://fivestars.blog/")!)
  .environment(\.openURL, OpenURLAction { url in
    // ...
    return .discarded
  })

回到OpenURLAction的定义,.discarded.handled之间的区别在于openURL完成块中传递的值:

public struct OpenURLAction {
  public func callAsFunction(_ url: URL, completion: @escaping (_ accepted: Bool) -> Void)
}
  • .handled将调用completiontrue
  • .discarded将调用completionfalse

.systemAction(_ url: URL)

最后一个选项类似于默认的.systemAction行为,但差异在于我们现在可以转发到另一个URL:

Link("Go to Google", destination: URL(string: "https://google.com/")!)
  .environment(\.openURL, OpenURLAction { url in
    // Go to Bing instead 😈
    return .systemAction(URL(string: "https://www.bing.com")!)
  })

自定义操作

openURL的隐藏电源在.handled案例中:
最常见的用例是将用户转发出应用程序,但现在我们可以处理不同的意图。

例如,想象应用程序中的欢迎屏幕,提示用户登录或注册:

VStack {
  Text("Welcome to Five Stars!")
    .font(.largeTitle)

  Text("Please [sign in](https://fivestars.blog/sign-in) or [sign up](https://fivestars.blog/create-account) to continue.")
    .font(.headline)
    .environment(\.openURL, OpenURLAction { url in
      switch url.lastPathComponent {
        case "sign-in":
          // show sign in screen
          return .handled
        case "crate-account":
          // show sign up screen
          return .handled
        default:
          // Intent not recognized.
          return .discarded
      }
    })
}

首先,Text有两个不同的交互式部分:在最新的SwiftUI迭代之前,这是不可能的。

尽管有新功能,但我仍然期待着有一天我们拥有.onTapGesture(_:)Text修饰符,FB8917806。

其次,由于URL现在在应用程序内处理,我们不需要整个http声明。
我们可以通过以下内容简化上述代码:

Text("Please [sign in](sign-in) or [sign up](create-account) to continue.")
  .font(.headline)
  .environment(\.openURL, OpenURLAction { url in
    switch url.absoluteString {
      case "sign-in":
        // show sign in page
        return .handled
      case "crate-account":
        // show sign up page
        return .handled
      default:
        // Intent not recognized.
        return .discarded
    }
  })

环境价值

与我们之前讨论过onSubmitAction环境值不同,设置openURL环境值总是取代上一个,这意味着只会调用离我们视图最近的闭包。

在以下示例中,将只触发返回.systemAction的闭包:

Link(...)
  .environment(\.openURL, OpenURLAction { url in
    return .systemAction
  })
  .environment(\.openURL, OpenURLAction { url in
    return .systemAction(URL(string: "https://www.anotherURL.com")!)
  })
  .environment(\.openURL, OpenURLAction { url in
    return .handled
  })
  .environment(\.openURL, OpenURLAction { url in
    return .discarded
  })

从今天开始,无法告诉SwiftUI触发“下一个”关闭,而不是在第一个关闭后停止。

查看修饰符

In SwiftUI we already have a onOpenURL(perform:) view modifier, used to handle deep-links in the app. This naming is unfortunate, as it would make sense to have a companion onOpenURL(handler:) modifier for openURL.

尽管此修饰符不是官方API的一部分,但我们仍然可以自己创建它:

extension View {
  func onOpenURL(handler: @escaping (URL) -> OpenURLAction.Result) -> some View {
    environment(\.openURL, OpenURLAction(handler: handler))
  }
}

例如,这种扩展将减少这种情况:

Link("FIVE STARS", destination: URL(string: "https://fivestars.blog/")!)
  .environment(\.openURL, OpenURLAction { url in
    return .systemAction
  })

对此:

Link("FIVE STARS", destination: URL(string: "https://fivestars.blog/")!)
  .onOpenURL(handler: { url in
    return .systemAction
  })

...这不那么冗长,看起来更容易。

posted @ 2021-08-19 16:39  zuidap  阅读(1142)  评论(0编辑  收藏  举报