在 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()
    }
}
相关推荐
恋猫de小郭15 小时前
什么?Flutter 可能会被 SwiftUI/ArkUI 化?全新的 Flutter Roadmap
flutter·ios·swiftui
网安墨雨19 小时前
iOS应用网络安全之HTTPS
web安全·ios·https
福大大架构师每日一题21 小时前
37.1 prometheus管理接口源码讲解
ios·iphone·prometheus
BangRaJun2 天前
LNCollectionView-替换幂率流体
算法·ios·设计
刘小哈哈哈2 天前
iOS 多个输入框弹出键盘处理
macos·ios·cocoa
靴子学长2 天前
iOS + watchOS Tourism App(含源码可简单复现)
mysql·ios·swiftui
一如初夏丿2 天前
xcode15 报错 does not contain ‘libarclite‘
ios·xcode
杨武博3 天前
ios 混合开发应用白屏问题
ios
BangRaJun3 天前
LNCollectionView
android·ios·objective-c
二流小码农3 天前
鸿蒙元服务项目实战:终结篇之备忘录搜索功能实现
android·ios·harmonyos