How do I centre map over current location Swift - swift

I'm pretty new to coding in general and to get to this point I've been following multiple tutorials online which may have caused my code to be jumbled so I apologise for this.
I have started creating an app using MapKit with map annotations in my home town. I am currently living abroad so my location is not where these map annotations are. When I accept the location permissions the map defaults to the first annotation pin where I would like it to default to my current location.
My code is below and any help/explanation would be greatly appreciated.
Locations View Model
'''class LocationsViewModel: ObservableObject {
#Published var locations: [Location]
#Published var mapLocation: Location {
didSet {
updateMapRegion(location: mapLocation)
}
}
#Published var mapRegion: MKCoordinateRegion = MKCoordinateRegion()
let mapSpan = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
init() {
let locations = LocationsDataServices.locations
self.locations = locations
self.mapLocation = locations.first!
self.updateMapRegion(location: locations.first!)
}
private func updateMapRegion(location: Location) {
withAnimation(.easeInOut) {
mapRegion = MKCoordinateRegion(
center: location.coordinates,
span: mapSpan)
}
}
func showNextLocation(location: Location) {
withAnimation(.easeInOut) {
mapLocation = location
}
}
}'''
Locations Services Model
'''import MapKit
enum MapDetails {
static let startingLocation = CLLocationCoordinate2D(latitude: ***, longitude: ***)
static let defaultSpan = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
}
final class LocationServicesModel: NSObject, ObservableObject, CLLocationManagerDelegate {
#Published var region = MKCoordinateRegion(center: MapDetails.startingLocation, span: MapDetails.defaultSpan)
var locationManager: CLLocationManager?
func checkIfLocationServicesIsEnabled() {
if CLLocationManager.locationServicesEnabled() {
locationManager = CLLocationManager()
locationManager!.delegate = self
locationManager?.desiredAccuracy = kCLLocationAccuracyBest
} else {
print("turn on location manager")
}
}
private func checkLocationAuthorisation() {
guard let locationManager = locationManager else { return }
switch locationManager.authorizationStatus {
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
case .restricted:
print("location is restricted")
case .denied:
print("location has been denied, go into settings to change permission")
case .authorizedAlways, .authorizedWhenInUse:
break
#unknown default:
break
}
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
checkLocationAuthorisation()
}'''
The code works to a degree, the map annotations are placed correctly and it does show my current location but I'm not sure where or how I'd get the map to centre of the user location on acceptance of location permissions instead of the coordinates I have input above.
Thanks!

Once you have the required permission you need to invoke startUpdatingLocation()
locationManager.startUpdatingLocation()
This starts the generation of updates that report the user’s current location. These updates are available to you via the delegate method:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// Ensure we have an actual location in the update.
guard let location = locations.last else { return }
// Do something with the location.
}
The location is a CLLocation and you can extract the coordinate from that.
let span = MKCoordinateSpan(latitudeDelta: 0.009, longitudeDelta: 0.009)
let region = MKCoordinateRegion(center: location.coordinate, span: span)
mapView.setRegion(region, animated: true)

Related

Google Maps does not animate to user's location

