Search code examples
delegatesswiftuigoogle-maps-markersgoogle-maps-sdk-ios

How to change UI state when user touch GoogleMaps' Marker in SwiftUI?


This question is about SwiftUI.

I'm trying to show a map and allow the user to touch any marker available. When it happens, I wish to change a text on my view, reflecting that user's action.

After a lot of search, I think the solution can be somewhere near Observable protocol, but I just can't figure out the right way for doing that. Here's my code:


struct Home: View {

    // Here's the attribute I want to be changed when user touches the marker
    var selectedMarker: GMSMarker?

    var body: some View {

            VStack(spacing: 0) {

                // Condition to be applied when user touches the marker                                
                if (selectedMarker == nil){
                    Text("No marker selected").padding()
                }else{
                    Text("Now, there's a marker selected").padding()
                }

                GoogleMapsHome()

        }
        .navigationBarBackButtonHidden(true)
        .navigationBarTitle(Text("Marker question"), displayMode: .inline)

    }

}

struct Home_Previews: PreviewProvider {
    static var previews: some View {
        Home()
    }
}

Here's the GoogleMaps definition:

struct GoogleMapsHome: UIViewRepresentable {

    private let zoom: Float = 18

    // Just for didactic purposes. Later, I'm going to use LocationManager
    let lat: Double = -15.6692660716233
    let lng: Double = -47.83980712156295

    func makeUIView(context: Self.Context) -> GMSMapView {

        let camera = GMSCameraPosition.camera(
            withLatitude: lat,
            longitude: lng,
            zoom: zoom)

        let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)

        mapView.mapType = .hybrid

        mapView.delegate = context.coordinator

        return mapView

    }

    func updateUIView(_ mapView: GMSMapView, context: Context) {

        mapView.animate(toLocation: CLLocationCoordinate2D(latitude: lat, longitude: lng))

        let position = CLLocationCoordinate2D(latitude: lat, longitude: lng)
        let marker = GMSMarker(position: position)

        marker.title = "You"

        marker.map = mapView

    }

    func makeCoordinator() -> Coordinator {
       Coordinator(owner: self)
    }

    class Coordinator: NSObject, GMSMapViewDelegate, ObservableObject {

        let owner: GoogleMapsHome       // access to owner view members,

        init(owner: GoogleMapsHome) {
         self.owner = owner
        }

        @Published var selectedMarker: GMSMarker? {
            willSet { objectWillChange.send() }
        }

        func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {

            print("A marker has been touched by the user")

            self.selectedMarker = marker

            return true

        }

    }
}

I hope someone can help me and, later, this question become useful for anyone with the same need.

Best regards!


Solution

  • After a while, I found a way to solve it.

    The keywords for that are "Coordinator" and "Binding".

    Of course, I'm not sure if this is the right way, or the best, but it worked, at least.

    
    import Foundation
    import SwiftUI
    import GoogleMaps
    
    struct Home: View {
    
        @State var selectedMarker: GMSMarker?
    
        var body: some View {
    
                VStack(spacing: 0) {
    
                    if (selectedMarker == nil){
                        Text("No marker selected").padding()
                    }else{
                        Text("There's a marker selected").padding()
                    }
    
                    GoogleMapsHome(selectedMarker: self.$selectedMarker)
    
            }
            .navigationBarBackButtonHidden(true)
            .navigationBarTitle(Text("Map Test"), displayMode: .inline)
    
        }
    
    }
    
    struct Home_Previews: PreviewProvider {
        static var previews: some View {
            Home()
        }
    }
    
    struct GoogleMapsHome: UIViewRepresentable {
    
        private let zoom: Float = 18
    
        let lat: Double = -15.6692660716233
        let lng: Double = -47.83980712156295
    
        @Binding var selectedMarker: GMSMarker?
    
        func makeCoordinator() -> Coordinator {
            return Coordinator(
                owner: self,
                selectedMarker: $selectedMarker)
        }
    
        func makeUIView(context: Self.Context) -> GMSMapView {
    
            let camera = GMSCameraPosition.camera(
                withLatitude: lat,
                longitude: lng,
                zoom: zoom)
    
            let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
    
            mapView.mapType = .hybrid
    
            mapView.delegate = context.coordinator
    
            return mapView
    
        }
    
        func updateUIView(_ mapView: GMSMapView, context: Context) {
    
            mapView.animate(toLocation: CLLocationCoordinate2D(latitude: lat, longitude: lng))
    
            let position = CLLocationCoordinate2D(latitude: lat, longitude: lng)
            let marker = GMSMarker(position: position)
    
            marker.title = "You"
    
            marker.map = mapView
    
        }
    
    
        class Coordinator: NSObject, GMSMapViewDelegate, ObservableObject {
    
            let owner: GoogleMapsHome       // access to owner view members,
    
            @Binding var selectedMarker: GMSMarker?
    
            init(
                owner: GoogleMapsHome,
                selectedMarker: Binding<GMSMarker?>
            ) {
    
                self.owner = owner
    
                _selectedMarker = selectedMarker
    
            }
    
            func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
    
                print("A marker has been touched")
    
                self.selectedMarker = marker
    
                return true
    
            }
    
        }
    }