App crashes when dismissing view controller - ios

I've a simple ViewController that displays my current location coordinates.
Everything is working properly, but when I dismiss the ViewController, the app crashes without any specific error log.
The class code goes like this:
import UIKit
class LocationViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate, UIPopoverPresentationControllerDelegate {
// General objects
#IBOutlet var closeButton: UIButton!
#IBOutlet var latitudeLabel: UILabel!
#IBOutlet var longitudeLabel: UILabel!
#IBOutlet var infoButton: UIButton!
// Global variables
var location: CLLocationManager? = CLLocationManager()
var geocoder = CLGeocoder();
var placemark = CLPlacemark();
var hasPin: Bool = false;
override func viewDidLoad() {
super.viewDidLoad()
// Ask for Authorisation from the User.
location?.requestAlwaysAuthorization();
// For use in foreground
location?.requestWhenInUseAuthorization();
getCurrentLocation();
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func closeButton(_ sender: AnyObject) {
self.dismiss(animated: true, completion: {
print("dismissing locationViewController");
self.location = nil;
});
}
#IBAction func infoButton(_ sender: AnyObject) {
// TODO
}
// MARK: - General functions
func getCurrentLocation() -> Void {
if (CLLocationManager.locationServicesEnabled()) {
location?.delegate = self;
location?.desiredAccuracy = kCLLocationAccuracyBest;
location?.startUpdatingLocation();
}
}
// MARK: - CLLocationManagerDelegate
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("ERROR = \(error)");
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// Gets the user coordinates
let locValue:CLLocationCoordinate2D = manager.location!.coordinate;
USER_LATITUDE = locValue.latitude;
USER_LONGITUDE = locValue.longitude;
longitudeLabel.text = "\(USER_LONGITUDE)";
latitudeLabel.text = "\(USER_LATITUDE)";
location?.stopUpdatingLocation()
}
Does anyone have any clue why this happens?
No error log is prompted that's what makes me even more confused.
First I thought I had to set the location variable to be optional and then set it to nil when I dismiss the VC but the crash is still happening.
Crashlytics says that the App crashes inside the LocationViewController line 0 , which is in fact weird.
I call this ViewController, from a button click inside another VC like this:
#IBAction func locationButton(_ sender: AnyObject) {
let storyboard = UIStoryboard(name: "Main", bundle: nil);
let viewController = storyboard.instantiateViewController(withIdentifier: "locationVC");
self.present(viewController, animated: true, completion: nil);
}
I'm using Swift3 with the latest Xcode Beta Version on iOS 10.
Thanks

Replace this:
var location: CLLocationManager? = CLLocationManager()
With this:
let location = CLLocationManager()
Change all code as necessary (this is no longer an Optional so there is nothing to unwrap) and delete the line that tries to set it to nil.
If you are worried that the location manager might be trying to get your location when you dismiss, then implement viewWillDisappear and tell it to stop updating.

You need to add the privacy entry in Info.plist and also request authorization to use location services. A good overview can be found here: http://nevan.net/2014/09/core-location-manager-changes-in-ios-8/

Related

How to get location within the camera use in Swift 3/4?

