【问题标题】:SwiftUI View init called multiple times多次调用 SwiftUI View init
【发布时间】:2021-08-03 09:49:18
【问题描述】:

我对 SwiftUI 还是很陌生。我有一个非常简单的看法。它只是一个根视图,其中包含一个包装在 UIViewRepresentable 中的 WKWebView。我的问题是,打开视图时 UIViewRepresentable 的 init 方法被调用了 6 次。这意味着 WKWebView 被初始化了 6 次,并且我所有的初始化代码(设置 JS 回调,...)被调用了 6 次。我在根视图 MyWebView 和子视图 WebView(UIViewRepresentable)的初始化函数中添加了打印语句。根视图init只被调用一次,而子视图的init被调用6次。这是正常的吗?还是我做错了什么?

struct MyWebView:查看 {

@ObservedObject private var viewModel = WebViewModel()

init() {
    print("root init")
}

var body: some View {
        VStack(alignment: .leading, spacing: 0, content: {
            WebView(viewModel: viewModel)
        })
        .navigationBarTitle("\(viewModel.title)", displayMode: .inline)
        .navigationBarBackButtonHidden(true)
} }

struct WebView: UIViewRepresentable {

var wkWebView: WKWebView!

init(viewModel: WebViewModel) {
    print("webview init")
    doWebViewInitialization(viewModel: viewModel)
}

func makeUIView(context: UIViewRepresentableContext<WebView>) -> WKWebView {
    let request = URLRequest(url: URL(string: "https://www.google.com")!, cachePolicy: .returnCacheDataElseLoad)
    wkWebView.load(request)

    return wkWebView
}

}

【问题讨论】:

  • 您是否在代码中的其他位置调用并更改“viewModel”中的某些内容?
  • @workingdog:不,ViewModel 只做业务逻辑,与视图无关。

标签: ios swiftui


【解决方案1】:

我没有收到您多次调用 UIViewRepresentable 的 init 方法的问题。 我稍微修改了您的 WebView,这就是我测试答案的方式:

import SwiftUI
import Foundation
import WebKit

@main
struct TestSApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct ContentView: View {
    var body: some View {
        NavigationView {
            MyWebView()
        }.navigationViewStyle(StackNavigationViewStyle())
    }
}

// for testing
class WebViewModel: ObservableObject {
    @Published var title = ""
}

struct WebView: UIViewRepresentable {
    let wkWebView = WKWebView()
    
    init(viewModel: WebViewModel) {
        print("\n-----> webview init")
      //  doWebViewInitialization(viewModel: viewModel)
    }
    
    func makeUIView(context: UIViewRepresentableContext<WebView>) -> WKWebView {
        if let url = URL(string: "https://www.google.com") {
            let request = URLRequest(url: url, cachePolicy: .returnCacheDataElseLoad)
            wkWebView.load(request)
        }
        return wkWebView
    }
    
    func updateUIView(_ webview: WKWebView, context: UIViewRepresentableContext<WebView>) { }
}

struct MyWebView: View {
    @ObservedObject private var viewModel = WebViewModel()
    
    init() {
        print("\n-----> root init")
    }
    
    var body: some View {
        VStack(alignment: .leading, spacing: 0, content: {
            WebView(viewModel: viewModel)
        })
            .navigationBarTitle("\(viewModel.title)", displayMode: .inline)
            .navigationBarBackButtonHidden(true)
    }
}

这会使“doWebViewInitialization”留下一个可能的问题点。

【讨论】:

  • 谢谢你的例子。你是对的,问题似乎是 doWebViewInitialization 函数。我在那里做了很多事情,比如设置委托、禁用缩放、注入 js ......其中一些事情似乎会触发根视图中的更改,从而导致多个 init 调用。
【解决方案2】:

您必须在编写代码时假设 SwiftUI 中 View 的初始化程序将被多次调用。

在这种情况下,您在makeUIView(context:)中编写初始化过程。

见: https://developer.apple.com/documentation/swiftui/uiviewrepresentable/makeuiview(context:)


比如我基于this answer写了如下代码。我在这个引用的代码中添加了一个切换高度按钮。

