Getting user's current location but getting back "Optional" California.. etc - ios

I'm trying to get the user's current location using Swift. Here is what I am currently using:
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
let locationManager = CLLocationManager();
//Info about user
#IBOutlet weak var userTF: UITextField!
#IBOutlet weak var BarbCustTF: UITextField!
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.requestWhenInUseAuthorization();
self.locationManager.startUpdatingLocation();
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// GPS STUFF
// UPDATE LOCATION
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
CLGeocoder().reverseGeocodeLocation(manager.location!) { (placemarks, ErrorType) -> Void in
if(ErrorType != nil)
{
print("Error: " + ErrorType!.localizedDescription);
return;
}
if(placemarks?.count > 0)
{
let pm = placemarks![0] ;
self.displayLocationInfo(pm);
}
}
}
// STOP UPDATING LOCATION
func displayLocationInfo(placemark: CLPlacemark)
{
self.locationManager.stopUpdatingLocation();
print(placemark.locality);
print(placemark.postalCode);
print(placemark.administrativeArea);
print(placemark.country);
}
// PRINT OUT ANY ERROR WITH LOCATION MANAGER
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("Error: " + error.localizedDescription);
}
Everything seems to work fine, but the output I'm getting is like really weird and says Optional in front of it, and is definitely (unfortunately) not my current location.
This is the output I'm getting when I print it to the console
Optional("Cupertino")
Optional("95014")
Optional("CA")
Optional("United States")
Things I've tried:
1) In my info.plist I have : NSLocationWhenInUseUsageDescription
2) I've also heard weird stuff happens and I tried going to Debug>>Location>> and changing it to in city and all sorts of things (didn't help)
I think that the problem is something in my function LocationManager that has to do with like "wrapping" or something ? I'm not sure, this is my first day messing with iOS programming with Swift and I don't really know what wrapping is but I think that may be what's going on from what I've seen on the internet... Basically I dont understand why i'm printing out some default apple locations (California.. blah blah) I don't live in Cali (unfortunately).

Instead of this
print(placemark.locality);
do this
if let locality = placemark.locality {
print(locality)
}
The if let pattern here is a way of only printing locality if it's not nil. This is the way to do it in this case.
If you were sure that locality was never nil, you could do
print(placemark.locality!)
but if locality happened to be nil, your app would crash on that line.

Related

Is it bad idea to create static stream in reactive programming?

Now I'm working on iOS using RxSwift framework. In my app I have to user user location, but I don't need it to be updated in real time. It's enough if location updated every time user opens app or does some defined action. Therefore, how about implementing singleton class where the last result is cached. Each update by action changes cached result and accepts it to the stream. Stream's default value is cached value. Then, views where user location is needed would subscribe on this stream.
Example implementation using Cache and RxSwift
import Foundation
import Cache
import CoreLocation
import RxSwift
import RxCocoa
class UserLocationManager: NSObject {
private enum Keys: String {
case diskConfig = "Disk Config"
case lastLocation = "User Last Location"
}
// MARK: - Variables
private func cache<T: Codable>(model: T.Type) -> Cache.Storage<T> {
let storage = try! Cache.Storage(diskConfig: DiskConfig(name: Keys.diskConfig.rawValue), memoryConfig: MemoryConfig(expiry: .never), transformer: TransformerFactory.forCodable(ofType: model))
return storage
}
private let locationManager = CLLocationManager()
private var lastPosition: MapLocation? {
get {
do {
return try cache(model: MapLocation.self).object(forKey: Keys.lastLocation.rawValue)
}
catch { return nil }
}
set {
do {
guard let location = newValue else { return }
try cache(model: MapLocation.self).setObject(location, forKey: Keys.lastLocation.rawValue)
}
catch { }
}
}
private let disposeBag = DisposeBag()
static let shared = UserLocationManager()
var locationStream = BehaviorRelay<CLLocationCoordinate2D?>(value: nil)
// MARK: - Methods
func updateLocation() {
if CLLocationManager.locationServicesEnabled() {
locationManager.requestLocation()
}
}
func subscribe() {
locationStream.accept(lastPosition?.clCoordinate2D)
locationStream.subscribe(onNext: { [weak self] location in
guard let `self` = self else { return }
guard let location = location else { return }
self.lastPosition = MapLocation(latitude: location.latitude, longitude: location.longitude)
}).disposed(by: disposeBag)
locationManager.delegate = self
}
// MARK: - Lifecycle
override init() {
super.init()
defer {
self.subscribe()
}
}
}
// MARK: - CLLocationManagerDelegate
extension UserLocationManager: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else { return }
UserLocationManager.shared.locationStream.accept(location.coordinate)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
}
}
There's no problem conceptually of having a global stream that can be subscribed to. However, your specific implementation is worrisome to me.
One of the cool things about Observable streams is that they are lazy, no work is done unless needed, but you are writing extra code to bypass that feature and I don't think it's necessary. Also, storing there current location when the app goes into the background and just assuming that is a valid location when the app comes back to the foreground (possibly weeks later) sounds inappropriate to me.
The RxCocoa package already has an Rx wrapper for CLLocationManager. It seems to me it would be far simpler to just use it. If you only need one location update then use .take(1). I'd be inclined to add a filter on the accuracy of the location before the take(1).

