前言
之前讲过如何在项目中使用 SFSafariViewController,但那个是 Swift 版的,今天看到有人问如何在 SwiftUI 中使用 SFSafariViewController,虽然在 Swift UIKit 中很容易使用,但是在 SwiftUI 中还是有些难度的,今天就来稍微讲讲。
为 SFSafariViewController 创建 SwiftUI 包装器
其实 SwiftUI 并没有直接提供 SFSafariViewController 这个组件,那么在 SwiftUI 中使用就只能通过桥接一层。我们先通过创建一个继承 UIViewRepresentable 协议的自定义结构体,然后再开始实现对应的功能。该协议允许我们创建一个包装 UIKit 视图控制器的 SwiftUI 视图:
首先导入三个需要用到的框架:
import SwiftUI
import UIKit
import SafariServices
然后创建自定义的 SFSafariView:
struct MySFSafariView: UIViewControllerRepresentable {
let url: URL
func makeUIViewController(context: UIViewControllerRepresentableContext<Self>) -> SFSafariViewController {
return SFSafariViewController(url: url)
}
func updateUIViewController(_ uiViewController: SFSafariViewController, context: UIViewControllerRepresentableContext<MySFSafariView>) {
}
}
要自定义一个 SwiftUI 中桥接 UIKit 的 View,首先要继承 UIViewControllerRepresentable 协议,然后必须要实现协议中的两个方法:
makeUIViewController(context: ) 方法将调用它来创建 UIViewController 实例
updateUIViewController(_ : context: ) 方法将调用它以使用来自 SwiftUI 的数据以更新 UIViewController 的状态
在我们的例子中,我只是简单的创建了一个 SFSafariViewController 对象,实际中你可以根据需求来增加功能。
创建可重用的视图修饰符
为了以后可以重复使用这个 View,我们再来创建一些可重用的视图修饰符 ViewModifier,便于使用 openURL 打开一个链接:
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)
}
}
private struct SafariViewControllerViewModifier: ViewModifier {
@State private var urlToOpen: URL?
func body(content: Content) -> some View {
content
.environment(\.openURL, OpenURLAction { url in
urlToOpen = url
return .handled
})
.sheet(isPresented: $urlToOpen.mappedToBool(), onDismiss: {
urlToOpen = nil
}, content: {
MySFSafariView(url: urlToOpen!)
})
}
}
我们使用视图修饰符来捕获任何传入的 URL,并将它们用作 sheet 的输入。这个新的 Sheet 使用我们之前创建 MySFSafariView 的 URL 在应用程序内显示 SFSafariViewController。
在 SwiftUI 中展示 SFSafariViewController
我们先给 View 写一个扩展方法,以使用刚刚写好的视图修饰符:
extension View {
func handleOpenURLUseSafariView() -> some View {
modifier(SafariViewControllerViewModifier())
}
}
现在我们已经写好了所有逻辑,现在可以开始在 SwiftUI 中展示任何传入的 URL 的视图了。
struct ContentView: View {
var body: some View {
VStack {
Link("打开 Apple 官网", destination: URL(string: "https://www.apple.com")!)
. handleOpenURLUseSafariView()
}
}
}

点击之后可以看到能够正常展示 SFSafariViewController:
总结
以上就是使用 SwiftUI 展示 SFSafariViewController 的内容了,而且我们还对代码进行了封装,以后再有类似的需求,只需要在有链接的 View 上使用 .handleOpenURLUseSafariView() 方法即可。