Pass data from AppDelegate to ViewController - ios

I know this question has been asked a lot but I can't find a similar to my situation. I use Google Maps SDK and I want pass my current location coordinates from app delegate to my ViewController. I know how to pass it with
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate but I want to change my location every time my view loads. So I need my current location ONLY on start.
AppDelegate:
import UIKit
import GoogleMaps
import GooglePlaces
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {
var window: UIWindow?
let storyboard = UIStoryboard(name: "Main", bundle: nil)
var currentLocation:CLLocation?
var locationManager:CLLocationManager?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
GMSServices.provideAPIKey("KEY")
GMSPlacesClient.provideAPIKey("KEY")
setupLocationManager()
return true
}
func setupLocationManager(){
locationManager = CLLocationManager()
locationManager?.delegate = self
locationManager?.requestWhenInUseAuthorization()
locationManager?.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager?.startUpdatingLocation()
}
// Below method will provide you current location.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if currentLocation == nil {
currentLocation = locations.last
locationManager?.stopMonitoringSignificantLocationChanges()
let locationValue:CLLocationCoordinate2D = manager.location!.coordinate
print(locationValue)//THIS IS WHAT I WANT TO PASS
locationManager?.stopUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error")
}
}
ViewController:
import UIKit
import GoogleMaps
class MapViewController: UIViewController, GMSMapViewDelegate {
#IBOutlet weak var mapView: GMSMapView!
var location : CLLocationCoordinate2D?
override func viewDidLoad() {
super.viewDidLoad()
print(location)
}
}

If you really want to do it that way you can get your location from AppDelegate every time the view load in your MapViewController like so:
import UIKit
import GoogleMaps
class MapViewController: UIViewController, GMSMapViewDelegate {
#IBOutlet weak var mapView: GMSMapView!
var location : CLLocationCoordinate2D?
override func viewDidLoad() {
super.viewDidLoad()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
self.location = appDelegate.currentLocation
print(location)
}
}
I'd argue that AppDelegate really isn't the right place to have this CLLocationManager logic, though. If this is the only UIViewController using the location you might just consider implementing that logic inside MapViewController instead. If it is shared by many screens perhaps you should create a LocationManager class that you implement as a singleton so that all of your UIViewController subclasses have access to the up-to-date location data.

You could extend CLLocationManagerDelegate on your MapViewController instead, that way you have access to any variable from your view controller
Create new file: MapViewCLLocationExtension.swift
import Foundation
import UIKit
import GoogleMaps
import GooglePlaces
extension MapViewController: CLLocationManagerDelegate {
...
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if currentLocation == nil {
currentLocation = locations.last
locationManager?.stopMonitoringSignificantLocationChanges()
let locationValue:CLLocationCoordinate2D = manager.location!.coordinate
//print(locationValue)//THIS IS WHAT I WANT TO PASS
location = locationValue
locationManager?.stopUpdatingLocation()
//You should call a method here for any further thing you need to
//do with location. DO NOT USE location on viewDidLoad
useLocation()
}
}
I must warn you tho, you can't print the location on viewDidLoad(), b/c it won't be there yet, you need to move any logic to a method and call it at the point where you are sure its gonna be there.
Hope this helps!

Related

Error creating a global CLLocationManager