How to catch GPS data of func LocationManager?

i got some problems with this whole UIViewController thing. My thoughts were, that the viewDidLoad() would be something like a main() in other languages, but especially in this case I don't see any functions called in the viewDidLoad() func.
First of all I am totally confused by the var locationManager which is actually a CLLocationManager and a func at the same time. How?
Where do I call the func locationManager? Can I return the locValue.latitude and the locValue.longitude? How do I catch them in the viewDidLoad()? Finally I want to send these two parameters to something, after I pressed a button (see: func SendButtonAction).
But my problem is, that I don't know how to bring these two guys from the body of func locationManager to an input in func SendButtonAction.
Appreciate any help :) I guess I need more basic knowledge.
import UIKit
import MapKit
import CoreLocation
class GPSNew: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var zurueckButton: UIButton!
#IBOutlet weak var SendButton: UIButton!
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
// Ask for Authorisation from the User.
self.locationManager.requestAlwaysAuthorization()
// For use in foreground
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}
// Do any additional setup after loading the view.
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
//guard let locValue: CLLocationCoordinate2D = manager.location?.coordinate else { return }
let locValue: CLLocationCoordinate2D = manager.location!.coordinate
print("locations = \(locValue.latitude) \(locValue.longitude)")
let userLocation = locations.last
let viewRegion = MKCoordinateRegion(center: (userLocation?.coordinate)!, latitudinalMeters: 600, longitudinalMeters: 600)
self.mapView.setRegion(viewRegion, animated: true)
//return (locValue.latitude, locValue.longitude)
}
#IBAction func SendButtonAction(_ sender: Any) {
//send the user location to something
//end updating location
locationManager.stopUpdatingLocation()
}
}
Actually the method that gives the location is asynchronous so you can try
#IBAction func sendButtonAction(_ sender: Any) {
if let loc = locationManager.location?.coordinate {
print(loc.latitude, loc.longitude)
locationManager.stopUpdatingLocation()
}
}
From your question it sounds like you're used to procedural programming. In a C command-line utility, for example, you have a main() function that gets called at the launch-time. Main calls other functions to do setup, then might have a loop that steps through the work it has to do, and then either loops, waiting for input from the user, or returns if it's a "one-and-done" utility.
Apps for most (all?) GUI-based OS'es don't work that way. They are event-driven, and usually use an object-oriented design. You should read up on event-driven development. Until you study it, you're going to be very confused and won't be able to get off of square one.
Here is a short intro to the concepts, but this is a much deeper topic than we can cover in a simple post:
In an object-oriented,event-driven program, you create objects that have methods (functions) that respond to things that happen. Your program defines a set of starting objects, and then those objects wait for stuff to happen.
The method viewDidLoad() is an example of a method that gets called when something happens. It gets called when a view controller's (an object that manages a view) view gets created. It gives you a chance to do one-time setup to get ready for the user to "do stuff." Your viewDidLoad() function does that one-time setup, and then returns.
Control then returns to the system, and your app just waits to get called again.
You might also add methods that respond to the user tapping on buttons, sliding, notifications about updated GPS locations, etc.
The location manager (CLLocationManager) is an object that you create when you want to get information about the device's location. You create one, and ask it to notify you about various types of location events. You set up an object to be the location manager's "delegate". This is like giving the location manager a phone number and saying "call this number when the user's location changes."
The location manager calls its delegate when events occur that you told it you care about.

Google places API doesn't work as expected

