【问题标题】:Calling a local UIViewController function from a SwiftUI View从 SwiftUI 视图调用本地 UIViewController 函数
【发布时间】:2020-02-16 00:58:48
【问题描述】:

我正在尝试从 ContentView 调用本地 ViewController 函数。该函数使用了一些局部变量,不能移到 ViewController 之外。

class ViewController: UIViewController {
    func doSomething() {...}
} 

extension ViewController : LinkViewDelegate {...}

位于不同的文件中:

struct ContentView: View {

    init() {
        viewController = .init(nibName:nil, bundle:nil)
    }
    var viewController: viewController

var body: some View {
    Button(action: {self.viewController.doSomething()}) {
            Text("Link Account")
        }
    }
}

UIViewController 不能更改为 UIViewRepresentable 之类的东西,因为 LinkViewDelegate 只能扩展 UIViewController。

【问题讨论】:

    标签: ios swift view swiftui


    【解决方案1】:

    这是我想出的一种方法,它不会导致 “在视图更新期间修改状态,这将导致未定义的行为” 问题。诀窍是将您的 ViewModel 的引用传递给 ViewController 本身,然后重置调用您的函数的布尔值,而不是在您的 UIViewControllerRepresentable 中。

    public class MyViewModel: ObservableObject {
        @Published public var doSomething: Bool = false
    }
    
    
    struct ContentView: View {
    
        @StateObject var viewModel = MyViewModel()
    
        var body: some View {
            MyView(viewModel: viewModel)
    
            Button("Do Something") {
                viewModel.doSomething = true
            }
        }
    }
    
    
    struct MyView: UIViewControllerRepresentable {
        
        @ObservedObject var viewModel: MyViewModel
        
        func makeUIViewController(context: Context) -> MyViewController {
            return MyViewController(viewModel)
        }
        
        func updateUIViewController(_ viewController: MyViewController, context: Context) {
            if viewModel.doSomething {
                viewController.doSomething()
                // boolean will be reset in viewController
            }
        }
    }
    
    
    class MyViewController: UIViewController {
        
        var viewModel: MyViewModel
        
        public init(_ viewModel: MyViewModel) {
            self.viewModel = viewModel
        }
        
        public func doSomething() {
            // do something, then reset the flag
            viewModel.doSomething = false
        }
    }
    
    

    【讨论】:

      【解决方案2】:

      所以你需要在 SwiftUI 中创建一个简单的 bool 绑定,将其翻转为 true 以触发 UIKit viewController 中的函数调用,然后将其设置回 false 直到下一次按下 swiftUI 按钮。 (至于LinkViewDelegate 防止像UIViewControllerRepresentable 这样不应该阻止你的事情,使用Coordinator 来处理代理调用。)

      struct ContentView: View {
      
          @State var willCallFunc = false
      
          var body: some View {
              ViewControllerView(isCallingFunc: $willCallFunc)
      
              Button("buttonTitle") {
                  self.willCallFunc = true
              }
          }
      }
      
      struct ViewControllerView: UIViewControllerRepresentable {
      
          @Binding var isCallingFunc: Bool
      
          func makeUIViewController(context: Context) -> YourViewController {
              makeViewController(context: context) //instantiate vc etc.
          }
      
          func updateUIViewController(_ uiViewController: YourViewController, context: Context) {
              if isCallingFunc {
                  uiViewController.doSomething()
                  isCallingFunc = false
              }
          }
      }
      

      【讨论】:

      • 我得到“在视图更新期间修改状态,这将导致未定义的行为。” XCode 中的警告,当设置 isCallingFunc = false
      • 而且还是调用了 uiViewController.doSomething() 两次
      【解决方案3】:

      您可以将 ViewController 的实例作为参数传递给 ContentView:

      struct ContentView: View {
          var viewController: ViewController // first v lowercase, second one Uppercase
      
          var body: some View {
              Button(action: { viewController.doSomething() }) { // Lowercase viewController
                  Text("Link Account")
              }
          }
      
          init() {
              self.viewController = .init(nibName:nil, bundle:nil) // Lowercase viewController
          }
      } 
      
      // Use it for the UIHostingController in SceneDelegate.swift
      window.rootViewController = UIHostingController(rootView: ContentView()) // Uppercase ContentView
      

      更新了答案以更好地适应问题。

      【讨论】:

      • 我的场景委托“let contentView = ContentView(viewController: ViewController)”出现错误 - 使用未解析的标识符“ViewController”
      • @Liv 在创建 ContentView 和 UIHostingController 之前,您必须初始化 ViewController 的实例。请确保 viewController 是用小写的 v 编写的,因为在这种情况下它是一个实例而不是一个类型。
      • 我更新了示例代码以反映更改。项目构建,但是当执行按钮操作时,项目崩溃并出现以下错误:“libc++abi.dylib: terminating with uncaught exception of type NSException”
      • @Liv 更新了答案,希望离解决方案更近一步!我希望它现在更像你需要它。
      • 我仍然无法让它工作,它仍然给我同样的错误。我已经联系了我试图实现的 API 的支持。我会看看我听到了什么。当我开始工作时会更新这篇文章:)
      猜你喜欢
      • 1970-01-01
      • 2020-12-19
      • 2020-01-17
      • 2020-10-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-06
      • 2021-12-15
      相关资源
      最近更新 更多