How to update UIViewRepresentable with ObservableObject
I'm gonna provide a general solution for any UI/NS view representable using combine. There are performance benefits to my method.
- Created an Observable Object and wrap the desired properties with @Published wrapper
- Inject the Observed object via the updateView method in the view representable using a method you'll make in step 3
- Subclass the desired view with the view model as a parameter. Create an addViewModel method and use combine operators/ subscribers and add them to cancellable.
Note - Works great with environment objects.
struct swiftUIView : View {
@EnvironmentObject var env : yourViewModel
...
...
UIViewRep(wm : env)
}
struct UIViewRep : UIViewRepresentable {
var wm : yourViewModel
func makeUIView {
let yv = yourView()
yv.addViewModel(wm)
return yv
}}
class yourView : UIView {
var viewModel : yourViewModel?
var cancellable = Set<AnyCancellable>()
...
...
func addViewModel( _ wm : yourViewModel) {
self.viewModel = wm
self.viewModel?.desiredProperty
.sink(receiveValue: { [unowned self] w in
print("Make changes with ", w)
}).store(in: &cancellable)
}
}
this solution worked for me but with EnvironmentObject https://gist.github.com/svanimpe/152e6539cd371a9ae0cfee42b374d7c4
To make sure your ObservedObject
does not get created multiple times (you only want one copy of it), you can put it outside your UIViewRepresentable
:
import SwiftUI
import MapKit
struct ContentView: View {
@ObservedObject var dataSource = DataSource()
var body: some View {
MyView(locationCoordinates: dataSource.locationCoordinates, value: dataSource.value)
}
}
class DataSource: ObservableObject {
@Published var locationCoordinates = [CLLocationCoordinate2D]()
var value: Int = 0
init() {
Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { timer in
self.value += 1
self.locationCoordinates.append(CLLocationCoordinate2D(latitude: 52, longitude: 16+0.1*Double(self.value)))
}
}
}
struct MyView: UIViewRepresentable {
var locationCoordinates: [CLLocationCoordinate2D]
var value: Int
func makeUIView(context: Context) -> MKMapView {
MKMapView(frame: .zero)
}
func updateUIView(_ view: MKMapView, context: Context) {
print("I am being called!")
let newestCoordinate = locationCoordinates.last ?? CLLocationCoordinate2D(latitude: 52, longitude: 16)
let annotation = MKPointAnnotation()
annotation.coordinate = newestCoordinate
annotation.title = "Test #\(value)"
view.addAnnotation(annotation)
}
}