I am trying to get my google maps map to center on my user's location. The user's location updates properly and shows the blue dot indicating their location. However, the camera, also set to the same coordinate locations, instead places itself at (0, 0). I have modified the program below so that it does move to the proper location after it loads. However, since my Bool is a state variable, it gives me a warning that I cannot update State variables during view updates. See the code below:
import SwiftUI
import GoogleMaps
import GoogleMapsUtils
struct GoogleMapsView: UIViewRepresentable {
#ObservedObject var locationManager = LocationManager()
var marker: GMSMarker = GMSMarker()
#Binding var heatmapWeightedData: [GMUWeightedLatLng]
#State var heatmapLayer = GMUHeatmapTileLayer()
#State var isCenteredOnCamera = false
func makeUIView(context: Context) -> GMSMapView {
let camera = GMSCameraPosition.camera(withLatitude: locationManager.latitude, longitude: locationManager.longitude, zoom: 15)
let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
heatmapLayer.radius = 75
heatmapLayer.weightedData = heatmapWeightedData
heatmapLayer.map = mapView
mapView.isMyLocationEnabled = true
return mapView
}
func updateUIView(_ mapView: GMSMapView, context: UIViewRepresentableContext<GoogleMapsView>) {
if self.isCenteredOnCamera == false {
mapView.animate(to: GMSCameraPosition(latitude: locationManager.latitude, longitude: locationManager.longitude, zoom: 15))
self.isCenteredOnCamera = true //Gives me the error
}
heatmapLayer.weightedData = heatmapWeightedData
heatmapLayer.map = mapView
}
}
Here is the locationManager code:
import Foundation
import CoreLocation
class LocationManager: NSObject, ObservableObject {
private let locationManager = CLLocationManager()
#Published var location: CLLocation? {
willSet { objectWillChange.send() }
}
var latitude: CLLocationDegrees {
return location?.coordinate.latitude ?? 0
}
var longitude: CLLocationDegrees {
return location?.coordinate.longitude ?? 0
}
override init() {
super.init()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
}
extension LocationManager: CLLocationManagerDelegate {
func locationManager( manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
print(status)
}
func locationManager( manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
return
}
self.location = location
}
}
Presumably, the camera is just reading the optional longitude and latitude variables and setting them to 0, for reasons I do not know.
Is there a way for me to update the camera location just once so that it does not infinitely update the camera location? If so, what should I do?

Onchange of variable in class

I have a class such as:
class LocationViewModel: NSObject, ObservableObject, CLLocationManagerDelegate {
#Published var lastSeenLocation: CLLocation?
#Published var currentPlacemark: CLPlacemark?
#Published var authorizationStatus: CLAuthorizationStatus
private let locationManager: CLLocationManager
override init() {
locationManager = CLLocationManager()
authorizationStatus = locationManager.authorizationStatus
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
func requestPermission() {
locationManager.requestAlwaysAuthorization()
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
authorizationStatus = manager.authorizationStatus
}
}
I'm trying to check if last seen location = cordinates using this code:
let radius: Double = 5 // miles
let userLocation = CLLocation(latitude: locationViewModel.lastSeenLocation?.coordinate.latitude, longitude: locationViewModel.lastSeenLocation?.coordinate.longitude)
let venueLocation = CLLocation(latitude: 51.500909, longitude: -0.177366)
let distanceInMeters = userLocation.distanceFromLocation(venueLocation)
let distanceInMiles = distanceInMeters * 0.00062137
if distanceInMiles < radius {
// user is near the venue
}
The only problem is, that I don't know how to run that code to check constantly. I was thinking .onChange but couldn't figure out how to test for lastSeenlocation in a class. What can I do?
I found a solution here.
For a quick overview:
//I put this code in my LocationViewModel class
func getUserLocation() {
locationManager.startUpdatingLocation()
locationManager.delegate = self
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.last {
print("latitude: \(location.coordinate.latitude), longitude: \(location.coordinate.longitude)")
}
}
//I put this code in my ContentView()
.onAppear {
locationViewModel.getUserLocation()
}
Then it prints your latitude & longitude every time your location changes.

continuous center of user with Mapkit

Good afternoon,
I am having trouble displaying a map where it only centers around the user and will stay on the user with movement. My error is in my view file where I mark //HERE.
My error is Type '()' cannot conform to 'View'
Why is it that this line is giving me an error? If that line runs, my assumption would be that the map region is changing but it still would return the Map which conforms. View file is presented below.
If I run it without the commented line, it does not display my current location. I have my simulator settings are set to Features > Location > Apple. Even when I am zooming way out, nothing is marked on the map.
import SwiftUI
import MapKit
struct Location: Identifiable {
let id = UUID()
let name: String
let content: String?
let lat: Double
let lon: Double
var dangerLevel: CGFloat? = 10.0
var coord: CLLocationCoordinate2D { CLLocationCoordinate2D(latitude: lat, longitude: lon) }
}
struct ContentView: View {
#EnvironmentObject var locationManager: LocationManager
#State private var userTrackingMode: MapUserTrackingMode = .follow
#State var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 40.7128, longitude: 74.0060), span: MKCoordinateSpan( latitudeDelta: 0.03, longitudeDelta: 0.03))
var body: some View {
var locationManager = LocationManager()
region = MKCoordinateRegion(center: locationManager.location!.coordinate, span: MKCoordinateSpan( latitudeDelta: 0.03, longitudeDelta: 0.03)) // HERE!!!!
VStack {
Map(coordinateRegion: $region,
interactionModes: .all,
showsUserLocation: true)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environmentObject(LocationManager())
}
}
This is the next file where I define my locationManager
import Foundation
import CoreLocation
class LocationManager: NSObject, ObservableObject {
private let locationManager = CLLocationManager()
#Published var location: CLLocation?
override init() {
super.init()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
}
}
extension LocationManager : CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
self.location = location
}
}
There were a couple of issues with your code. First, to answer the question asked, you should refrain from putting variables that are NOT views directly into the var body. While there are ways of getting around this restriction, there is not good reason to any longer. Since region is not a view, the code through the error. And yes, I know you defined var locationManager and the ViewBuilder took it as it was the init of a variable, not the variable itself. However, you already ha a reference to locationManager that you defined in the header. Use that.
I put a few more changes into your code with comments to help things along. Let me know if you have further questions.
struct ContentView: View {
// Unless you are using locationManager all over your code, you don't need to pass it as an
// .environmentObject, though you can if needed. Since this is the first instance in this example
// of locationManager, I made it a #StateObject.
#StateObject var locationManager = LocationManager()
#State private var userTrackingMode: MapUserTrackingMode = .follow
#State var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 37.60697453, longitude: -122.42798519), span: MKCoordinateSpan( latitudeDelta: 0.03, longitudeDelta: 0.03))
var body: some View {
VStack {
Map(coordinateRegion: $region,
interactionModes: .all,
showsUserLocation: true)
// Since locationManager is an ObservableObject, you can watch for changes with .onChange(of:)
.onChange(of: locationManager.location) { newLocation in
// Never force unwrap an optional unless you just set it yourself in the code.
guard let newLocation = newLocation else { return }
region = MKCoordinateRegion(center: newLocation.coordinate, span: MKCoordinateSpan( latitudeDelta: 0.03, longitudeDelta: 0.03)) // HERE!!!!
}
}
}
}
import CoreLocation
// I would consider renaming this class. It can be confusing to see
// locationManager.locationManager in code.
class LocationManager: NSObject, ObservableObject {
private let locationManager = CLLocationManager()
#Published var location: CLLocation?
override init() {
super.init()
// You can generally drop .self, with some exceptions. The compiler will correct you.
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
}
extension LocationManager : CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
// This is an exception to dropping self when a variable in a closure has the same name as a
// self variable.
self.location = location
}
}

