Search code examples
iosswiftuikitswiftuiuiviewrepresentable

How to use extended function of the custom UIView in a wrapper Swift View?


(tested in Xcode 11.3, Swift 5.1.3)

I want to extend UIView, wrap it with UIViewRepresentable, and use it as a Swift View. However, it is difficult for me to access extended functions of the custom UIView from the wrapper Swift View.

class UICameraCaptureImageView: UIImageView, AVCaptureVideoDataOutputSampleBufferDelegate {
  @State var capturedImage: UIImage? = UIImage(named: "default_placeholder")

  func startCameraCapture()  {  
    // start camera capture when it is ready
  }

  // AVCaptureVideoDataOutputSampleBufferDelegate delegate method follows
  // ...

}
struct CameraCaptureImageView: UIViewRepresentable {

  // cannot set containedUIView in makeUIView/updateUIView, and they are not mutating method
  private var containedUIView: UICameraCaptureImageView?

  func makeUIView(context: UIViewRepresentableContext<CameraCaptureImageView>) -> 
      UICapturedImageView {
    UICapturedImageView()     
  }

  func updateUIView(_ uiView: UICapturedImageView, 
      context: UIViewRepresentableContext< CameraCaptureImageView>) {
    uiView.image = capturedImage
  }

  func startCameraCapture()  {  
    // redirect to UICameraCaptureImageView.startCameraCapture(), 
    // but cannot set self.containedUIView
    guard self.containedUIView != nil else {
      print("The containedUICaptureView doesn't exist")
      return
    }
    self.containedUIView?.startCameraCapture()
  }
}

At first, though it's a kind of stateful strategy, I tried to declare a member variable in CameraCaptureImageView and set the UICameraCaptureImageView instance when it is made. But as you see, makeUIView() is not declared as mutating method so that I cannot mutate any members of CameraCaptureImageView.

How can I access the extended custom function startCameraCapture() in my UIView subclass from the UIViewRepresentable wrapper? Or, is there any stateless, decent solution to use extended old UIView in SwiftUI?


Solution

  • You are supposed to create a Coordinator that manages this shuttling for you. It's a class and therefore is not strictly beholden to non-mutating semantics.

    struct CameraCaptureImageView: UIViewRepresentable {
    
      func makeUIView(context: UIViewRepresentableContext<CameraCaptureImageView>) -> 
          UICapturedImageView {
        return UICapturedImageView()     
      }
    
      func makeCoordinator() -> Coordinator {
        return .init(self)
      }
    }
    
    extension CameraCaptureImageView {
    
      // add any delegate/protocol conformances here, it's just an object!
      private class Coordinator  {
        let cameraCaptureImageView: CameraCaptureImageView
    
        init(_ cameraCaptureImageView: CameraCaptureImageView) {
           // CameraCaptureImageView is a `struct`, so it's a copy!
           self.cameraCaptureImageView = cameraCaptureImageView
        }
    
        // now here is all your UIView specific logic
      }
    }
    

    Need to signal out? Add a closure on your View that your Coordinator can call on certain events.

    struct CameraCaptureImageView: ... {
    
        let onSomeEvent: (Event) -> Void
    }
    
    class Coordinator {
    
        let cameraCaptureImageView: ...
    
        func view(_ view: UIViewOfSomeKind, didReceive event: Event) {
          cameraCaptureImageView.onEvent(event)
        }
    }