I am coming to this problem where I have a camera and a map view where all I want to do is get the location if the photo is taken using the app that you can access the user's location at the moment the image is captured and extend UIImage to include a property for location and save this location at the same time onto the map view or tableView that you save the image when the picture is taken using the phone's camera. Thank you for any help.
Here is my code. (Camera Controller)
import UIKit
class ViewController: UIViewController, UINavigationControllerDelegate {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var resultLbl: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func CamBtn(_ sender: Any) {
if !UIImagePickerController.isSourceTypeAvailable(.camera) {
return
}
let cameraPicker = UIImagePickerController()
cameraPicker.delegate = self
cameraPicker.sourceType = .camera
cameraPicker.allowsEditing = false
MapViewController.location.requestLocation()
guard let userLocation = MapViewController.location.location else {return}
present(cameraPicker, animated: true)
}
#IBAction func LibBtn(_ sender: Any) {
let picker = UIImagePickerController()
picker.allowsEditing = false
picker.delegate = self
picker.sourceType = .photoLibrary
present(picker, animated: true)
}
}
extension ViewController: UIImagePickerControllerDelegate {
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
dismiss(animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
picker.dismiss(animated: true)
resultLbl.text = "Analyzing Image..."
guard let image = info["UIImagePickerControllerOriginalImage"] as? UIImage else {
return
}
}
MapViewController
import Foundation
import UIKit
import MapKit
class MapViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var tableView: UITableView!
let regionRadius: CLLocationDistance = 1000
var location: CLLocationManager!
let nearbyRequest = MKLocalSearchRequest()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
location = CLLocationManager()
location!.delegate = self
location.desiredAccuracy = kCLLocationAccuracyBest
location.requestAlwaysAuthorization()
location.startUpdatingLocation()
if CLLocationManager.authorizationStatus() == .authorizedWhenInUse {
location!.startUpdatingLocation()
} else {
location!.requestWhenInUseAuthorization()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
mapView.showsUserLocation = true
}
override func viewWillDisappear(_ animated: Bool) {
mapView.showsUserLocation = false
}
func location(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
print("Not Determined")
case .restricted:
print("Restricted")
case .denied:
print("Denied")
case .authorizedAlways:
print("AuthorizedAlways")
case .authorizedWhenInUse:
print("AuthorizedWhenInUse")
location!.startUpdatingLocation()
}
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("Failed to initialize Map: ", error.description)
}
}
If you want to take the user's location at the time of the photo, you need to request a location update when the user takes a photo and not just start location updates in your other ViewController. You can request a one time location update using CLLocationManager().requestLocation(), but be aware that this is an asynchronous call, so the user might finish taking the photo by the time this returns.
To mitigate this issue, you can either use PromiseKit to get your function to return only when the asynchronous method finished or you can use CLLocationManagerDelegate func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) method and update the picture/MapView as soon as the location update is received.
In MapViewController change var location: CLLocationManager! to static var location = CLLocationManager(), update your MapViewController class accordingly to work with location being a type property and not an implicitly unwrapped instance property. Then in CameraController change your function like so:
#IBAction func CamBtn(_ sender: Any) {
if !UIImagePickerController.isSourceTypeAvailable(.camera) {
return
}
let cameraPicker = UIImagePickerController()
cameraPicker.delegate = self
cameraPicker.sourceType = .camera
cameraPicker.allowsEditing = false
MapViewController.location.requestLocation()
guard let userLocation = MapViewController.location.location else {return}
present(cameraPicker, animated: true)
}
This is not a full example, you still need to add userLocation to your image/MapView, but you should be able to figure that part out by yourself. Also, with this implementation requestLocation() is not guaranteed to return by the time you check its value, so I'd rather use PromiseKit's CLLocationManager.promise() function, which only returns once the location is updated.

Making an app using swift that gets user's location

I ran my code for getting the user's location and this is the error I get. Can someone please help me figure it out?
My code is as follows:
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var citybtn: UIButton!
#IBOutlet weak var citylbl: UILabel!
let locationManager = CLLocationManager()
var currentLocation : CLLocation!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Used to start getting the users location
//let locationManager = CLLocationManager()
// For use when the app is open
//locationManager.requestAlwaysAuthorization()
// If location services is enabled get the users location
//if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest // You can change the locaiton accuary here.
locationManager.requestWhenInUseAuthorization()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
locationAuthStatus()
}
func locationAuthStatus() {
if CLLocationManager.authorizationStatus() == .authorizedWhenInUse {
currentLocation = locationManager.location
print(currentLocation.coordinate.latitude)
print(currentLocation.coordinate.longitude)
} else {
locationManager.requestWhenInUseAuthorization()
locationAuthStatus()
}
}
#IBAction func buttonpresses(_ sender: Any) {
}
}
sounds like you did not enable the location update capability of your target:
choose target
select capabilities
in Background Modes, tick Location updates

Getting location using another class not working when calling function swift

