How does one call a function from ViewController in AppDelegate? - ios

I am making an app that uses iBeacon. When the iBeacon is detected (inside AppDelegate), the app detects the phone's coordinates and forwards them to an API.
I have a function called runGPS() set up in ViewController which does the above and am trying to get it to execute when the iBeacon is detected.
When I do ViewController().runGPS(), I get an error: Missing parameter for 'coder' in call
I've looked up this issue and keep going around in circles on resolving it. One correction leads to another error etc. etc. etc.
How does one correct this or is my overall strategy for this wrong?
func locationManager(manager: CLLocationManager,
didEnterRegion region: CLRegion) {
manager.startRangingBeaconsInRegion(region as! CLBeaconRegion)
manager.startUpdatingLocation()
NSLog("You entered the region")
ViewController(NSCoder)!.runGPS()
}

I have also implemented iBeacon in my application. You have to add this location manager delegate in that viewcontroller and set delegate there like
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
and you can check in viewController if any user enter to your beacon region then this delegate method will run
func locationManager(manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], inRegion region: CLBeaconRegion) {
}
you can check your beacon in beacons array received in parameter.

You should separate out the runGPS() code from the ViewController class if its not meant to be there. Is it using any of the ViewController's functionality? If not then you should move it to a separate class meant for passing data to API. If you cannot do that then you can declare runGPS as a static method and called ViewController.runGPS() provided you are not using any of the properties of the ViewController in runGPS() method

Related

Properly launching an iOS app into the background from a location event

(iOS 11, Swift 4.1)
I wrote a cordova plugin in swift to handle region monitoring, but it isn't working properly when my app is suspended or killed. Here is my class with the relevant functions:
class GeofenceManager : NSObject, CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
switch state {
case .inside:
log ("Did Enter Region: " + region.identifier)
self.postGeofenceTransition(region: region, transitionType: 1)
break
case .unknown:
log ("Unknown Transition for region: " + region.identifier)
// self.postGeofenceTransition(region: region, transitionType: 1)
break
default:
break
}
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
log ("Did Exit Region: " + region.identifier)
self.postGeofenceTransition(region: region, transitionType: 2)
}
func locationManager(_ manager: CLLocationManager, didStartMonitoringFor region: CLRegion) {
log("Did start monitoring region: " + region.identifier)
self.locationManager.requestState(for: region)
}
}
(I do it this way to handle someone that's already in the region when we start monitoring)
I've also got "didChangeAuthorizationStatus" that fetches and sets all the geofences, that is working fine.
In my AppDelegate (objc) I'm attempting to start Location Services if the app is opened from a region event, with
if([launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]) {
_locationManager = [[CLLocationManager alloc] init];
[_locationManager setDelegate:self];
[_locationManager setDistanceFilter:kCLHeadingFilterNone];
//change the desired accuracy to kCLLocationAccuracyBest
[_locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
//SOLUTION: set setPausesLocationUpdatesAutomatically to NO
[_locationManager setPausesLocationUpdatesAutomatically:NO];
[_locationManager startUpdatingLocation];
}
My question is, if my app is opened from an event, i.e. "didEnterRegion", I immediately start location services, but will I hit my "didEnterRegion" delegate again? And am I doing something wildly wrong here?
I think I'm missing some understanding of iOS application states, have dug into the docs but it hasn't clicked yet. Any insight is very appreciated.
My question is, if my app is opened from an event, i.e. "didEnterRegion", I immediately start location services, but will I hit my "didEnterRegion" delegate again?
If your app was suspended or terminated, and if it is awakened or launched just to receive didEnterRegion, it stays in the background. It is given time just to handle this one event and then it is suspended.
You cannot start location updates with startUpdatingLocation at that point. You are in the background! Even if you are authorized for background updates, you cannot start getting updates while you are in the background. And even if you could, your code would fail, because you never set allowsBackgroundLocationUpdates. And even if you could do that, you wouldn't get any events, because you have no implementation of didUpdateLocation.
The correct procedure is: do nothing. Just handle didEnterRegion and get out. If the user enters the region again, your didEnterRegion will be called again. Region monitoring just goes on forever until you stop it (and make sure you do stop it, or the user may be be forced to delete your app).
Addendum: How to respond to being launched from scratch in the background like this? You can detect the key in didFinishLaunchWithOptions and handle it there and return false, or you can ignore it and return true and receive didEnterRegion. But either way you must immediately create a location manager and appoint its delegate or you will get nothing, obviously. This is why you should always create the location manager and set its delegate in your app delegate or root view controller, something that always exists, and exists as early as possible in the life of the app, as soon as it launches.

Getting current location using CLLocationManager in Swift

How do I get the current location of my iOS device?
It seems that Apple has made changes that to how get location and I could not find an up-to-date post. I answered this question myself below:
I couldn't find an up-to-date solution to getting the current location so here's how I did it.
My source is apple's sample code on location tracking and smart watch called PotLocCoreLocationwithiPhoneandAppleWatch: https://github.com/robovm/apple-ios-samples/tree/master/PotLocCoreLocationwithiPhoneandAppleWatch
Here's what you have to do in your app.
Note that everything from steps 2-6 are in this gist: https://gist.github.com/JonMercer/75b3f45cda16ee5d1e0955b2492f66dd
In the project settings in xcode, click on the capabilities tab (it's the tab beside the general tab where you put in your bundle identifier). Then turn on Background Mode and enable Location updates. EDIT thanks to #Rob: And also add NSLocationWhenInUseUsageDescription to your plist. The value can be anything you like
Inside your view controller, extend the CLLocationManagerDelegate
class YourClass: UIViewController, CLLocationManagerDelegate {
Implement these two functions
/**
Increases that location count by the number of locations received by the
manager. Updates the batch count with the added locations.
*/
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
//use the "locations" variable here
}
/// Log any errors to the console.
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("Error occured: \(error.localizedDescription).")
}
Create a let manager = CLLocationManager() variable
On viewDidLoad() set the following: manager.requestWhenInUseAuthorization(), manager.allowsBackgroundLocationUpdates = true, and manager.startUpdatingLocation()
In order to stop tracking do the following: manager.stopUpdatingLocation() and manager.allowsBackgroundLocationUpdates = false

CLLocationManagerDelegate Method Not Being Called

I am coding in Swift and I want to use the user's location in my app. I have already added NSLocationWhenInUseUsageDescription and NSLocationAlwaysUsageDescription to Info.plist and have properly configured them. I have also added CoreLocation.framework and imported it in the ViewController. I am also using CLLocationManagerDelegate in my class definition. I have implemented the method didChangeAuthorizationStatus and this works when the user chooses to allow for their location to be given to the app. I have printed out text to console when this happens to make sure. However, when I call the method didUpdateLocations from this method right after printing to the console, the method does not run and nothing new is printed to console. I have made sure to include a print command in the didUpdateLocations method just to make sure of this. This is the code for one of the methods:
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if status == .AuthorizedWhenInUse {
print("permission granted")
locationManager.startUpdatingLocation()
} else {
print(status)
}
}
As you can see, in the method, locationManager.startUpdatingLocation() is called and it is supposed to start doing that. Here is the code for this method:
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print(locations)
}
However, once I press Allow in my app when it asks me, it doesn't print anything to the console besides "permission granted", which was called in the first method. How do I fix this? Any help is greatly appreciated!
EDIT:
Well, actually I just fixed the problem by simulating movement in the simulator phone. You just have to click Debug, hover over Location, and pick a form of movement!

