Search code examples
swiftxcodeswiftuimapkitswift5

SwiftUI: How to put NavigationLink in MapPin or MapMarker


I have already decode my JSON API and successfully display the location on the map with MapAnnotations and put NavigationLink to see the detail on it. But somehow, when I zoomed out the map to see all marked locations, suddenly my view becomes very laggy with simulator and real iPhone 8 (maybe because I have 100+ annotations on the map?). And then I tried to use MapMarker and the view becomes more smoother, but the problem is now I can't put NavigationLink on MapMarker as well as MapPin. Is there a proper way to display marker/annotations on the map and NavigationLink without making the view lag??

Here is my LocationManager Code to track user's location

import Foundation
import CoreLocation

class LocationManager: NSObject, ObservableObject {
    
    private let locationManager = CLLocationManager()
    @Published var location: CLLocation?
    
    override init() {
        super.init()
        
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.distanceFilter = kCLDistanceFilterNone
        locationManager.requestAlwaysAuthorization()
        locationManager.startUpdatingLocation()
        locationManager.delegate = self
    }
    
}

extension LocationManager: CLLocationManagerDelegate {
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        
        guard let location = locations.last else { return }
        locationManager.stopUpdatingLocation()
        
        DispatchQueue.main.async {
            self.location = location
        }
        
    }
    
}

My ContentView to display the Map and Show the annotations

import SwiftUI
import MapKit
import Combine

struct ContentView: View {
    
    var body: some View {
        NavigationView{
            ServiceLocation()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    
    static var previews: some View {
        ContentView()
    }
}

extension MKCoordinateRegion {
    
    static var defaultRegion: MKCoordinateRegion {
        MKCoordinateRegion(center: CLLocationCoordinate2D.init(latitude: -0.789275, longitude: 113.921327), latitudinalMeters: 5000, longitudinalMeters: 5000)
    }
    
}

//MARK: MAP VIEW
struct ServiceLocation: View{
    
    @State var serviceLocations: [ServiceLocationJSON] = []
    @ObservedObject private var locationManager = LocationManager()
    @State private var region = MKCoordinateRegion.defaultRegion
    @State private var cancellable: AnyCancellable?
    
    private func setCurrentLocation() {
        cancellable = locationManager.$location.sink { location in
            region = MKCoordinateRegion(center: location?.coordinate ?? CLLocationCoordinate2D(), latitudinalMeters: 20000, longitudinalMeters: 20000)
        }
    }
    
    var body: some View{
        GeometryReader{ geometry in
            VStack{
                if locationManager.location != nil {
                    Map(coordinateRegion: $region, interactionModes: .all, showsUserLocation: true, userTrackingMode: .none, annotationItems: serviceLocations) { location in
                        MapAnnotation(coordinate: CLLocationCoordinate2D(latitude: location.LATITUDE, longitude: location.LONGITUDE)){
                            NavigationLink(destination: serviceLocationDetail(serviceLocations: location)){
                                Image(systemName: "mappin")
                                    .resizable()
                                    .scaledToFit()
                                    .frame(width: geometry.size.width / 15, height: geometry.size.height / 15)
                            }
                        }
                    }
                } else {
                    VStack{
                        Spacer()
                        ProgressView()
                        Spacer()
                    }
                }
            }.onAppear{
                setCurrentLocation()
                getServiceLocation(url: "https://my.api.mockaroo.com/latlong.json?key=e57d0e40"){ (serviceLocations) in
                    self.serviceLocations = serviceLocations
                }
            }
            .navigationTitle("Service")
            .navigationBarTitleDisplayMode(.inline)
            
        }
    }
}

//MARK: DETAIL VIEW
struct serviceLocationDetail: View{
    var serviceLocations: ServiceLocationJSON
    
    var body: some View{
        
        VStack{
            if serviceLocations.DEALER_NAME.isEmpty{
                VStack{
                    Spacer()
                    ProgressView()
                    Spacer()
                }
            }else{
                VStack(alignment: .leading, spacing: 10){
                    
                    Text(serviceLocations.DEALER_NAME)
                        .fontWeight(.medium)
                        .padding(.leading, 10)
                    Text(serviceLocations.DEALER_ADDRESS)
                        .padding(.leading, 10)
                    HStack(spacing: 5){
                        Image(systemName: "phone.fill")
                        Text(serviceLocations.PHONE)
                    }.padding(.leading, 10)
                    
                    Spacer()
                    
                }.navigationBarTitle(serviceLocations.DEALER_NAME)
            }
        }
        Spacer()
    }
}

//MARK: JSON MODEL
struct ServiceLocationJSON: Identifiable, Decodable{
    var id: Int
    var LATITUDE: Double
    var LONGITUDE: Double
    var DEALER_NAME: String
    var DEALER_ADDRESS: String
    var DEALER_PICTURE: String
    var PHONE: String
}

//MARK: DECODE JSON MODEL
func getServiceLocation(url: String, completion: @escaping ([ServiceLocationJSON])->()){
    let session = URLSession(configuration: .default)
    session.dataTask(with: URL(string: url)!){ (data, _, err) in
        if err != nil{
            print(err!.localizedDescription)
            return
        }
        do{
            let serviceLocations = try
                JSONDecoder().decode([ServiceLocationJSON].self, from: data!)
            completion(serviceLocations)
        }
        catch{
            print(error)
        }
    }.resume()
}

Build with Xcode 12 and Swift 5


Solution

  • Nevermind, I just solved it. I just need to change coordinateRegion: $region to coordinateRegion: .constant(region)