Xun Ruan
Xun Ruan

Reputation: 117

SwiftUI why a variable is passed on other views automatically?

How is my MapView "annotations" variable get updated every time I change my "locations" variable in my ContentView? I googled that swift array is a value type, and I don't even need to use binding on the "annotations" and bind to the "locations" for "annotations" get to know when "locations" changes, why is that?

import SwiftUI
import MapKit

struct ContentView: View {
    @State var centerCoordinate = CLLocationCoordinate2D()
    @State var locations = [MKPointAnnotation]()
    var body: some View {
        ZStack{
            MapView(centerCoordinate: $centerCoordinate, annotations: locations)
            Circle()
                .fill(Color.blue)
                .opacity(0.5)
                .frame(width: 32, height: 32, alignment: .center)
            VStack{
                Spacer()
                HStack{
                    Spacer()
                    Button(action: {
                        let newLocation = MKPointAnnotation()
                        newLocation.coordinate = centerCoordinate
                        locations.append(newLocation)
                    }){
                        Image(systemName: "plus")
                    }
                    .padding()
                    .background(Color.black.opacity(0.75))
                    .foregroundColor(.white)
                    .font(.title)
                    .clipShape(Circle())
                    .padding([.trailing, .bottom])
                }
            }
        }
        .edgesIgnoringSafeArea(.all)
    }
}
struct MapView: UIViewRepresentable{
    @Binding var centerCoordinate: CLLocationCoordinate2D
    var annotations: [MKPointAnnotation]
    
    func makeUIView(context: Context) -> MKMapView {
        let mapView = MKMapView()
        mapView.delegate = context.coordinator
        return mapView
    }
    // Callback function - when anything being sent to UIViewRepresentabel struct is changed
    func updateUIView(_ uiView: UIViewType, context: Context) {
        print("Updating UIView")
        if annotations.count != uiView.annotations.count{
            uiView.removeAnnotations(uiView.annotations)
            uiView.addAnnotations(annotations)
        }
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(self)
    }
    
    class Coordinator: NSObject, MKMapViewDelegate {
        let parent: MapView
        init(_ parent: MapView) {
            self.parent = parent
        }
        
        func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
            parent.centerCoordinate = mapView.centerCoordinate
        }
    }
}

extension MKPointAnnotation{
    static var example: MKPointAnnotation{
        let point = MKPointAnnotation()
        point.coordinate = CLLocationCoordinate2D(latitude: 51.5, longitude: -0.13)
        point.title = "London"
        point.subtitle = "The home of 2012 Summer Olympics"
        return point
    }
}

Upvotes: 0

Views: 70

Answers (1)

Jan Cássio
Jan Cássio

Reputation: 2269

Not sure if I understood your question but let me try.

First you have this state in your View:

@State var locations = [MKPointAnnotation]()

Next, you assign its value to:

MapView(centerCoordinate: $centerCoordinate, annotations: locations)

This means, every time you change locations, SwiftUI will re-render the MapView with new values.

Any UIViewRepresentable have to implement the func updateUIView in order to receive these changes when SwiftUI re-render the respective component.

So, every time your MapView being rendered, this function is going to be trigged:

func updateUIView(_ uiView: UIViewType, context: Context) {
  print("Updating UIView")
  if annotations.count != uiView.annotations.count{
    uiView.removeAnnotations(uiView.annotations)
    uiView.addAnnotations(annotations)
  }
}

The uiView.annotations and annotations are note related each other. At the moment uiView.annotations changes, it just make side effects in the view itself and don't update the given @State because it could create an infinity rendering loop.

Upvotes: 1

Related Questions