I've been trying to solve this for a few hours and I can't seem to arrive at a solution. I am trying to create a button on my map view that zooms in on the users location when pressed. Here is the code for the function that pertains to the button:
func zoomInOnLocation() {
let userLocation = MKUserLocation()
let locationManager = CLLocationManager()
locationManager.requestWhenInUseAuthorization()
let currentLocation: CLLocation? = userLocation.location
let latitude = currentLocation?.coordinate.latitude
let longitude = currentLocation?.coordinate.longitude
let span: MKCoordinateSpan = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
let location: CLLocationCoordinate2D = CLLocationCoordinate2DMake(latitude!, longitude!)
let region: MKCoordinateRegion = MKCoordinateRegionMake(location, span)
mapView.setRegion(region, animated: true)
}
When I click the button in the simulator, I receive an error stating fatal error:
unexpectedly found nil while unwrapping an Optional value
with the fifth line mapDelegate.mapView!... highlighted in red. Also, I added the proper tag to Info.plist. Any help is much appreciated.
Check this :
if #available(iOS 9.0, *) {
locationManager.requestLocation()
} else {
// Fallback
}
let latitude:CLLocationDegrees = //insert latitutde
let longitude:CLLocationDegrees = //insert longitude
let latDelta:CLLocationDegrees = 0.05
let lonDelta:CLLocationDegrees = 0.05
let span = MKCoordinateSpanMake(latDelta, lonDelta)
let location = CLLocationCoordinate2DMake(latitude, longitude)
let region = MKCoordinateRegionMake(location, span)
mapView.setRegion(region, animated: false)
For more : Making the map zoom to user location and annotation (swift 2)
So you probably do not want to to interact with the mapView delegate as you are doing right now.
How about adding the delegates to the class of the view that holds the mapView, like this:
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
and setting the delegates in viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
mapView.delegate = self
}
And of course you want to have the locationManager and userLocation set up:
let locationManager = CLLocationManager()
var userLocation = CLLocation()
Notice that userLocation is a variable because most likely you would want to update it at some point.
This is how you probably want to work with locationManager:
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
You would make changes according to the needs of your app, and taking into account that the desired accuracy has an impact on battery life. Also, are you starting and stopping updates on userLocation? Because I do not see that in your code, unless you are doing it outside of this function.
A good practice is to try to minimize what an specific function does down to one task. You probably want to do all this setup elsewhere and then only zoom in inside the function. :)
Finally, in order to zoom in, you can change the values of MKCoordinateSpanMake, and remember that larger span values zoom in the map, so a smaller area is viewable.
let userLocationCoordinates = CLLocationCoordinate2D(latitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude)
let span = MKCoordinateSpanMake(0.3, 0.3)
let region = MKCoordinateRegion(center: userLocationCoordinates, span: span)
mapView.setRegion(region, animated: true)
Hopefully that helps you out a bit, let me know how it goes!
Perhaps this will help. I created this function to zoom into an area defined by an array of positions, ranging from just the current user location out to the area around a set of points making up a polyline. The function provides for a buffer around the points based on a regionPaddingFactor constant set in my system constants.
func setRectView(_ locations: [MKAnnotation], mapView: MKMapView) // Size the area for display and reset the view
{
var maxLat = -90.0
var minLat = 90.0
var maxLon = -180.0
var minLon = 180.0
if locations.count >= 1 {
for waypoint in locations {
maxLat = max(maxLat, waypoint.coordinate.latitude)
minLat = min(minLat, waypoint.coordinate.latitude)
maxLon = max(maxLon, waypoint.coordinate.longitude)
minLon = min(minLon, waypoint.coordinate.longitude)
}
let loc = CLLocationCoordinate2DMake((maxLat-fabs(maxLat - minLat)/2), (maxLon-fabs(maxLon - minLon)/2))
let span = MKCoordinateSpanMake(0.001 + (1.0 + Setting.shared.regionPaddingFactor) * fabs(maxLat - minLat), 0.001 + (1.0 + Setting.shared.regionPaddingFactor) * fabs(maxLon-minLon))
// The 0.001 values above ensure that you do not get a 0.0 valued span if all of the points have the same latitude, longitude, or both, or if there is only one point
// The regionPaddingFactor is a constant to allow some space around the points passed in
let reg = MKCoordinateRegionMake(loc, span)
mapView.setRegion(reg, animated: true)
mapView.animatedZoom(zoomRegion: reg, duration: 0.8)
}
}
In the calling code, I provide for 3 settings that rotate as the user presses the button:
Show a tight view (just pass current location in the call)
Show the whole route (pass all points in the template)
Change to manual zooming / positioning
The last option is needed since I call the function whenever a new current position is received to reposition the view based on current location, which repeatedly refocuses the view if the user is trying to reposition the map.
If you don't want the flexibility of sending different position arrays, you can do the positioning here using mapView.annotations or just the current location as the array.
Related
I am working on MapKit and core location to display my current GPS location.
The code is working ok but with some problems.
The problem is as follows:
I turn on my app in an open area like on the roadside. The app is able to get my current GPS location and display it on the Map.
I walk into a building with the app on. When inside, I launch the page to show the GPS location. It shows my previous GPS location. As I know, when I am inside the building, the Mapkit and Corelocation should not be able to get GPS. But in this case, it shows my previous GPS data!
I walk out of the building with the app on. In the open space I launch the page to show my current GPS location but the app is unable to get my new GPS location but displays the previous GPS data. In this case the app should fetch a new GPS location. I have to try a few times to launch the page (Navigate from GPS-VC to home-VC, from home click a button to launch the GPS-VC to get GPS).
Why is the GPS so slow even though I am in the open space with good signal strength?
Is there a difference to call these methods:
LocationManager.startUpdatingLocation()
LocationManager.requestLocation()
Here the code:
#IBOutlet weak var Map: MKMapView!
var locationMgr : CLLocationManager!
override func viewDidload(){
if(CLLocationManager.locationServicesEnabled() )
{
locationMgr = CLLocationManager()
locationMgr.delegate = self
locationMgr.desriedAccuracy = kCLLocationAccuracyBest
locationMgr.requestWhenInuseAuthorization()
locationMgr.startUpdatingLocation()
}
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLoction: CLLocation = locations[0]
let latitude = userLoction.coordinate.latitude
let longitude = userLoction.coordinate.longitude
let latDelta: CLLocationDegrees = 0.05
let lonDelta: CLLocationDegrees = 0.05
let span:MKCoordinateSpan = MKCoordinateSpanMake(latDelta, lonDelta)
let location: CLLocationCoordinate2D = CLLocationCoordinate2DMake(latitude, longitude)
let region: MKCoordinateRegion = MKCoordinateRegionMake(location, span)
let dropin = MKPointAnnotation()
dropin.coordinate = location
droping.title = "Here"
self.Map.setRegion(region, animated: true)
self.Map.addAnnotation(dropin)
self.Map.showsUserLocation = true
}
I'm not sure this will solve all 3 of your issues but I spotted the following issues in your delegate method:
the locations array is sorted in ascending time order so locations.last will contain the most recent location.
This method might be called with old cached values so you need to check the timestamp of the location to decide if it's worth using.
well, guess this one is kinda big...
I'm making an app in which there will be a MapKit and a button. When the user clicks the button, he/she will open a new ViewController with several options of destinations to go. After confirming the one he wants to go, the MapKit view will reopen with directions for the specific place. To do this, I created a function on the ViewController linked to the MapKit view:
func createmap(latit: Double, longit: Double){
//set what's going to show up in the Map
MapView.delegate = self
MapView.showsScale = true
MapView.showsPointsOfInterest = true
MapView.showsUserLocation = true
//request authorization for user location data storage
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
//if authorization is given, use the user location
if CLLocationManager.locationServicesEnabled(){
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
//set source and destination coordinates of direction recommendation
let sourceCoordinates = locationManager.location?.coordinate
let destinationCoords = CLLocationCoordinate2D(latitude: latit, longitude: longit)
//set placemarks with source and destination coordinates
let sourcePlacemark = MKPlacemark(coordinate: sourceCoordinates!)
let destPlacemark = MKPlacemark(coordinate: destinationCoords)
//put placemarks on maps with the source and destination coordinates
let sourceItem = MKMapItem(placemark: sourcePlacemark)
let destItem = MKMapItem(placemark: destPlacemark)
//set direction request, source and destination request coordinates and transport type
let directionRequest = MKDirectionsRequest()
directionRequest.source = sourceItem
directionRequest.destination = destItem
directionRequest.transportType = .automobile
//set response if sucess or error
let directions = MKDirections(request: directionRequest)
directions.calculate(completionHandler: {
response, error in
guard let response = response else {
if let error = error {
print("Something went wrong.")
}
return
}
//set format of route line
let route = response.routes[0]
self.MapView.add(route.polyline, level: .aboveRoads)
//set map framing
let rekt = route.polyline.boundingMapRect
self.MapView.setRegion(MKCoordinateRegionForMapRect(rekt), animated: true)
})
}
And then, to make this function run, I did this:
override func viewDidLoad() {
super.viewDidLoad()
createmap(latit: lat, longit: long)
}
lat and long are variables declared publicly.
In the other view, I have a button called "go back to the Map" in which I try to make a simple segue. However, there is a problem with this code: if I get directions to some place, reopen the other view and then press "go back to the Map", the directions that were set disappear. I don't know what to do to make them stay, can someone please help?
just a bit more information: the "go back to the Map" button is linked to the MapKit view as a simple segue (i clicked, dragged and dropped to the MapKit view, didn't write any line of code)
I think problem is your Map VC is already added into the navigation stack so ViewDidLoad method is not called again you should move your code from viewdidload to viewWill appear like
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
createmap(latit: lat, longit: long)
}
Did you used this Function to add a polyline? you added poly line correctly . but I think you had not provided values to poly line for drawing path
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = UIColor.blue
renderer.lineWidth = 4.0
return renderer
}
I am new to Swift (and this website, so sorry if I am doing anything wrong), and I am trying to make a running app that tracks the user's location. While the function I used to track the distance works, it doesn't start at 0. When I hit the start button, the distance starts at a random number and then it starts tracking from there.
My question is: Is there something I am not addressing something correctly? If so, is there a way to fix it so that the tracking is more accurate? Here is what I have so far:
override func viewDidLoad() {
super.viewDidLoad()
stopwatchLabel.text = "00:00.00"
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
locationManager.activityType = .fitness
locationManager.distanceFilter = 10.0
mapView.showsUserLocation = true
startLocation = nil
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Location Delegate Methods
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
let location = locations.last
let center = CLLocationCoordinate2D(latitude: location!.coordinate.latitude, longitude: location!.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.002, longitudeDelta: 0.002))
self.mapView.setRegion(region, animated: true)
if startLocation == nil {
startLocation = locations.first
}
var distance = startLocation.distance(from: location!)
let lastDistance = location?.distance(from: location!)
distance += lastDistance!
distanceString = "\(distance)"
distanceLabel.text = distanceString
}
Here is what the app looks like:
the run screen
I realize that other people have asked similar questions, but the questions either have no answer, or they are in a different language (such as Objective-C). If this question has been answered before and I'm just overlooking it, could someone please link the answer to me? Thank you!
When the location manager starts, the first location returned is the cached, last know location. You need to check for this, via the timestamp, as well as check for the level of accuracy that is returned. Something like this in your didUpdateLocations delegate:
let newLocation = locations.last
let timeDiff = newLocation?.timestamp.timeIntervalSinceNow
let accuracyNeeded:CLLocationAccuracy=100.0
if timeDiff < 5.0 && (newLocation?.horizontalAccuracy)!<=accuracyNeeded{
//your code here
}
You have to allow the sensors time to warm up.
Here is a typical didUpdateLocations implementation. We keep track of both the time elapsed since we started updating locations and the improving horizontal accuracy as the sensors warm up. If the horizontal accuracy doesn't improve in a reasonable time, we give up.
You will need a nil property, a Date?, called startTime, and constants REQ_TIME and REQ_ACC. self.stopTrying() turns off updates and resets startTime to nil.
let loc = locations.last!
let acc = loc.horizontalAccuracy
let time = loc.timestamp
let coord = loc.coordinate
if self.startTime == nil { // Date? property, keep track of time
self.startTime = Date()
return // ignore first attempt
}
let elapsed = time.timeIntervalSince(self.startTime)
if elapsed > REQ_TIME { // required time before giving up
self.stopTrying()
return
}
if acc < 0 || acc > REQ_ACC { // desired accuracy
return // wait for the next one
}
// got it
print("You are at \(coord.latitude) \(coord.longitude)")
I've implemented MapKit into my Swift app and everything runs properly and when I run the app it shows a map. However, it's an overview of the United States and not where I want it to be.
I've imported the MapKit into ViewController and connected the MapView to the ViewController via an IBOutlet, however it's not working.
I didn't put the code in the viewDidLoad because the app crashed every time. Rather, I made a function and that seemed to do the trick. This is all of the code that I've got going for this map:
func mapMexico()
{
let lat:CLLocationDegrees = 20.648097
let long:CLLocationDegrees = -105.235168
let coordinate = CLLocationCoordinate2D(latitude: lat, longitude: long)
let latDelta:CLLocationDegrees = 0.01
let longDelta:CLLocationDegrees = 0.01
let span = MKCoordinateSpan(latitudeDelta: latDelta, longitudeDelta: longDelta)
let region = MKCoordinateRegion(center: coordinate, span: span)
mapView.setRegion(region, animated: true)
}
I'm trying to make an uber-like location picker from a small MKMapView in my class. My problem is the following :
Since I want to have the initial region to be centered at the user current location, I set the initial region inside the location manager function like this:
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let locValue:CLLocationCoordinate2D = (locations.last?.coordinate)!
let latDelta = 0.005
let lonDelta = 0.005
let span:MKCoordinateSpan = MKCoordinateSpan(latitudeDelta: latDelta, longitudeDelta: lonDelta)
let region:MKCoordinateRegion = MKCoordinateRegionMake(locValue, span)
self.locationMap.setRegion(region, animated: false)
}
The problem with this is that every time that the user tries to drag the map region to pick a location, obviously since the location is being always updated and the function is being called at everytime the location is updates, the map region goes back to its initial point as the function above states.
Another thing is that if I try to extract the locValue variable and assign it to a instance variable of the class within the function adding a line like this :
self.userLocation = locValue
self.userLocation won't change. If you have any suggestion on how to get a single location without updating it or how to fix either one of these problems it would be really appreciated if you could let me know.
Thanks.