在 SwiftUI 中使用SFSafariViewController:在 APP 中打开网页

SFSafariViewController 可用于让用户在应用程序内而不是在外部浏览器中打开网页。虽然视图控制器非常适合 UIKit,但在 SwiftUI 应用程序中直接使用它还是有一定的挑战性的。

每当遇到仅 UIKit 解决方案可用的情况时,你可能想知道如何编写包装器并使 UIKit 类可用于 SwiftUI 视图。理想情况下,它是可重复使用的,以便你以后可以重复使用它。下面让我们来深入了解一下!

创建 SFSafariViewController 的 SwiftUI 包装器

我们通过实现 SFSafariViewControllerUIViewControllerRepresentable 协议来开始。该协议允许我们创建一个包装 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()
    }
}
相关推荐
Magnetic_h8 小时前
【iOS】单例模式
笔记·学习·ui·ios·单例模式·objective-c
归辞...10 小时前
「iOS」——单例模式
ios·单例模式·cocoa
yanling202312 小时前
黑神话悟空mac可以玩吗
macos·ios·crossove·crossove24
归辞...14 小时前
「iOS」viewController的生命周期
ios·cocoa·xcode
crasowas18 小时前
Flutter问题记录 - 适配Xcode 16和iOS 18
flutter·ios·xcode
2401_8524035518 小时前
Mac导入iPhone的照片怎么删除?快速方法讲解
macos·ios·iphone
SchneeDuan18 小时前
iOS六大设计原则&&设计模式
ios·设计模式·cocoa·设计原则
JohnsonXin1 天前
【兼容性记录】video标签在 IOS 和 安卓中的问题
android·前端·css·ios·h5·兼容性
蒙娜丽宁1 天前
Go语言错误处理详解
ios·golang·go·xcode·go1.19
名字不要太长 像我这样就好1 天前
【iOS】push和pop、present和dismiss
学习·macos·ios·objective-c·cocoa