I was using the ViewController class before to get the users updates but now when expanding the application i needed to move it to another class that simply handles all the location updates. Here is the code that i am using now:
class ViewController: UIViewController, UITextFieldDelegate {
#IBAction func pickMeUpButton(sender: AnyObject) {
sendPushNotificationController().sendPushNotification("sendRequest",userLat: defaults.stringForKey("userLat")!, userLong: defaults.stringForKey("userLong")! )
}
#IBOutlet var numberForPickup: UITextField!
let defaults = NSUserDefaults.standardUserDefaults()
override func viewDidLoad() {
super.viewDidLoad()
self.numberForPickup.delegate = self
getLocationController().initLocation()
}
So i made another class called getLocationController with an init function that should start the location updates. Here is the code:
class getLocationController: UIViewController, CLLocationManagerDelegate {
var locationManager: CLLocationManager!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func initLocation(){
print("Im in here")
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didUpdateToLocation newLocation: CLLocation, fromLocation oldLocation: CLLocation) {
print("In location")
if UIApplication.sharedApplication().applicationState == .Active {
print("App in Foreground")
}else {
let Device = UIDevice.currentDevice()
let iosVersion = Double(Device.systemVersion) ?? 0
let iOS9 = iosVersion >= 9
if iOS9{
locationManager.allowsBackgroundLocationUpdates = true;
locationManager.pausesLocationUpdatesAutomatically = false;
}
//let iOS7 = iosVersion >= 7 && iosVersion < 8
print("App is backgrounded. New location is %#", newLocation)
}
}
}
Now the print in initLocation is printed but not the print in didUpdateLocations. I used the very same code in ViewController class and it worked perfectly fine. Now when i am trying to move it to another class that is now really a view on the phone but simply a helper class its not working. Any ideas why?
I don't see you assigning the getLocationController to a variable anywhere in the ViewController. That means the getLocationController would go out of scope and be destroyed, wouldn't it? That would explain why the callback didUpdateToLocation isn't called.
Try:
class ViewController: UITextFieldDelegate {
#IBAction func pickMeUpButton(sender: AnyObject) {
sendPushNotificationController().sendPushNotification("sendRequest",userLat: defaults.stringForKey("userLat")!, userLong: defaults.stringForKey("userLong")! )
}
#IBOutlet var numberForPickup: UITextField!
let defaults = NSUserDefaults.standardUserDefaults()
var glc:getLocationController // is this how it is in Swift?!
override func viewDidLoad() {
super.viewDidLoad()
self.numberForPickup.delegate = self
glc = getLocationController()
glc.initLocation()
}

How to get reference to already instantiated ViewController?

swift newbie here. I am trying to get my simple core location app retrieve data automatically after getting coordinates by locationManager.
I have implemented separate class not to make my main view controller be responsible for too many tasks here how it looks like:
import Foundation
import CoreLocation
class CoreLocationController : NSObject, CLLocationManagerDelegate {
var locationManager = CLLocationManager()
var lastCoordinates: (lat: Double, lon: Double)?
override init() {
super.init()
self.locationManager.delegate = self
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.distanceFilter = 3000
self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations.last! as CLLocation
self.lastCoordinates = (location.coordinate.latitude, location.coordinate.longitude)
print("didUpdateLocations: \(location.coordinate.latitude), \(location.coordinate.longitude)")
}
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
print("didChangeAuthorizationStatus")
switch status {
case .NotDetermined:
print(".NotDetermined")
break
case .AuthorizedWhenInUse:
print(".AuthorizedWhenInUse")
self.locationManager.startUpdatingLocation()
break
case .Denied:
print(".Denied")
break
default:
print("Unhandled authorization status")
break
}
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
}
}
Of course i have initialized it in AppDelegate.swift
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var coreLocationController: CoreLocationController?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
self.coreLocationController = CoreLocationController()
return true
}
Now my main ViewController after clicking button is performing retrieveWeatherForecast with appDelegate passed to it to get reference to CoreLocationController.lastCoordinates property. I came to conclusion that in order to perform retrieveWeatherForecast after getting coordinates immediate after launching the best way will be to run this method inside locationManager func (the one with didUpdateLocations argument). In order to do it i will need to have reference to ViewController running instance to perform sth like:
runningViewControlerinstance.retrieveWeatherForecast(runningViewControlerinstance.appDel)
main ViewController code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var currentTemperatureLabel: UILabel?
#IBOutlet weak var currentHumidityLabel: UILabel?
#IBOutlet weak var currentPrecipitationLabel: UILabel?
#IBOutlet weak var currentWeatherIcon: UIImageView?
#IBOutlet weak var currentWeatherSummary: UILabel?
#IBOutlet weak var refreshButton: UIButton?
#IBOutlet weak var activityIndicator: UIActivityIndicatorView?
let appDel = UIApplication.sharedApplication().delegate! as! AppDelegate
private var forecastAPIKey: String?
override func viewDidLoad() {
super.viewDidLoad()
let path = NSBundle.mainBundle().pathForResource("APIkeys", ofType: "plist")
let dict = NSDictionary(contentsOfFile: path!)
self.forecastAPIKey = dict!.objectForKey("forecastAPIKey") as? String
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func retrieveWeatherForecast(appDel: AppDelegate ) {
let currentCoordinates :(lat: Double, lon: Double) = (appDel.coreLocationController?.lastCoordinates)!
let forecastService = ForecastService(APIKey: forecastAPIKey!)
forecastService.getForecast(currentCoordinates.lat, lon: currentCoordinates.lon) {
(let currently) in
if let currentWeather = currently {
dispatch_async(dispatch_get_main_queue()) {
if let temperature = currentWeather.temperature {
self.currentTemperatureLabel?.text = "\(temperature)ยบ"
}
if let humidity = currentWeather.humidity {
self.currentHumidityLabel?.text = "\(humidity)%"
}
if let precipitation = currentWeather.precipProbability {
self.currentPrecipitationLabel?.text = "\(precipitation)%"
}
if let icon = currentWeather.icon {
self.currentWeatherIcon?.image = icon
}
if let summary = currentWeather.summary {
self.currentWeatherSummary?.text = summary
}
self.toggleRefreshAnimation(false)
}
}
}
}
#IBAction func refreshWeather() {
toggleRefreshAnimation(true)
retrieveWeatherForecast(appDel)
}
func toggleRefreshAnimation(on: Bool) {
refreshButton?.hidden = on
if on {
activityIndicator?.startAnimating()
} else {
activityIndicator?.stopAnimating()
}
}
}
I will appreciate very much any help, comments and suggestions from you the swift community, Thanks!
If you have a separate class handling the location services (which is a good design pattern) or alternatively the app delegate, the best way to notify any active view controllers is via NSNotification.
Register in the view controller via NSNotificationCenter in viewDidAppear and remove yourself as an observer in viewWillDisappear. There is plenty of documentation to explain the details.
This loose coupling of controllers to asynchronous processes is much safer than keeping references to UI objects.
"Of course i have initialized it in AppDelegate.swift"
Why? There is no reason to initialise it there, is there? Initialise it where you intend to use it. You viewController needs access to your CoreLocationController to use, display or edit locations. So initialise and use it there and you won't have to pass your view controller to your location manager.

