Home > Software design >  UIViewRepresentable and Map SwiftUI
UIViewRepresentable and Map SwiftUI

Time:10-17

I am using UIKit MKMapView in SwiftUI to have more functionality. I pass the map region to the UIViewRepresentable via Binding, so I can update the UIKit view from the SwiftUI view that uses that MKMapView. The issue is that I have a button on top of the map that is responsible for centering the map back to the user location, but every time the map region changes, the updateUIView function triggers, and the map centers it self, which leads to the map being stuck because whenever I move the map (i.e zoom, drag, rotate) the map centers itself.

How can I conditionally check if the button was clicked - and only then center the map?


updateUIView:
func updateUIView(_ uiView: MKMapView, context: Context) {
    if let userLocation = uiView.annotations.first(where: { $0 is MKUserLocation }) {
        uiView.setRegion(MKCoordinateRegion(center: userLocation.coordinate, span: MapDetails.defaultSpan), animated: true)
    } // this gets triggered everytime map region changes.
}

UIViewRepresentable:

struct MapView: UIViewRepresentable {
@Binding var mapRegion: MKCoordinateRegion
@Binding var locations: [Location]
...
}

Main SwiftUI View (Where I use the MKMapView):

struct ExploreView: View {


@StateObject private var viewModel = MapViewModel()
@State private var didAppearOnce = false

@ViewBuilder
var body: some View {
    NavigationView {
        ZStack {
            MapView(mapRegion: $viewModel.mapRegion, locations: $viewModel.locations)
                .accentColor(.pink)
            ...
}

So my question is, is there any way to conditionally check whether the button was pressed and only then center the map inside updateUIView ?

CodePudding user response:

Leave updateUIView empty, and add Combine in your viewModel

import Combine

class MapViewModel:ObservableObject {

@Published var buttonCenterPressed = false
var buttonCancellable: AnyCancellable?

}

In UIViewRepresentable add subscriber in makeUIView with mapView in parameters

struct MapView: UIViewRepresentable {
   @Binding var mapRegion: MKCoordinateRegion
   @Binding var locations: [Location]
   var viewModel: MapViewModel
 ...

 func makeUIView(context: Context) -> MKMapView {
    let mapView = MKMapView(frame: .zero)
    mapView.showsUserLocation = true
    setSubscriber(mapView) 
    return mapView 

  }

 func updateUIView(_ mapView: MKMapView, context: Context) {}

 func setSubscriber(_ mapView: MKMapView){
    viewModel.buttonCancellable = viewModel.$buttonCenterPressed.sink(receiveValue: { _ in
        
        if let userLocation = mapView.annotations.first(where: { $0 is MKUserLocation }) {
            mapView.setRegion(MKCoordinateRegion(center: userLocation.coordinate, span: MapDetails.defaultSpan), animated: true)
        }
            
        
    })
}

add viewModel in parameter to the MapView

struct ExploreView: View {


@StateObject private var viewModel = MapViewModel()
@State private var didAppearOnce = false

@ViewBuilder
var body: some View {
   NavigationView {
    ZStack {
        MapView(mapRegion: $viewModel.mapRegion, locations:   $viewModel.locations, viewModel: viewModel)
            .accentColor(.pink)
        ...
  }

now you can implement action centering button and MapView will be updated.

Button(action: {
         viewModel.buttonCenterPressed.toggle()
        }, label: {
            Text("Center")

        })
  • Related