Apple Docs suggest not to store your CLLocationManager in a local variable. So I created a global constant outside the scope of my ViewController class just after the import statements. Trying to access the constant inside the class, however, throws compiler errors:
A similarly declared globalDictionaryconstant of type NSMutableDictionary seems to be accessible inside the class.
What is it that I am doing wrong here? Why does the above not work?
Code:
//
// ViewController.swift
// CoreLocationExample
//
//
import UIKit
import CoreLocation
import Foundation
let locationManager = CLLocationManager()
let globalDictionary = NSMutableDictionary()
class ViewController: UIViewController, CLLocationManagerDelegate {
// let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
let dict = [
"name":"John"
]
globalDictionary.addEntries(from: dict)
print(globalDictionary)
}
}
Using Swift 3 on Xcode Version 8.3.1
Apple Docs suggest not to store your CLLocationManager in a local variable.
It means do not create local instance inside a method / function.
please declare like this.
class AppDelegate: UIResponder, UIApplicationDelegate {
//CLLocation Manager
let locationManager = CLLocationManager()
var locValue = CLLocationCoordinate2D()
}
CLLocation Manager or globalDictionary Inside the class.
use singleton class for this
import UIKit
import CoreLocation
public class LocationManager: NSObject,CLLocationManagerDelegate {
public static let sharedInstance = LocationManager()
public lazy var locationManager: CLLocationManager = CLLocationManager()
var globalDictionary = NSMutableDictionary()
public override init() {
super.init()
self.locationManager.requestAlwaysAuthorization()
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.distanceFilter = kCLDistanceFilterNone;
self.locationManager.startUpdatingLocation()
self.locationManager.delegate = self
let dict = [
"name":"John"
]
self.globalDictionary.addEntries(from: dict)
print(self.globalDictionary)
}
// MARK: - Location Manager Delegate
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error)
}
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
let newLocation: CLLocation? = locations.last
let locationAge: TimeInterval? = -(newLocation?.timestamp.timeIntervalSinceNow)!
if Double(locationAge!) > 5.0 {
return
}
if Double((newLocation?.horizontalAccuracy)!) < 0 {
return
}
}
public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
}
}
You can do it in the following way -
class LocationTracker {
static var locationManager: CLLocationManager? = LocationTracker.sharedLocationManager()
class func sharedLocationManager() -> CLLocationManager {
let lockQueue = DispatchQueue(label: "self")
lockQueue.sync {
if _locationManager == nil {
_locationManager = CLLocationManager()
_locationManager?.desiredAccuracy = kCLLocationAccuracyBestForNavigation
_locationManager?.allowsBackgroundLocationUpdates = true
_locationManager?.pausesLocationUpdatesAutomatically = false
}
}
return _locationManager!
}
}
And when you need to call it in your code, you can do the below -
var locationManager: CLLocationManager? = LocationTracker.sharedLocationManager()
you can then add other Location related methods in this class as well like for startt tracking, update location, stoptracking etc.

I'm trying to find present location, But i'm not getting present location

I'm trying to find present location coordinates,but my program not calling didUpdateLocations method. And Also i add NSLocationWhenInUseUsageDescription in info.plist. Any help please (i'm using Version 7.2 (7C68)). I'm Testing simulator only.
import UIKit
import CoreLocation
import MapKit
class ViewController: UIViewController, CLLocationManagerDelegate {
var location: CLLocationManager!
override func viewDidLoad() {
let location = CLLocationManager()
location.delegate = self
location.desiredAccuracy = kCLLocationAccuracyBest
location.startUpdatingLocation()
location.requestWhenInUseAuthorization()
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
let cord = locations[0] as CLLocation
let lati = cord.coordinate.latitude
let long = cord.coordinate.longitude
print(lati,"",long)
}
}

error EXC Bad Instruction trying to get current location from another class?

