SwiftUI - 如果应用程序使用 UniversalLink 启动,加载器不会在 WebView 加载后消失

问题描述

我已经实现了一个加载器,当我的 web 视图加载页面时向用户显示一个旋转的圆圈。当我第一次在应用程序中加载 webview 并在 webview 中的页面之间浏览时,这非常有效。

然而,有一种情况下 Loader 永远不会消失:如果应用程序是通过通用链接启动的:在这种情况下,即使我浏览 WebView 中的其他几个页面,Loader 也会永远停留。就像没有检测到 WebView 完成加载一样。

这是我的代码

WebsiteView.swift

//
//  WebsiteView.swift
//

import SwiftUI

struct WebsiteView: View {
    
    @EnvironmentObject var viewRouter: ViewRouter
    @Observedobject var viewmodel = viewmodel()
    @State var showLoader = false
    
    var body: some View {
        ZStack {
            vstack(spacing: 0) {
                WebView(viewmodel: viewmodel).overlay (
                    RoundedRectangle(cornerRadius: 4,style: .circular)
                        .stroke(Color.gray,linewidth: 0.5)
                ).padding(.leading,0).padding(.trailing,0).padding(.top,0)
                
            //webViewNavigationBar
            }.onReceive(self.viewmodel.showLoader.receive(on: RunLoop.main)) { value in
                self.showLoader = value
            }
            
            // A simple loader that is shown when WebView is loading any page and hides when loading is finished.
            if showLoader {
                Loader()
            }
        }
    }
}

struct websiteView_Previews: PreviewProvider {
    static var previews: some View {
        WebsiteView().environmentObject(ViewRouter())
    }
}

Loader.swift

//
//  Loader.swift
//

import SwiftUI

struct Loader: View {
    @State var spinCircle = false
    
    var body: some View {
        ZStack {
                Circle()
                    .stroke(Color.white,linewidth: 18)
                    .frame(width: 70,height: 70)
                Circle()
                    .stroke(Color(.systemGray5),linewidth: 14)
                    .frame(width: 70,height: 70)
                Circle()
                    .trim(from: 0,to: 0.2)
                    .stroke(Color.green,linewidth: 7)
                    .frame(width: 70,height: 70)
                    .rotationEffect(Angle(degrees: spinCircle ? 360 : 0))
                    .animation(Animation.linear(duration: 1).repeatForever(autoreverses: false))
                    .onAppear() {
                        self.spinCircle = true
                }
          }
    }
}

struct Loader_Previews: PreviewProvider {
    static var previews: some View {
        Loader()
    }
}

WebView.swift

//
//  WebView.swift
//

import Foundation
import UIKit
import SwiftUI
import Combine
import WebKit
import CoreLocation

// MARK: - WebViewHandlerDelegate
// For printing values received from web app
protocol WebViewHandlerDelegate {
    func receivedJsonValueFromWebView(value: [String: Any?])
    func receivedStringValueFromWebView(value: String)
}

// MARK: - WebView
struct WebView: UIViewRepresentable,WebViewHandlerDelegate {
    
    @EnvironmentObject var viewRouter: ViewRouter
        
    func receivedJsonValueFromWebView(value: [String : Any?]) {
        print("JSON value received from web is: \(value)")
    }
    
    func receivedStringValueFromWebView(value: String) {
        print("String value received from web is: \(value)")
    }
    
    // viewmodel object
    @Observedobject var viewmodel: viewmodel
    
    // Make a coordinator to co-ordinate with WKWebView's default delegate functions
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    func makeUIView(context: Context) -> WKWebView {
        
        // Customize the webView configuration
        let preferences = WKPreferences()
        preferences.javaScriptCanopenWindowsAutomatically = true
        let configuration = WKWebViewConfiguration()
        configuration.userContentController.add(self.makeCoordinator(),name: "iOSNative")
        configuration.preferences = preferences
        configuration.allowsInlinemediaplayback = true

        // Create the webView
        let webView = WKWebView(frame: CGRect.zero,configuration: configuration)
        webView.navigationDelegate = context.coordinator
        webView.allowsBackForwardNavigationGestures = true
        webView.scrollView.isScrollEnabled = true
        
        viewRouter.navigatorGeolocation.setUserContentController(webViewConfiguration: configuration)
        viewRouter.navigatorGeolocation.setWebView(webView: webView)
                
        return webView
    }
    
