SwiftUI打开网页
所属分类:ios | 浏览:1208 | 发布于 2023-06-05
在做iOS开发中,打开网页是个常见的需求,比如,要打开的用户协议和隐私政策等页面是h5页面。
打开浏览器主要有两种场景:
1、通过外部浏览器打开网页
2、在App内部打开网页
本篇我们就基于Swift和SwiftUI,分别实现这两种场景。
通过外部浏览器打开网页
目前看到的是有两种方法来打开外部浏览器
方法一:使用Link
struct ContentView: View {
    var body: some View {
        Link("Baidu", destination: URL(string: "https://baidu.com")!)
    }
}
方法二:使用环境变量openUrl
struct OpenBrowserWithOpenURL: View {
    @Environment(\.openURL) var openURL
    var body: some View {
        Button("Open Baidu") {
            openURL(URL(string: "https://baidu.com")!)
        }
    }
}
在App内部打开网页
在SwiftUI中,没有打开网页的接口,要实现打开网页的功能,需要借助WebKit组件桥接WKWebView。
1、桥接WKWebView初步使用
这里分为两步
1.1、创建WebView结构体
创建WebView结构体,实现UIViewRepresentable协议,用来桥接WKWebView
import SwiftUI
// 1
import WebKit
struct WebView: UIViewRepresentable {
    // 2
    let url: URL
    
    // 3
    func makeUIView(context: Context) -> WKWebView {
        return WKWebView()
    }
    
    // 4
    func updateUIView(_ webView: WKWebView, context: Context) {
        let request = URLRequest(url: url)
        webView.load(request)
    }
}
1.2、使用WebView打开网页
在创建实现了UIViewRepresentable协议的WebView结构体后,就可以向使用其它View组件一样使用它。
struct ContentView: View {
    // 1
    @State private var isPresentWebView = false
    var body: some View {
        Button("Open WebView") {
            // 2
            isPresentWebView = true
        }
        .sheet(isPresented: $isPresentWebView) {
            NavigationStack {
                // 3
                WebView(url: URL(string: "https://baidu.com")!)
                    .ignoresSafeArea()
                    .navigationTitle("Baidu In App")
                    .navigationBarTitleDisplayMode(.inline)
            }
        }
    }
}
1.3、存在的问题
上面的代码是可以打开网页了,但是一个问题:无法关闭sheet。
- 
This method should not be called on the main thread as it may lead to UI unresponsiveness.
 - 无法关闭sheet
 
在App内打开网页进阶版
在上面我已经实现了最基础了桥接WebKit下的WKWebView来打开网页,但实际使用的时候,我们还需要一些精细化的控制,大白话就是app与打开的网页的交互。
- 通过@Binding属性,可以将值从SwiftUI传递到UIView控件
 - 反过来,SwiftUI想要获取UIView控件的数据,那就需要使用Coordinator
 
关于UIViewRepresentable和Coordinator的介绍和使用,可以看这篇文章:SwiftUI中使用"UIViewRepresentable"桥接UIKit View
进阶版会引入WebViewModel类,这是一个ObservableObject类,进阶版的使用也分为三步:
1、创建WebViewModel类
import Foundation
import WebKit
class WebViewModel: ObservableObject {
    
    @Published var isLoading: Bool = false
    
    @Published var canGoBack: Bool = false
    
    @Published var shouldGoBack: Bool = false
    
    @Published var title: String = ""
    
    var urlString = ""
    
    
    func setUrlString(urlString: String) {
        self.urlString = urlString
    }
}
2、创建WebView结构体
import SwiftUI
import WebKit
struct WebView: UIViewRepresentable {
    
    @ObservedObject var model: WebViewModel
    
    func makeUIView(context: Context) -> WKWebView {
        guard let url = URL(string: self.model.urlString) else {
            return WKWebView()
        }
        let request = URLRequest(url: url)
        let webView = WKWebView()
        webView.navigationDelegate = context.coordinator
        webView.load(request)
        return webView
    }
    
    func updateUIView(_ webView: WKWebView, context: Context) {
        if model.shouldGoBack {
            webView.goBack()
            model.setShouldGoBackToFalse()
        }
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self, model)
    }
    
}
extension WebView {
    class Coordinator: NSObject, WKNavigationDelegate {
        @ObservedObject private var model: WebViewModel
        private let parent: WebView
        
        init(_ parent: WebView, _ model: WebViewModel) {
            self.parent = parent
            self.model = model
        }
        
        func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
            model.isLoading = true
        }
        
        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            model.isLoading = false
            model.title = webView.title ?? ""
            model.canGoBack = webView.canGoBack
        }
        
        func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
            model.isLoading = false
        }
    }
}
3、使用WebView打开网页