No location permission pop up for locationManager.requestWhenInUseAuthorization()

I'm following an online tutorial to make a simple project that displays a user's location on the map. However, when I run it, the user location is not displayed and there is no pop up to ask for location permission. I'm not sure how I can fix that. I've attached the code below.
import MapKit
import SwiftUI
struct ContentView: View {
#StateObject private var viewModel=ContentViewModel()
var body: some View {
Map(coordinateRegion: $viewModel.region)
.ignoresSafeArea()
.onAppear{viewModel.checkIfLocationServicesIsEnabled()}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
final class ContentViewModel: NSObject, ObservableObject, CLLocationManagerDelegate{
#Published var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude:37.3331516, longitude: -121.891054), span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1));
var locationManager: CLLocationManager?
func checkIfLocationServicesIsEnabled(){
if CLLocationManager.locationServicesEnabled(){
locationManager=CLLocationManager()
locationManager!.delegate=self
}
else{
print("Turn location on")
}
}
private func checkLocationAuthorization(){
guard let locationManager = locationManager else {
return
}
switch locationManager.authorizationStatus{
case.notDetermined:
locationManager.requestWhenInUseAuthorization()
case .restricted:
print("Your location is restricted likely due to parental controls.")
case .denied:
print("You have denied this app location permission. Go into settings to change it.")
case .authorizedAlways:
region=MKCoordinateRegion(center: locationManager.location!.coordinate, span:MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1))
case .authorizedWhenInUse:
region=MKCoordinateRegion(center: locationManager.location!.coordinate, span:MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1))
#unknown default:
break
}
func locationManagerDidChangeAuthorization(_manager: CLLocationManager) {
checkLocationAuthorization()
}
}
}
Your code has 2 issues
You are not asking for permissions.
You implemented the delegate wrong.
This should work:
final class ContentViewModel: NSObject, ObservableObject, CLLocationManagerDelegate{
#Published var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude:37.3331516, longitude: -121.891054), span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1));
var locationManager: CLLocationManager?
func checkIfLocationServicesIsEnabled(){
if CLLocationManager.locationServicesEnabled(){
locationManager=CLLocationManager()
locationManager?.delegate=self
//ask for permissions here
//depending on your usage you could also ask for
//locationManager?.requestWhenInUseAuthorization()
locationManager?.requestAlwaysAuthorization()
}
else{
print("Turn location on")
}
}
private func checkLocationAuthorization(){
guard let locationManager = locationManager else {
return
}
switch locationManager.authorizationStatus{
case.notDetermined:
//the function containing this will only be called if the permission get requested so remove it
//locationManager.requestWhenInUseAuthorization()
case .restricted:
print("Your location is restricted likely due to parental controls.")
case .denied:
print("You have denied this app location permission. Go into settings to change it.")
case .authorizedAlways:
region=MKCoordinateRegion(center: locationManager.location!.coordinate, span:MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1))
case .authorizedWhenInUse:
region=MKCoordinateRegion(center: locationManager.location!.coordinate, span:MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1))
#unknown default:
break
}
}
// this function was wrapped inside your checkLocationAuthorization() function
//move it to outer scope. It gets called every time you request permissions
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
checkLocationAuthorization()
}
}
Be aware you will have to add multiple privacy keys to get this working.

