将 tapAction 从 SwiftUI 按钮操作发送到 UIView 函数

Send tapAction from SwiftUI button action to UIView function

我正在尝试找到一种方法来触发一个操作,当在 swiftUI 中点击一个按钮时,该操作将调用我的 UIView 中的一个函数。

Here's my setup:

foo()(UIView) 需要 运行 当 Button(SwiftUI) 被点击时

我的自定义 UIView class 使用 AVFoundation 框架

class SomeView: UIView {

    func foo() {}
}

要在 swiftUI 中使用我的 UIView,我必须将它包装在 UIViewRepresentable

struct SomeViewRepresentable: UIViewRepresentable {

    func makeUIView(context: Context) -> CaptureView {
        SomeView()
    }

    func updateUIView(_ uiView: CaptureView, context: Context) {        
    }
}

托管我的 UIView() 的 SwiftUI 视图

struct ContentView : View {

    var body: some View {
        VStack(alignment: .center, spacing: 24) {
            SomeViewRepresentable()
                .background(Color.gray)
            HStack {
                Button(action: {
                    print("SwiftUI: Button tapped")
                   // Call func in SomeView()
                }) {
                    Text("Tap Here")
                }
            }
        }
    }
}

您可以将自定义 UIView 的实例存储在您的可表示结构中(SomeViewRepresentable 此处)并在点击操作时调用其方法:

struct SomeViewRepresentable: UIViewRepresentable {

  let someView = SomeView() // add this instance

  func makeUIView(context: Context) -> SomeView { // changed your CaptureView to SomeView to make it compile
    someView
  }

  func updateUIView(_ uiView: SomeView, context: Context) {

  }

  func callFoo() {
    someView.foo()
  }
}

您的视图主体将如下所示:

  let someView = SomeViewRepresentable()

  var body: some View {
    VStack(alignment: .center, spacing: 24) {
      someView
        .background(Color.gray)
      HStack {
        Button(action: {
          print("SwiftUI: Button tapped")
          // Call func in SomeView()
          self.someView.callFoo()
        }) {
          Text("Tap Here")
        }
      }
    }
  }

为了测试它,我在 foo() 方法中添加了打印:

class SomeView: UIView {

  func foo() {
    print("foo called!")
  }
}

现在点击您的按钮将触发 foo() 并显示打印语句。

M Reza 的解决方案适用于简单的情况,但是如果您的父 SwiftUI 视图有状态更改,则每次刷新时,它都会导致您的 UIViewRepresentable 创建新的 UIView 实例,原因如下:let someView = SomeView() // add this instance。因此 someView.foo() 正在调用您创建的 SomeView 的先前实例的操作,该实例在刷新时已经过时,因此您可能看不到 UIViewRepresentable 的任何更新出现在您的父视图上。 参见:https://medium.com/zendesk-engineering/swiftui-uiview-a-simple-mistake-b794bd8c5678

更好的做法是在调用其函数时避免创建和引用 UIView 的实例。

我对 M Reza 解决方案的适应是通过父视图的状态更改间接调用该函数,这会触发 updateUIView :

  var body: some View {
    @State var buttonPressed: Bool = false
    VStack(alignment: .center, spacing: 24) {

      //pass in the @State variable which triggers actions in updateUIVIew
      SomeViewRepresentable(buttonPressed: $buttonPressed)
        .background(Color.gray)
      HStack {
        Button(action: {
          buttonPressed = true
        }) {
          Text("Tap Here")
        }
      }
    }
  }

struct SomeViewRepresentable: UIViewRepresentable {
  @Binding var buttonPressed: Bool 

  func makeUIView(context: Context) -> SomeView {
    return SomeView()
  }

  //called every time buttonPressed is updated
  func updateUIView(_ uiView: SomeView, context: Context) {
    if buttonPressed {
        //called on that instance of SomeView that you see in the parent view
        uiView.foo()
        buttonPressed = false
    }
  }
}

这是使用桥接的另一种方法 class。

//SwiftUI
struct SomeView: View{
  var bridge: BridgeStuff?

  var body: some View{
    Button("Click Me"){
      bridge?.yo()
    }
  }
}

//UIKit or AppKit (use NS instead of UI)
class BridgeStuff{
  var yo:() -> Void = {}
}

class YourViewController: UIViewController{

  override func viewDidLoad(){
    let bridge = BridgeStuff()
    let view = UIHostingController(rootView: SomeView(bridge: bridge))
    bridge.yo = { [weak self] in
      print("Yo")
      self?.howdy()
    }
  }

  func howdy(){
    print("Howdy")
  }
}