-----&gt; makeUIView 日志只输出一次, 但每次按下切换按钮时都会输出-----&gt; webview init 日志。

import SwiftUI
import WebKit

struct ContentView: View {
  var body: some View {
    MyWebView()
  }
}

class WebViewModel: ObservableObject {
  @Published var title = ""
}

struct WebView: UIViewRepresentable {
  let wkWebView = WKWebView()

  init(viewModel: WebViewModel) {
    print("\n-----> webview init")
  }

  func makeUIView(context: UIViewRepresentableContext<WebView>) -> WKWebView {
    print("\n-----> makeUIView")
    if let url = URL(string: "https://www.google.com") {
      let request = URLRequest(url: url, cachePolicy: .returnCacheDataElseLoad)
      wkWebView.load(request)
    }
    
    return wkWebView
  }

  func updateUIView(_ webview: WKWebView, context: UIViewRepresentableContext<WebView>) { }
}

struct MyWebView: View {
  @State private var toggleHight = false
  @ObservedObject private var viewModel = WebViewModel()

  init() {
    print("\n-----> root init")
  }

  var body: some View {
    VStack {
      WebView(
        viewModel: viewModel
      )
      .frame(
        height: { toggleHight ? 600 : 300 }()
      )

      Button(
        "toggle",
        action: {
          toggleHight.toggle()
        }
      )
    }
  }
}

此外,我在编写示例代码后意识到WebView: UIViewRepresentable 不应该有wkWebView 的实例变量。

请在makeUIView(context:) 中完成所有操作(创建实例和配置),如下所示。 这是因为每次调用初始化程序时都会重新创建实例变量。

import SwiftUI
import WebKit

struct ContentView: View {
  var body: some View {
    MyWebView()
  }
}

class WebViewModel: ObservableObject {
  @Published var title = ""
}

struct WebView: UIViewRepresentable {
  init(viewModel: WebViewModel) {
    print("\n-----> webview init")
  }

  func makeUIView(context: UIViewRepresentableContext<WebView>) -> WKWebView {
    print("\n-----> makeUIView")
    let wkWebView = WKWebView()
    if let url = URL(string: "https://www.google.com") {
      let request = URLRequest(url: url, cachePolicy: .returnCacheDataElseLoad)
      wkWebView.load(request)
    }

    return wkWebView
  }

  func updateUIView(_ webview: WKWebView, context: UIViewRepresentableContext<WebView>) { }
}

struct MyWebView: View {
  @State private var toggleHight = false
  @ObservedObject private var viewModel = WebViewModel()

  init() {
    print("\n-----> root init")
  }

  var body: some View {
    VStack {
      WebView(
        viewModel: viewModel
      )
      .frame(
        height: { toggleHight ? 600 : 300 }()
      )

      Button(
        "toggle",
        action: {
          toggleHight.toggle()
        }
      )
    }
  }
}

当我使用UIViewControllerRepresentable 进行开发时,我一直在与这种严格的约束作斗争。在同事的帮助下,我完成了代码。


您的代码已被调用 6 次,因此可能存在一些问题。但我无法从您提供的代码中看出问题所在。

initSwiftUI 中被多次调用是很常见的。我们需要编写代码来处理这个问题。如果您的init 被频繁调用,您可能需要寻找根本原因。我引用的代码和我写的代码在启动时只有一次。

【讨论】:

  • 你复制了我的答案并添加了一个按钮,这是你做的吗?
  • @workingdog 在您的代码示例中,确实只调用了一次,但我做了一个多次调用的示例。
  • @workingdog 我在回答中已经提到我使用您的代码作为参考。如果有任何问题,请告诉我。
  • 对不起,如果我听起来很突然。完全没有问题。您的代码确实有目的,并且很好地突出了您的观点。
  • 谢谢!这是一个很好的例子。看来我完全误解了视图生命周期。
猜你喜欢
  • 2021-06-05
  • 2020-06-18
  • 2021-05-09
  • 1970-01-01
  • 1970-01-01
  • 2010-11-11
  • 1970-01-01
  • 2019-05-01
  • 2020-08-27
相关资源
最近更新 更多