I had a Massive View Controller and attempting to separate my code into different classes.
I created a class CurrentLocation. In the View Controller I called the google maps method animateToLocation and I get an EXC Bad Instruction. Have done a fair amount of research on proper app architecture but still new and learning through experience. Using google maps for iOS and trying to implement this properly. Is it acceptable to put the updating location in a separate class from the ViewController Then just call the methods I desire to call in the ViewController? I am thinking that I've implemented Model-View-Controller correctly and maybe just inheriting something I should not have. Should be a simple fix just have no idea where to start.
import UIKit
import GoogleMaps
class ViewController: UIViewController, CLLocationManagerDelegate, GMSMapViewDelegate {
#IBOutlet weak var googleMapView: GMSMapView!
let locationManager = CLLocationManager()
let currentLocation = CurrentLocation()
override func viewDidLoad() {
super.viewDidLoad()
currentLocation.trackLocation
}
override func viewDidAppear(animated: Bool)
{
super.viewDidAppear(animated)
if CLLocationManager.locationServicesEnabled() {
googleMapView.myLocationEnabled = true
googleMapView.animateToLocation(currentLocation.coordinate) // EXC Bad Instruction
} else
{
locationManager.requestWhenInUseAuthorization()
}
}
import Foundation
import CoreLocation
import GoogleMaps
class CurrentLocation: NSObject,CLLocationManagerDelegate {
override init()
{
super.init()
}
var locationManager = CLLocationManager()
var location : CLLocation!
var coordinate : CLLocationCoordinate2D!
var latitude : CLLocationDegrees!
var longitude : CLLocationDegrees!
func trackLocation()
{
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
if CLLocationManager.locationServicesEnabled() {
location = locations.last
coordinate = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
latitude = coordinate.latitude
longitude = coordinate.longitude
}
}
This error is happening because currentLocation.coordinate is nil and you're accessing it as an implicitly unwrapped optional. Basically, you're trying to access a variable before it has anything in in. You need to initialize a CLLocationManager, ask for permissions, and then start updating the location. Check out this great writeup from NSHipster: Core Location in i​OS 8.

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.

Core Location to an iOS Framework

I am creating an iOS Framework and i want to use Core Location to interact with Beacons. For testing reasons i am trying to get user location.
This is the class i created in the framework.
import Foundation
import CoreLocation
public class BeaconManager:NSObject,CLLocationManagerDelegate{
var locationManager:CLLocationManager = CLLocationManager()
public override init() {
super.init()
locationManager.requestAlwaysAuthorization()
locationManager.delegate = self
locationManager.startUpdatingLocation()
}
public func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
if let location = locations.first as? CLLocation {
println(location)
}
}
}
And i am calling it from a test app that has the framework like this
import UIKit
import OtravitaSDK
import CoreLocation
class ViewController: UIViewController {
var bm = BeaconManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
But is not working , is not printing the location. I have set the NSLocationAlwaysUsageDescription both in framework's info.plist and the app's info.plist
you can add your decription in NSLocationAlwaysUsageDescription & NSLocationWhenInUseUsageDescription in plist
This code put into AppDelegate file
var locationManager:CLLocationManager?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
//You can give this permission for fetch current location
var type = UIUserNotificationType.Badge | UIUserNotificationType.Alert | UIUserNotificationType.Sound;
var setting = UIUserNotificationSettings(forTypes: type, categories: nil);
UIApplication.sharedApplication().registerUserNotificationSettings(setting);
UIApplication.sharedApplication().registerForRemoteNotifications();
locationManager = CLLocationManager()
locationManager?.requestAlwaysAuthorization()
locationManager?.delegate = self
locationManager?.startUpdatingLocation()
// Override point for customization after application launch.
return true
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
if let location = locations.first as? CLLocation {
println(location)
}
}
What you need to do as of iOS 8 is configure your Info.plist file to cater for 2 kinds of location behaviour. You need to supply a default message that appears with a popup by default, asking the user for consent to use their location.
NSLocationWhenInUseUsageDescription and NSLocationAlwaysUsageDescription
See this article for a full walkthrough and another SO post which discusses this topic. Hope this helps!
You can create at first the reporter class (with shared instance) which will implement the CLLocationManagerDelegate, so you could implement your logic inside delegate methods
import Foundation
import CoreLocation
class LocationReporter: NSObject, CLLocationManagerDelegate {
static let sharedInstance = LocationReporter()
func startUpdating(locationManager: CLLocationManager) {
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
func stopUpdating(locationManager: CLLocationManager) {
locationManager.stopUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.first {
print("latitude: ", location.coordinate.latitude)
print("longitude: ", location.coordinate.longitude)
}
}
//implement other locationManger delegate methods
}
Next you can create a Client class
import Foundation
import CoreLocation
class LocationDetectionClient {
private let locationManager = CLLocationManager()
func start() {
LocationReporter.sharedInstance.startUpdating(locationManager: locationManager)
}
func stop() {
LocationReporter.sharedInstance.stopUpdating(locationManager: locationManager)
}
}
And finally call the Client methods where you need
let locationDetectionClient = LocationDetectionClient()
public func startLocationDetection() {
locationDetectionClient.start()
}
public func stopLocationDetection() {
locationDetectionClient.stop()
}
Hope this would help

Resources