简介
从iOS14开始,SwiftUI当前不支持与WKWebView等效的View。
因此,有必要包装和使用UIKit的WKWebView来实现SwiftUI。
这次,目标是显示配备基本功能(工具栏,进度栏)的WebView,我想总结一下如何使用其中显示的
*在本文中,考虑到多功能性,将使用iOS13之前支持的技术来显示WebView。
关于UIViewRepresentable
您必须使用
描述
1 | func makeUIView(context: Self.Context) -> Self.UIViewType |
需要实施。
创建要显示的View的实例。
SwiftUI中使用的UIKit的View作为返回值返回。
1 | func updateUIView(Self.UIViewType, context: Self.Context) |
需要实施。
在应用程序状态更新时调用。
如果有View的更新,请在此功能中对其进行描述。
1 | static func dismantleUIView(_ uiView: Self.UIViewType, coordinator: Self.Coordinator) |
在删除指定的UIKit视图时调用。
描述此功能的清除过程,例如,如有必要,删除注册的通知。
1 | func makeCoordinator() -> Self.Coordinator |
在有事件要从View端通知时实施。
通过定义
使用WKWebView
创建WebView
在
中,我们将输入WKWebView的显示内容,这是主要主题。
显示WKWebView
首先,只需在SwiftUI中显示WebView。
这次,我将使用WKWebView。
WebView.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | struct WebView: UIViewRepresentable { /// 表示するView private let webView = WKWebView() /// 表示するURL let url: URL func makeUIView(context: Context) -> WKWebView { // 戻り値をWKWebViewとし、返却する webView.load(URLRequest(url: url)) return webView } func updateUIView(_ uiView: WKWebView, context: Context) { } } |
创建工具栏
接下来,我们将创建包含三个元素
点击按钮时的动作控制
首先,创建一个工具栏并放置每个按钮。
接下来,使用
如果在此状态下点击工具栏上的按钮,则应用程序的状态将被更新,并且
从上面的WebView一侧,根据
WebView.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | struct WebView: UIViewRepresentable { // 省略... /// WebViewのアクション enum Action { case none case goBack case goForward case reload } /// アクション @Binding var action: Action func updateUIView(_ uiView: WKWebView, context: Context) { /// バインドしている値が更新されるたびに呼ばれる /// actionが更新されたら、更新された値に応じて処理を行う switch action { case .goBack: uiView.goBack() case .goForward: uiView.goForward() case .reload: uiView.reload() case .none: break } action = .none } } |
WebToolBarView.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | struct WebToolBarView: View { /// アクション @Binding var action: WebView.Action var body: some View { VStack() { HStack() { // タップしたボタンに応じてアクションを更新 Button("Back") { action = .goBack } Button("Forward") { action = .goForward } Button("Reload") { action = .reload } } } } } |
RichWebView.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | struct RichWebView: View { /// URL let url: URL /// アクション @State private var action: WebView.Action = .none var body: some View { VStack() { WebView(url: url, action: $action) WebToolBarView(action: $action) } } } |
此处不解释
*
有许多文章详细解释了这些内容,因此如有必要,请分别参阅它们。
状态和数据流| Apple开发人员文档
按钮停用
现在,可以在WebView中点击按钮时进行处理。
但是,可以在任何状态下轻按按钮,因此,如果您无法移至上一页或下一页,请停用每个按钮。
定义协调器
WebView页面加载完成后,确定是否可以移动到上一页或下一页。
为此,您需要实现
因此,在
WebView.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | struct WebView: UIViewRepresentable { // 省略... /// 戻れるか @Binding var canGoBack: Bool /// 進めるか @Binding var canGoForward: Bool func makeCoordinator() -> WebView.Coordinator { return Coordinator(parent: self) } } extension WebView { final class Coordinator: NSObject, WKNavigationDelegate { /// 親View let parent: WebView init(parent: WebView) { self.parent = parent } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { parent.canGoBack = webView.canGoBack parent.canGoForward = webView.canGoForward } } } |
之后,通过
WebToolBarView.swift
1 2 | Button("Back") { action = .goBack }.disabled(!canGoBack) Button("Forward") { action = .goForward }.disabled(!canGoForward) |
创建进度条
最后,让我们创建一个进度条。
通过KVO
获得价值
您需要获取
进度栏。
这次,我们将对每个使用KVO。
您将收到来自View的更改通知,因此请使用之前使用的
WebView.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | struct WebView: UIViewRepresentable { // 省略... /// 読み込みの進捗状況 @Binding var estimatedProgress: Double /// ローディング中かどうか @Binding var isLoading: Bool static func dismantleUIView(_ uiView: WKWebView, coordinator: Coordinator) { // WKWebView削除時に呼ばれます // インスタンスが削除されるタイミングで通知を無効化、削除しておきます coordinator.observations.forEach({ $0.invalidate() }) coordinator.observations.removeAll() } } extension WebView { final class Coordinator: NSObject, WKNavigationDelegate { /// 親View let parent: WebView /// NSKeyValueObservations var observations: [NSKeyValueObservation] = [] init(parent: WebView) { self.parent = parent // 通知を登録する let progressObservation = parent.webView.observe(\.estimatedProgress, options: .new, changeHandler: { _, value in parent.estimatedProgress = value.newValue ?? 0 }) let isLoadingObservation = parent.webView.observe(\.isLoading, options: .new, changeHandler: { _, value in parent.isLoading = value.newValue ?? false }) observations = [ progressObservation, isLoadingObservation ] } // 省略... } } |
创建进度条
现在,您拥有进度条所需的所有部分。
您要做的就是创建
ProgressBarView.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | struct ProgressBarView: View { /// 読み込みの進捗状況 var estimatedProgress: Double var body: some View { VStack { GeometryReader { geometry in Rectangle() .foregroundColor(Color.gray) .opacity(0.3) .frame(width: geometry.size.width) Rectangle() .foregroundColor(Color.blue) .frame(width: geometry.size.width * CGFloat(estimatedProgress)) } }.frame(height: 3.0) } } |
RichWebView.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | struct RichWebView: View { // 省略... /// 読み込みの進捗状況 @State private var estimatedProgress: Double = 0.0 /// ローディング中かどうか @State private var isLoading: Bool = false var body: some View { VStack() { if isLoading { ProgressBarView(estimatedProgress: estimatedProgress) } // 省略... } } } |
您现在已经实现了一组功能。
还有其他要考虑的方面,例如错误处理,但我认为我们能够使用粗糙的功能创建WebView。
结论
这次,我旨在显示丰富的WebView来实现它,但是为了使用SwiftUI实现WebView,有必要使用
如果您有兴趣,请尝试一下。
我将在下面的github上总结源代码,因此,如果您有兴趣,请看一下。
(github端包含一些此处省略的代码,例如导航栏的标题显示和布局限制。)
RichWebViewSample
另外,如果您对此实施有任何改进建议或意见,请提出意见,我们将不胜感激。
非常感谢你。
参考
- UIViewRepresentable | Apple开发人员文档
- 与UIKit交互— SwiftUI教程| Apple开发人员文档