Please note that for this app to be able show you your location on the map you need to enable it to ask for permission to track the user's location, in the project's targets' info:
Here's also a video that illustrates the lag: https://youtube.com/shorts/DSl-umGxs20?feature=share.
That being said, if you run this SwiftUI app and allow it to track your location, tap the MapUserLocationButton
and press the buttons at the bottom, you'll see that the map lags:
import SwiftUI
import MapKit
import CoreLocation
struct ContentView: View {
let currentLocationManager = CurrentUserLocationManager()
@State private var mapLocationsManager = MapLocationsManager()
@State private var mapCameraPosition: MapCameraPosition = .automatic
var body: some View {
Map(
position: $mapCameraPosition
)
.safeAreaInset(edge: .bottom) {
VStack {
if mapLocationsManager.shouldAllowSearches {
Button("First button") {
mapLocationsManager.shouldAllowSearches = false
}
}
Button("Second button") {
mapLocationsManager.shouldAllowSearches = true
}
}
.frame(maxWidth: .infinity)
.padding()
}
.mapControls {
MapUserLocationButton()
}
}
}
@Observable class MapLocationsManager {
var shouldAllowSearches = false
}
// MARK: - Location related code -
class CurrentUserLocationManager: NSObject {
var locationManager: CLLocationManager?
override init() {
super.init()
startIfNecessary()
}
func startIfNecessary() {
if locationManager == nil {
locationManager = .init()
locationManager?.delegate = self
} else {
print(">> \(Self.self).\(#function): method called redundantly: locationManager had already been initialized")
}
}
}; extension CurrentUserLocationManager: CLLocationManagerDelegate {
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
checkLocationAuthorization()
}
}; extension CurrentUserLocationManager {
private func checkLocationAuthorization() {
guard let locationManager else { return }
switch locationManager.authorizationStatus {
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
case .restricted:
print("Your location is restricted")
case .denied:
print("Go into setting to change it")
case .authorizedAlways, .authorizedWhenInUse, .authorized:
// locationManager.startUpdatingLocation()
break
@unknown default:
break
}
}
}
I've also tried it in a UIKit app (by just embedding ContentView
in a view controller), with the same results:
import UIKit
import MapKit
import SwiftUI
import CoreLocation
class ViewController: UIViewController {
let currentLocationManager = CurrentUserLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
currentLocationManager.startIfNecessary()
let hostingController = UIHostingController(
rootView: MapView()
)
addChild(hostingController)
hostingController.view.frame = view.bounds
hostingController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(hostingController.view)
hostingController.didMove(toParent: self)
}
}
extension ViewController { struct MapView: View {
@State private var mapLocationsManager = MapLocationsManager()
@State private var mapCameraPosition: MapCameraPosition = .automatic
var body: some View {
Map(
position: $mapCameraPosition
)
.safeAreaInset(edge: .bottom) {
VStack {
if mapLocationsManager.shouldAllowSearches {
Button("First button") {
mapLocationsManager.shouldAllowSearches = false
}
}
Button("Second button") {
mapLocationsManager.shouldAllowSearches = true
}
}
.frame(maxWidth: .infinity)
.padding()
}
.mapControls {
MapUserLocationButton()
}
}
} }
extension ViewController { @Observable class MapLocationsManager {
var shouldAllowSearches = false
} }
class CurrentUserLocationManager: NSObject {
var locationManager: CLLocationManager?
func startIfNecessary() {
if locationManager == nil {
locationManager = .init()
locationManager?.delegate = self
} else {
print(">> \(Self.self).\(#function): method called redundantly: locationManager had already been initialized")
}
}
}; extension CurrentUserLocationManager: CLLocationManagerDelegate {
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
checkLocationAuthorization()
}
}; extension CurrentUserLocationManager {
private func checkLocationAuthorization() {
guard let locationManager else { return }
switch locationManager.authorizationStatus {
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
case .restricted:
print("Your location is restricted")
case .denied:
print("Go into setting to change it")
case .authorizedAlways, .authorizedWhenInUse, .authorized:
// locationManager.startUpdatingLocation()
break
@unknown default:
break
}
}
}
If you don't center the map in the user's location, you might see an occasional lag, but it seems to me that it only happens at most once.
How do I avoid these lags altogether?
Xcode 15.4, iOS 17.5, iPhone 15 Pro simulator.
The user location indicator is centered in the view's safe area.
So, since the safe area inset resizes when its content changes, you should just use a different modifier to display views above the map, such as .overlay(alignment:content:)
:
import SwiftUI
import MapKit
import CoreLocation
struct ContentView: View {
let currentLocationManager = CurrentUserLocationManager()
@State private var mapLocationsManager = MapLocationsManager()
@State private var mapCameraPosition: MapCameraPosition = .automatic
var body: some View {
Map(
position: $mapCameraPosition
)
.overlay {
VStack {
Spacer()
if mapLocationsManager.shouldAllowSearches {
Button("First button") {
mapLocationsManager.shouldAllowSearches = false
}
}
Button("Second button") {
mapLocationsManager.shouldAllowSearches = true
}
}
.frame(maxWidth: .infinity)
.padding()
}
.mapControls {
MapUserLocationButton()
}
}
}
@Observable class MapLocationsManager {
var shouldAllowSearches = false
}
class CurrentUserLocationManager: NSObject {
var locationManager: CLLocationManager?
override init() {
super.init()
startIfNecessary()
}
func startIfNecessary() {
if locationManager == nil {
locationManager = .init()
locationManager?.delegate = self
} else {
print(">> \(Self.self).\(#function): method called redundantly: locationManager had already been initialized")
}
}
}; extension CurrentUserLocationManager: CLLocationManagerDelegate {
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
checkLocationAuthorization()
}
}; extension CurrentUserLocationManager {
private func checkLocationAuthorization() {
guard let locationManager else { return }
switch locationManager.authorizationStatus {
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
case .restricted:
print("Your location is restricted")
case .denied:
print("Go into setting to change it")
case .authorizedAlways, .authorizedWhenInUse, .authorized:
// locationManager.startUpdatingLocation()
break
@unknown default:
break
}
}
}
Credit: https://developer.apple.com/forums//thread/756534?answerId=790191022#790191022.