How to get reference to already instantiated ViewController? - ios

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.

Related

Pass data from AppDelegate to ViewController

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!

location authorization alert showing up after app closes

I created a singleton class to handle location authorization because I needed it for several views in my app. So I created the below Location.swift class.
NOTE: I have added correctly into Info.plist, and have looked at several other posts but none seem to address this (at least none I found)
protocol LocationServiceDelegate {
func tracingLocation(currentLocation: CLLocation)
func tracingLocationDidFailWithError(error: NSError)
}
class Location: NSObject,CLLocationManagerDelegate {
var latitude: Double!
var longitude: Double!
var currentLocation : CLLocation!
var locationManager: CLLocationManager?
var lastLocation: CLLocation?
var delegate: LocationServiceDelegate?
static let sharedInstance:Location = {
let instance = Location()
return instance
}()
override init() {
super.init()
self.locationManager = CLLocationManager()
self.locationManager?.delegate = self
guard let locationManagers = self.locationManager else {
return
}
if CLLocationManager.authorizationStatus() == .notDetermined {
locationManagers.requestWhenInUseAuthorization()
}
locationManagers.desiredAccuracy = kCLLocationAccuracyBest
locationManagers.pausesLocationUpdatesAutomatically = false
locationManagers.distanceFilter = 0.1
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
return
}
self.lastLocation = location
updateLocation(currentLocation: location)
}
#nonobjc func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
locationManager?.requestWhenInUseAuthorization()
break
case .authorizedWhenInUse:
locationManager?.startUpdatingLocation()
break
case .authorizedAlways:
locationManager?.startUpdatingLocation()
break
case .restricted:
// restricted by e.g. parental controls. User can't enable Location Services
break
case .denied:
// user denied your app access to Location Services, but can grant access from Settings.app
break
}
}
// Private function
private func updateLocation(currentLocation: CLLocation){
guard let delegate = self.delegate else {
return
}
delegate.tracingLocation(currentLocation: currentLocation)
}
private func updateLocationDidFailWithError(error: NSError) {
guard let delegate = self.delegate else {
return
}
delegate.tracingLocationDidFailWithError(error: error)
}
func startUpdatingLocation() {
print("Starting Location Updates")
self.locationManager?.startUpdatingLocation()
currentLocation = locationManager?.location
Location.sharedInstance.latitude = currentLocation.coordinate.latitude
Location.sharedInstance.longitude = currentLocation.coordinate.longitude
print(Location.sharedInstance.latitude, Location.sharedInstance.longitude)
// self.locationManager?.startMonitoringSignificantLocationChanges()
}
func stopUpdatingLocation() {
print("Stop Location Updates")
self.locationManager?.stopUpdatingLocation()
}
}
My app is crashing, and I think its because the location authorization is not set in the beginning. The funny thing is that the request alert which prompts the user to allow location services doesn't show up until you leave the app.
Once you close the app and accept the location services, the app works fine. So my question is, why isn't the alert showing up?
it is also interesting to note that this is only occurring through an actual device. In the simulator the alert pops up as expected when the initial view is loading.
my first view that is supposed to load and show data is as follows:
import UIKit
import Alamofire
class CurrentWeatherVC: UIViewController {
#IBOutlet weak var locationLabel: UILabel!
#IBOutlet weak var weatherIcon: UIImageView!
#IBOutlet weak var currentTempLabel: UILabel!
#IBOutlet weak var weatherTypeLabel: UILabel!
var currentWeather : CurrentWeather!
override func viewDidLoad() {
super.viewDidLoad()
Location.sharedInstance.locationManager(manager: Location.sharedInstance.locationManager, didChangeAuthorizationStatus: .authorizedWhenInUse)
currentWeather = CurrentWeather()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
Location.sharedInstance.startUpdatingLocation()
currentWeather.downloadWeatherDetails {
self.updateMainUI()
}
}
func updateMainUI() {
//Double value convterted to string for current temp.
//Added the degree symbol here
//For forecast it gets added in before saved into list so be aware of that.
currentTempLabel.text = "\(currentWeather.currentTemp)°"
weatherTypeLabel.text = currentWeather.weatherType
locationLabel.text = currentWeather.cityName
weatherIcon.image = UIImage(named: currentWeather.weatherType)
}
}
I suspect downloadWeatherDetailss implementation uses a dataTask or one of the other NSURLSession methods that run in background.
Make sure to call UI stuff only on the mainQueue:
// ...
DispatchQueue.main.async {
self.updateMainUI()
}
// ...

App crashes when dismissing view controller

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/

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()
}

Swift Closure, Missing argument for parameter #1 in call

Hello all who want to help. I am trying to do a callback function when I get my location updated. But I run into a problem that I have no idea how to solve.
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?
private let APIKey = "someString"
let location = LocationService(){
callBackFunction in
loadData()
}
func loadData()
{
let forecastService = ForecastService(APIKey: APIKey)
forecastService.getForecast(location.latitude, long: location.longitude)
{
(let currentW) in
if let currentWeather = currentW
{
dispatch_async(dispatch_get_main_queue())
{
if tjekIfNil(currentWeather.temperature)
{
self.currentTemperatureLabel?.text = "\(currentWeather.temperature!)°"
}
if tjekIfNil(currentWeather.humidity)
{
self.currentHumidityLabel?.text = "\(currentWeather.humidity!)%"
}
if tjekIfNil(currentWeather.precipProbability)
{
self.currentPrecipitationLabel?.text = "\(currentWeather.precipProbability!)%"
}
if tjekIfNil(currentWeather.icon)
{
self.currentWeatherIcon?.image = currentWeather.icon
}
if tjekIfNil(currentWeather.summary)
{
self.currentWeatherSummary?.text = currentWeather.summary
}
}
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
location.requestPermission()
location.getLocation()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The error occurs here when I call loadData()
let location = LocationService(){
callBackFunction in
println("data is loading")
loadData()
}
Here is the LocationService class
import CoreLocation
class LocationService: NSObject, CLLocationManagerDelegate
{
var locationManager : CLLocationManager!
var location : CLLocation?
var longitude : Double?
var latitude : Double?
let callBackFunction : ()->()
init(callBackFunc: (Void->Void))
{
locationManager = CLLocationManager()
callBackFunction = callBackFunc
}
func requestPermission()
{
locationManager.requestWhenInUseAuthorization()
}
func getLocation()
{
if(CLLocationManager.authorizationStatus() == CLAuthorizationStatus.AuthorizedWhenInUse)
{
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}else
{
location = nil
longitude = nil
latitude = nil
}
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!)
{
location = locationManager.location
if tjekIfNil(location?.coordinate.longitude)
{longitude = location!.coordinate.longitude}
if tjekIfNil(location?.coordinate.latitude)
{latitude = location!.coordinate.latitude}
locationManager.stopUpdatingLocation()
println(longitude)
println(latitude)
callBackFunction()
}
}
From what I have found myself it is something about calling loadData() as a class function while it is a instance function.
Im still very new to swift so I don't really know how to solve the problem, so any help would be greatly appreciated.
Edit:
I found the solotion. The reason that the error was popping op was because the function loadData() was initilaized but the callBackFunction wasn't properly initilaized. I solved it by moving the callBackFunction to getLocation() instead of the initialization.
The issue is with the way you are passing the callback, instead of:
let location = LocationService(){
callBackFunction in
loadData()
}
You need to pass the callback like:
let location = LocationService(callBackFunc:loadData)

Resources