Search code examples
swiftuikitmapkitswiftuicombine

How to update UIViewRepresentable with ObservableObject


I'm trying to learn Combine with SwiftUI and I'm struggling how to update my view (from UIKit) with ObservableObject (previously BindableObject). The issue is that, obviously, method updateUIView will not fire once the @Published object sends the notification it was changed.

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 {
    @ObservedObject var dataSource = DataSource()

    func makeUIView(context: Context) -> MKMapView {
        MKMapView(frame: .zero)
    }

    func updateUIView(_ view: MKMapView, context: Context) {
        let newestCoordinate = dataSource.locationCoordinates.last ?? CLLocationCoordinate2D(latitude: 52, longitude: 16)
        let annotation = MKPointAnnotation()
        annotation.coordinate = newestCoordinate
        annotation.title = "Test #\(dataSource.value)"
        view.addAnnotation(annotation)
    }
}

How to bind that locationCoordinates array to the view in such a way, that a new point is in fact added each time it refreshes?


Solution

  • 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)
        }
    }