Beacon Ranging in the Background on iOS

I'm currently trying to find a way to range beacons in the background in iOS by using location monitoring and then triggering the ranging like so:
func locationManager(manager: CLLocationManager, didDetermineState state: CLRegionState, forRegion region: CLRegion) {
if (state == .Inside) {
locationManager.startRangingBeaconsInRegion((region as? CLBeaconRegion)!)
}
}
I'm then trying to get an API call to be made in the beacon ranging
func locationManager(manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], inRegion region: CLBeaconRegion) {
for beacon in beacons {
let minor = beacon.minor as Int
let major = beacon.major as Int
do {
try APICall.canSeeBeacons(major, minor: minor)
} catch {
print("Error making API call")
}
}
}
However this only works for about ten minutes while the phone is in the background, after ten minutes it no longer works but I'm hoping to make it continuous so that API calls can always be made when a beacon is found. I do also have the correct keys set in the permissions and I'm using requestAlwaysAuthorization() on my locationManager
Take a look on the Apple's iOS Dev Library regarding Background Executions:
https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html
If the type of your app is one of the accepted types for longer background executions, add the key Required background modes to your Info.plist with an array, containing one or more of the enabled types: audio, location, voip, newsstand-content, external-accessory and/or bluetooth-central.
Just note that this will be reviewed by Apple before publishing your app on the App Store.
EDITED:
I didn't use this for a while, but tested now and it seems it's even easier since Xcode 6.
Follow the steps:
1. Add the key Required background modes to your Info.plist
2. Go to Capabilities
3. Select the background mode(s) that fit.
I had the same problem. As said a commentator I've moved location manager delegate methods into the App Delegate. And also I added this:
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
And it works.

CLLocationManager didUpdateLocations not being called iOS 8 with plist entries

I'm having some issues with CLLocationManager. This code used to work on iOS 8.2 but since upgrading to 8.3 it doesn't work. Here is the code for setting up the location manager which is called on startup.
let distanceThreshold:CLLocationDistance = 100.0
var currentLocation:CLLocationCoordinate2D?
override init() {
assert(locMan == nil)
super.init()
locMan = self
let locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
currentLocation = manager.location.coordinate
if UIApplication.sharedApplication().applicationState == .Background {
PlacesManager.fetchNearbyPlaces(LocationManager.getLocationManager().currentLocation!, radius: distanceThreshold, callback: placesCallback)
}
}
With this code didUpdateLocations is never called despite it being called before.
I have added the relevant entries to the Info.plist file:
I have tried it on both a device and the simulator and neither works. In fact it seems that it is no longer requesting location authorisation anymore if I delete the app and reinstall.
I know I'm missing something stupid but I can't workout what the hell it is.
I'd appreciate any help people can provide.
Cheers,
Gerard
Answer from Anna:
The CLLocationManager documentation says: "To configure and use a CLLocationManager object to deliver events...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."
In addition to the accepted answer, if you are using the simulator. You have to select a location after the app is running (Debug->Location->apple for apple headquarters). I set the location and assumed that the next time I ran the app that didUpdateLocations would be called with what I had set previously, but that assumption was wrong.

Resources