I am following Google Places API for IOS tutorial to view the user current place.
I used the same code in the tutorial as follow:
var placesClient: GMSPlacesClient!
// Add a pair of UILabels in Interface Builder, and connect the outlets to these variables.
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var addressLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
placesClient = GMSPlacesClient.shared()
}
// Add a UIButton in Interface Builder, and connect the action to this function.
#IBAction func getCurrentPlace(_ sender: UIButton) {
placesClient.currentPlace(callback: { (placeLikelihoodList, error) -> Void in
if let error = error {
print("Pick Place error: \(error.localizedDescription)")
return
}
self.nameLabel.text = "No current place"
self.addressLabel.text = ""
if let placeLikelihoodList = placeLikelihoodList {
let place = placeLikelihoodList.likelihoods.first?.place
if let place = place {
self.nameLabel.text = place.name
self.addressLabel.text = place.formattedAddress?.components(separatedBy: ", ")
.joined(separator: "\n")
}
}
})
}
But I get the following error in the console:
Pick Place error: The operation couldn’t be completed. The Places API
could not find the user's location. This may be because the user has
not allowed the application to access location information.
NOTE: I have set the NSLocationWhenInUseUsageDescription key (Privacy - Location When In Use Usage Description) in info.plist file.
It's confusing because I followed the tutorial step by step. And am testing the application using physical device with "Locations Services" enabled .
Any idea what I might be doing wrong?
Or is it because the documentation is not up-to-date?
This may be because the user has not allowed the application to access location information.
This points you towards your answer. For Google Places to work you need to request to use location services by calling requestWhenInUseAuthorization(). This will prompts the user to grant permission to the app to use location services.
Please refer to the Apple Docs for more info.
EDIT
You should keep a strong reference to the CLLocationManager that you create so it does not get deallocated when your function exits.
"Create an instance of the CLLocationManager class and store a strong reference to it somewhere in your app.
Keeping a strong reference to the location manager object is required until all tasks involving that object are complete. Because most location manager tasks run asynchronously, storing your location manager in a local variable is insufficient."
Taken from the CLLocationManager Docs
EXAMPLE
class LocationViewController: UIViewController, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
override func viewDidLoad()
{
super.viewDidLoad()
locationManager.delegate = self
if CLLocationManager.authorizationStatus() == .notDetermined
{
locationManager.requestWhenInUseAuthorization()
}
}
}
1. Request user for Location Usage Authorization
requestWhenInUseAuthorization() OR requestAlwaysAuthorization() according to your requirement.
2. In Info.plist, add the following keys:
a. NSLocationAlwaysUsageDescription OR NSLocationWhenInUseUsageDescription
b. NSLocationAlwaysAndWhenInUseUsageDescription
Example:
class ViewController: UIViewController, CLLocationManagerDelegate
{
let locationManager = CLLocationManager()
override func viewDidLoad()
{
super.viewDidLoad()
locationManager.requestWhenInUseAuthorization()
locationManager.delegate = self
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus)
{
}
}

Swift: Utilizing CLLocationManagerDelegate and CoreLocation.framework results in "Use of undeclared type" error

I'm going off a youtube video that gives a basic example of utilizing the location services of Xcode to program a sort of maps user interface to a program. I followed the steps in the video of setting up the program and get an assumedly simple "Use of undeclared type" error when trying to compile the application.
https://www.youtube.com/watch?v=qrdIL44T6FQ - Link to the youtube video.
The problem occurs at the class creation line.
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate
{ // ^ Use of undeclared type
#IBOutlet weak var mapView: MKMapView!
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
self.mapView.showsUserLocation = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreate-d.
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations.last
let center = CLLocationCoordinate2D(latitude: location!.coordingate.latitude, longitude: location!.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1))
self.mapView.setRegion(region, animated: true)
self.location.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager, difFailWithError error: NSError)
{
print("Errors: " + error.localizedDescription)
}
}
I'm not sure why the error is occurring.
I added the CoreLocation.framework in the "Link Binary With Libraries" section. I made sure to add the "NSLocationWhenInUseUsageDescription" to the Info.plist.
I connected the Map View Kit to the code itself at 2:26 in the video.
Other ways to go about doing this is also of interest. I'm solely trying to get a basic location services clearance to run in a program.
Version: Xcode Version 7.3.1
In swift
I was missing importing CoreLocation. I have added following line on top of my file and it worked.
import CoreLocation
I made a new project and followed the steps in the video again and the project now works appropriately. The only thing that may have caused the issue was I checked off Core Data when creating the project. I do not believe this is exactly why it was throwing an error, but if you run into a similar situation make sure this box is left empty.

parse geoPointForCurrentLocationInBackground Swift sample

Found multiple posts on this, but still can't quite piece it together.
I'm using Parse to retrieve the user's current location. Documentation makes it seem very easy, but several things appear to be missing. https://parse.com/docs/ios/guide#geopoints-getting-the-user-39-s-current-location
First, my code:
class TableViewController: UITableViewController, CLLocationManagerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
print("Get GPS")
PFGeoPoint.geoPointForCurrentLocationInBackground { (geoPoint, error ) -> Void in
if error == nil {
print("Got geoPoint") //Never reaches this
print(geoPoint)
} else {
print(error ) //No error either
}
}
I've updated the NSLocationWhenInUseUsageDescription in my info.plist
Tried simulator and two real devices.
Added CLLocationManagerDelegate
I'm trying to avoid making this more complex than it needs to be.
I've also experimented with CLLocationManager samples and it doesn't seem to be working either.
I'm using most recent versions of everything... started with Parse yesterday!
I've never been challenged for authorization to use my location. Tried that with CLLocationManager examples!
Would greatly appreciate some guidance / support.
You are right about missing a few things before you can handle the location update from Parse.
First, add a location manager property to your class.
class TableViewController: UITableViewController, CLLocationManagerDelegate {
var locationManager = CLLocationManager()
Set its delegate and use it to request authorization from the user:
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
print("Get GPS")
PFGeoPoint.geoPointForCurrentLocationInBackground { (geoPoint, error ) -> Void in
if error == nil {
print("Got geoPoint") //Never reaches this
print(geoPoint)
} else {
print(error ) //No error either
}
}
}
In your Info.plist, you need to add a key NSLocationWhenInUseUsageDescription and give it a string value for a message to the user during the authorization request.
The authorization request and the location update should work after the above changes.

Resources