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)
Related
my JSON have been printed to console with the correct value request. but label.text and city.text won't update UI in main controller
#IBOutlet weak var icon: UIImageView!
#IBOutlet weak var date: UILabel!
#IBOutlet weak var weatherType: UILabel!
#IBOutlet weak var degree: UILabel!
#IBOutlet weak var city: UILabel!
var currentWeather : CurrentWeather!
override func viewDidLoad() {
super.viewDidLoad()
currentWeather = CurrentWeather()
}
override func viewDidAppear(_ animated: Bool) {
super .viewDidAppear(animated)
locationAuthStatus()
}
func locationAuthStatus() {
if CLLocationManager.authorizationStatus() == .authorizedWhenInUse {
currentLocaion = locationManager.location
//Location.sharedIntance.latitude = currentLocaion.coordinate.latitude
//Location.sharedIntance.longitude = currentLocaion.coordinate.longitude
print(currentLocaion)
currentWeather.downloadWeatherDetail {
downloadForecastData {
self.updateMainUI()
}
}
} else {
locationManager.requestWhenInUseAuthorization()
locationAuthStatus()
}
}
func updateMainUI() {
icon.image = UIImage(named: currentWeather.weatherType)
city.text = currentWeather.cityName
degree.text = "\(currentWeather.currentTemp)"
weatherType.text = currentWeather.weatherType
date.text = currentWeather.date
}
}
I am new to coding and 'm having a hard figuring out why isn't it showing since I confirmed that I am pulling data from Json to my console
These lines won't do what you expect.
locationManager.requestWhenInUseAuthorization()
locationAuthStatus()
Your call to requestWhenInUseAuthorization is asynchronous and should be handled by a CLLocationManagerDelegate which can be your view controller. You can do this like this:
class ViewController: UITableViewController, CLLocationManagerDelegate {
// rest of class
Add these functions to your class
func locationManager(_ manager: CLLocationManager, didUpdateLocations objects: [CLLocation]) {
let location = objects[objects.count-1]
let currentLocation = location
print(currentLocation)
currentWeather.downloadWeatherDetail {
downloadForecastData {
self.updateMainUI()
}
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
// handle error
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
locationManager.startUpdatingLocation()
}
I'm currently developing an iOS app, and one of its functions include locating places of interest for nightlife. However, when my MapView loads, current user location is not displayed, even though location access has been granted.
Here is my code:
import UIKit
import MapKit
import CoreLocation
class BarMapVC: UIViewController {
#IBOutlet weak var searchBtn: UIBarButtonItem!
#IBOutlet weak var doneBtn: UIBarButtonItem!
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var locationBtn: UIButton!
#IBOutlet weak var barMapView: MKMapView!
var locationManager = CLLocationManager()
let authorizationStatus = CLLocationManager.authorizationStatus()
let regionRadius: Double = 1000
override func viewDidLoad() {
super.viewDidLoad()
searchBar.isHidden = true
barMapView.delegate = self
locationManager.delegate = self
configureLocationServices()
centerMapOnUserLocation()
}
#IBAction func searchBtnWasPressed(_ sender: Any) {
searchBar.isHidden = false
}
#IBAction func doneBtnWasPressed(_ sender: Any) {
dismiss(animated: true)
}
#IBAction func locationBtnWasPressed(_ sender: Any) {
if authorizationStatus == .authorizedAlways || authorizationStatus == .authorizedWhenInUse {
centerMapOnUserLocation()
}
}
}
extension BarMapVC: MKMapViewDelegate {
func centerMapOnUserLocation() {
guard let coordinate = locationManager.location?.coordinate else { return }
let coordinateRegion = MKCoordinateRegionMakeWithDistance(coordinate, regionRadius * 2.0, regionRadius * 2.0)
barMapView.setRegion(coordinateRegion, animated: true)
}
}
extension BarMapVC: CLLocationManagerDelegate {
func configureLocationServices() {
if authorizationStatus == .notDetermined {
locationManager.requestAlwaysAuthorization()
} else {
return
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
centerMapOnUserLocation()
}
}
I'm using CLLocation Manager, complying to mapViewDelegate, as well as have all needed outlets. Anything I' m doing wrong?
You can set this on your map view:
barMapView.showsUserLocation = true
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()
}
// ...
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 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.