    func updateUIView(_ webView: WKWebView,context: Context) {
        if !viewRouter.nextPost.isEmpty {
            let urlString = viewRouter.nextDomain + "/" + viewRouter.nextPage
            var request = URLRequest(url: URL(string: urlString)!)
            request.addValue("application/x-www-form-urlencoded",forHTTPHeaderField: "Content-Type")
            request.httpMethod = "POST"
            request.httpBody = viewRouter.nextPost.data(using: .utf8)
            webView.load(request)                
        } else {
            let urlString = viewRouter.nextDomain + "/" + viewRouter.nextPage
            let request = URLRequest(url: URL(string: urlString)!)
            webView.load(request)
        }            
    }
    
    // Function to go back to login page
    func gotoLoginView(webView: WKWebView)
    {
        viewRouter.currentPage = .loginPage
    }
    
    class Coordinator : NSObject,WKNavigationDelegate {
        var parent: WebView
        var delegate: WebViewHandlerDelegate?
        var valueSubscriber: AnyCancellable? = nil
        var webViewNavigationSubscriber: AnyCancellable? = nil
        
        init(_ WKWebView: WebView) { // was uiWebview
            self.parent = WKWebView // was uiWebview
            self.delegate = parent
        }
        
        deinit {
            valueSubscriber?.cancel()
            webViewNavigationSubscriber?.cancel()
        }
        
        func webView(_ webView: WKWebView,didFinish navigation: WKNavigation!) {
            // Page loaded so no need to show loader anymore
            self.parent.viewmodel.showLoader.send(false)
        }
        
        /* Here I implemented most of the WKWebView's delegate functions so that you can kNow them and
         can use them in different necessary purposes */
        
        func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
            // Hides loader
            self.parent.viewmodel.showLoader.send(false)
        }
        
        func webView(_ webView: WKWebView,didFail navigation: WKNavigation!,withError error: Error) {
            // Hides loader
            self.parent.viewmodel.showLoader.send(false)
        }
        
        func webView(_ webView: WKWebView,didCommit navigation: WKNavigation!) {
            // Shows loader
            self.parent.viewmodel.showLoader.send(true)
        }
        
        func webView(_ webView: WKWebView,didStartProvisionalNavigation navigation: WKNavigation!) {
            // Shows loader
            self.parent.viewmodel.showLoader.send(true)
        }
        
        // This function is essential for intercepting every navigation in the webview
        func webView(_ webView: WKWebView,decidePolicyFor navigationAction: WKNavigationAction,decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
            // Suppose you don't want your user to go a restricted site
            // Here you can get many information about new url from 'navigationAction.request.description'
                    decisionHandler(.allow)
                    return
        }
    }
}

// MARK: - Extensions
extension WebView.Coordinator: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController,didReceive message: WKScriptMessage) {
        // Make sure that your passed delegate is called
        if message.name == "iOSNative" {
            if let body = message.body as? [String: Any?] {
                delegate?.receivedJsonValueFromWebView(value: body)
            } else if let body = message.body as? String {
                delegate?.receivedStringValueFromWebView(value: body)
            }
        }
    }
}

appMyApp.swift

//
//  appApp.swift
//

import SwiftUI

@main
struct appApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    @StateObject var viewRouter = ViewRouter()
    
    var body: some Scene {
        WindowGroup {
            MotherView().environmentObject(viewRouter)
            .onopenURL { url in
                var urlPage = ""
                let urlString = url.absoluteString
                let patternUrl = "^(http|https):\\/\\/(.*).example.com\\/(?<page>.*)$"
                let regex = try! NSRegularExpression(pattern: patternUrl,options: .caseInsensitive)
                if let match = regex.firstMatch(in: urlString,options: [],range: NSRange(location: 0,length: urlString.utf16.count)) {
                    if let pageRange = Range(match.range(withName: "page"),in: urlString) {
                        urlPage = String(urlString[pageRange])
                    }
                }
                viewRouter.nextPage = urlPage
                viewRouter.currentPage = .websitePage
            }
        }
    }
}

ViewRouter.swift

//
//  ViewRouter.swift
//

import SwiftUI
import WebKit

class ViewRouter: ObservableObject {
    
    @Published var currentPage: Page = .loginPage
    
    // Language
    
    // URL String and Post data that will be called next time WebsiteView is loaded
    var nextDomain: String = "https://www.example.com"
    var nextPage: String = ""

    // Manages Geolocation
    var navigatorGeolocation = NavigatorGeolocation()
}

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)