处理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/")!)
Link
s是伪装的按钮(我们可以应用按钮样式),专门打开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
当用户与链接交互时(在Text
或Link
视图中),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
将调用completion
true
.discarded
将调用completion
false
.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
})
...这不那么冗长,看起来更容易。