SFSafariViewController
可用于让用户在应用程序内而不是在外部浏览器中打开网页。虽然视图控制器非常适合 UIKit,但在 SwiftUI 应用程序中直接使用它还是有一定的挑战性的。
每当遇到仅 UIKit 解决方案可用的情况时,你可能想知道如何编写包装器并使 UIKit 类可用于 SwiftUI 视图。理想情况下,它是可重复使用的,以便你以后可以重复使用它。下面让我们来深入了解一下!
创建 SFSafariViewController 的 SwiftUI 包装器
我们通过实现 SFSafariViewController
的 UIViewControllerRepresentable
协议来开始。该协议允许我们创建一个包装 UIKit 视图控制器的 SwiftUI 视图:
swift
struct SFSafariView: UIViewControllerRepresentable {
let url: URL
func makeUIViewController(context: UIViewControllerRepresentableContext<Self>) -> SFSafariViewController {
return SFSafariViewController(url: url)
}
func updateUIViewController(_ uiViewController: SFSafariViewController, context: UIViewControllerRepresentableContext<SFSafariView>) { }
}
通过上面的代码我们可以看到, 我们需要实现 UIViewControllerRepresentable
的两个函数:
makeUIViewController(context:)
:调用该函数来创建UIViewController
的实例。updateUIViewController(_:context:)
:将调用该函数以便使用来自SwiftUI
的最新信息,用其更新 UIViewController 的状态。
在我们的例子中,我们只需使用给定的 URL 实例化 SFSafariViewController
即可。所以 updateUIViewController(_:context:)
函数体内并没有编写代码。
创建可重用的视图修改器
我比较倾向于从一开始就编写可重用的代码,这样可以方便我的代码被重用。我甚至有一个专用的扩展包,我可以轻松地在不同的应用程序中重复使用它,这样当我遇到以前遇到的问题时,我就可以更快地编写应用程序。
在我们的例子中,还需要一个视图修饰符来捕获通常在外部浏览器中打开的任何链接。这些链接可以在 SwiftUI
中用下面的代码生成:
css
struct SwiftUILinksView: View {
var body: some View {
VStack(spacing: 20) {
// 普通链接
Link("SwiftUI Link Example", destination: URL(string: "https://www.xxx.com")!)
// Markdown 中的链接
Text("Markdown link example: [xxx](https://www.xxx.com)")
}
}
}
解决方案的关键就是在环境视图修饰符中使用 openURL
环境属性。视图修饰符的代码如下所示:
swift
private struct SafariViewControllerViewModifier: ViewModifier {
@State private var urlToOpen: URL?
func body(content: Content) -> some View {
content
.environment(\.openURL, OpenURLAction { url in
// 捕获任何即将在外部浏览器中打开的 URL。将其用我们的 SFSafariView 打开
urlToOpen = url
return .handled
})
.sheet(isPresented: $urlToOpen.mappedToBool(), onDismiss: {
urlToOpen = nil
}, content: {
SFSafariView(url: urlToOpen!)
})
}
}
我们使用视图修饰符来捕获任何传出 URL 并将它们用作 sheet 的输入。该 sheet 将使用我们之前创建的 SFSafariView,通过 SFSafariViewController 在应用程序内呈现 URL。
需要注意的是,我们正在使用 Binding 一个扩展,该扩展允许将任何可选绑定映射到布尔绑定:
swift
extension Binding where Value == Bool {
init(binding: Binding<(some Any)?>) {
self.init(
get: {
binding.wrappedValue != nil
},
set: { newValue in
guard newValue == false else { return }
binding.wrappedValue = nil
}
)
}
}
extension Binding {
func mappedToBool<Wrapped>() -> Binding<Bool> where Value == Wrapped? {
Binding<Bool>(binding: self)
}
}
这是我在编写 SwiftUI 解决方案时经常重复使用的最喜欢的扩展之一。
最后缺少的部分是一个好用的视图扩展,这可以更轻松地访问我们的逻辑:
scss
extension View {}
func handleOpenURLInApp() -> some View {
modifier(SafariViewControllerViewModifier())
}
}
在 SwiftUI 中展示 SFSafariViewController
现在我们已经有了所有的逻辑,我们可以开始在 SwiftUI 的 SFSafariViewController 中呈现任何传入的 URL。我们可以通过在 VStack 上使用视图的扩展方法来做到这一点:
css
struct SwiftUILinksView: View {
var body: some View {
VStack(spacing: 20) {
Link("SwiftUI Link Example", destination: URL(string: "https://www.xxx.com")!)
Text("Markdown link example: [RocketSim](https://www.xxx.com)")
}
.handleOpenURLInApp()
}
}