Allow users to move around the map while displaying their location

I want to allow users to zoom in/out and move freely around the map while the app is tracking and displaying their current location. Later i will try to add a button that you can press and it will move you back to the center of user location. I think the problem is that in my LocationManager file i am creating new region everytime i get a new location coordinates. But i do not know any other ways of tracking user location and displaying it.
LocationManager:
import Foundation
import CoreLocation
import MapKit
class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate{
let locationManager = CLLocationManager()
#Published var location: CLLocationCoordinate2D?
#Published var region = MKCoordinateRegion()
override init(){
super.init()
locationManager.delegate = self
}
func startTracking() {
locationManager.requestAlwaysAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.allowsBackgroundLocationUpdates = true
locationManager.startUpdatingLocation()
}
func stopTracking() {
print("stop test")
locationManager.stopUpdatingLocation()
locationManager.allowsBackgroundLocationUpdates = false
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let tempLocation = locations.last?.coordinate
print(tempLocation)
let center = CLLocationCoordinate2D(latitude: tempLocation?.latitude ?? 0.0, longitude: tempLocation?.longitude ?? 0.0)
let span = MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5)
let tempRegion = MKCoordinateRegion(center: center, span: span)
DispatchQueue.main.async {
self.location = tempLocation
self.region = tempRegion
}
}
}
ContentView:
import SwiftUI
import MapKit
struct ContentView: View {
#State var isButtonPressed: Bool = false
#StateObject var locationManager = LocationManager()
var body: some View {
VStack{
Button("START"){
isButtonPressed = true
locationManager.startTracking()
}
Button("STOP"){
isButtonPressed = false
locationManager.stopTracking()
}
mapView(isButtonPressed: isButtonPressed, manager: locationManager)
}
}
}
struct mapView: View{
var isButtonPressed: Bool
#ObservedObject var manager: LocationManager
#State var defaultRegion = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 0, longitude: 0), latitudinalMeters: 1000, longitudinalMeters: 1000)
var body: some View{
if isButtonPressed == true{
if let location = manager.location {
Map(coordinateRegion: $manager.region, interactionModes: .all, showsUserLocation: true, userTrackingMode:nil)
} else{
Map(coordinateRegion: $defaultRegion)
}
}
else
{
Map(coordinateRegion: $defaultRegion)
}
}
}
Thanks!
Creating a series of regions is the wrong way to monitor the user's location.
It's been quite a while since I've written a mapping app, but here's the general idea of what you should do:
Create an instance of the location manager.
Set yourself as the location manager's delegate.
Ask the user for permission to get location updates while your app is running (if you don't have permission already.)
Call startUpdatingLocation() to request location updates
Respond to calls to the location manager delegate's locationManager(_:didUpdateLocations:) method as needed. (If you set up the map to show the user's location you won't have to do anything special - the blue dot will update automatically.)