CLLocation manager not working iOS 8

I'm trying to follow this Google Maps tutorial: http://www.raywenderlich.com/81103/introduction-google-maps-ios-sdk-swift
Like many others I have hit a roadblock where CLLocationManager does not seem to fire startUpdatingLocation().
I have updated the pList with NSLocationAlwaysUsageDescription and NSLocationWhenInUseUsageDescription in accordance with Location Services not working in iOS 8, but still no location being fired.
Code below - any help is appreciated!
import UIKit
class MapViewController: UIViewController, TypesTableViewControllerDelegate, CLLocationManagerDelegate {
#IBOutlet weak var mapView: GMSMapView!
#IBOutlet weak var mapCenterPinImage: UIImageView!
#IBOutlet weak var pinImageVerticalConstraint: NSLayoutConstraint!
var searchedTypes = ["bakery", "bar", "cafe", "grocery_or_supermarket", "restaurant"]
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Types Segue" {
let navigationController = segue.destinationViewController as UINavigationController
let controller = segue.destinationViewController.topViewController as TypesTableViewController
controller.selectedTypes = searchedTypes
controller.delegate = self
}
}
// MARK: - Types Controller Delegate
func typesController(controller: TypesTableViewController, didSelectTypes types: [String]) {
searchedTypes = sorted(controller.selectedTypes)
dismissViewControllerAnimated(true, completion: nil)
}
func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if status == .AuthorizedWhenInUse {
println("success") // Works
locationManager.startUpdatingLocation() // Does not seem to fire
mapView.myLocationEnabled = true
mapView.settings.myLocationButton = true
}
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
println("test") // Does not work
println(locations.count) // Does not work
if let location = locations.first as? CLLocation {
mapView.camera = GMSCameraPosition(target: location.coordinate, zoom: 15, bearing: 0, viewingAngle: 0)
locationManager.stopUpdatingLocation()
}
}
}
Unbelievable. After a day of testing I found the solution.. Code is fine. Simulation requires that you change the location
Steps:
In Xcode Simulator --> Debug --> Location --> Apple (or whatever..)
Re-run simulation
Google Map will now be centered on where you chose
in your simulation settings (Xcode --> Edit Scheme --> Allow
Simulation + Default Location = New York)
Hope this helps someone else!
The code seems okay also you have mentioned that you have updated info.plist. Can you check with "requestAlwaysAuthorization" instead of "requestWhenInUseAuthorization". Hope it may solve your problem.
You can also refer this link - http://nevan.net/2014/09/core-location-manager-changes-in-ios-8/

Resources