Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you call a method on a UIView from outside the UIViewRepresentable in SwiftUI?

I want to be able to pass a reference to a method on the UIViewRespresentable (or perhaps it’s Coordinator) to a parent View. The only way I can think to do this is by creating a field on the parent View struct with a class that I then pass to the child, which acts as a delegate for this behaviour. But it seems pretty verbose.

The use case here is to be a able to call a method from a standard SwiftUI Button that will zoom the the current location in a MKMapView that’s buried in a UIViewRepresentable elsewhere in the tree. I don’t want the current location to be a Binding as I want this action to be a one off and not reflected constantly in the UI.

TL;DR is there a standard way of having a parent get a reference to a child in SwiftUI, at least for UIViewRepresentables? (I understand this is probably not desirable in most cases and largely runs against the SwiftUI pattern).

like image 816
RichieAHB Avatar asked Sep 21 '19 10:09

RichieAHB


1 Answers

I struggled with that myself, here's what worked using Combine and PassthroughSubject:

struct OuterView: View {
  private var didChange = PassthroughSubject<String, Never>()

  var body: some View {
    VStack {
      // send the PassthroughSubject over
      Wrapper(didChange: didChange)
      Button(action: {
        self.didChange.send("customString")
      })
    }
  }
}

// This is representable struct that acts as the bridge between UIKit <> SwiftUI
struct Wrapper: UIViewRepresentable {
  var didChange: PassthroughSubject<String, Never>
  @State var cancellable: AnyCancellable? = nil

  func makeUIView(context: Context) → SomeView {
    let someView = SomeView()
    // ... perform some initializations here

    // doing it in `main` thread is required to avoid the state being modified during
    // a view update
    DispatchQueue.main.async {
      // very important to capture it as a variable, otherwise it'll be short lived.
      self.cancellable = didChange.sink { (value) in
        print("Received: \(value)")
        
        // here you can do a switch case to know which method to call
        // on your UIKit class, example:
        if (value == "customString") {
          // call your function!
          someView.customFunction()
        }
      }
    }

    return someView
  }
}

// This is your usual UIKit View
class SomeView: UIView {
  func customFunction() {
    // ...
  }
}
like image 169
KBog Avatar answered Nov 04 '22 17:11

KBog