How to delay the prompt for location services permission - ios

In my app I'm using CLLocationManager and MKMapView. When app launches, I present the user with a disclaimer (once) which has to be accepted. However, when the disclaimer is shown, a popup appears requesting access to the user location.
Is there a way to delay this alertView until the disclaimer is accepted?
Please advice.
EDIT:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
if([[NSUserDefaults standardUserDefaults] objectForKey:#"disclaimerAccepted"] == nil) {
[self firstRun];
[[NSUserDefaults standardUserDefaults] synchronize];
}
else
{
[self locationStuff];
}
)

The location alert view is only displayed when you actually request the user's location, so the simplest way to do this would be to ensure that the first time your app is run you start the CLLocationManager or display the MKMapView until after your disclaimer has been accepted. This may require you to move around some methods.

Maybe you need first check for disclaimer and if it accepted start CLLocationManager. If disclaimer not accepted - wait for acceptation and that start CLLocationManager.
- (void)viewDidLoad
{
[super viewDidLoad];
if (disclaimerAccepted)
{
//start CLLocationManager
}
else
{
//Show disclaimer
}
}
- (void)disclaimerAccepted
{
disclaimerAccepted = YES;
//start CLLocationManager
}

Related

iOS 13 call requestAlwaysAuthorization again after user "Allow Once" only

During requesting for Always permission on iOS13, the user can tap "Allow Once" which will call appropriate delegate with status kCLAuthorizationStatusAuthorizedWhenInUse but requesting for "Always" again calls delegate with kCLAuthorizationStatusAuthorizedAlways. Why? When other combinations work only once like you request always, you get it and even calling again will not call delegate with status.
Sample code to test:
#import CoreLocation;
#interface ViewController () <CLLocationManagerDelegate>
#property (strong, nonatomic) CLLocationManager *locationManager;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
}
- (IBAction)doauthloc:(id)sender {
[self.locationManager requestAlwaysAuthorization];
}
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
switch(status) {
case kCLAuthorizationStatusNotDetermined:NSLog(#"AUTH STATUS:kCLAuthorizationStatusNotDetermined"); break;
case kCLAuthorizationStatusRestricted:NSLog(#"AUTH STATUS:kCLAuthorizationStatusRestricted"); break;
case kCLAuthorizationStatusDenied:NSLog(#"AUTH STATUS:kCLAuthorizationStatusDenied"); break;
case kCLAuthorizationStatusAuthorizedAlways:NSLog(#"AUTH STATUS:kCLAuthorizationStatusAuthorizedAlways"); break;
case kCLAuthorizationStatusAuthorizedWhenInUse:NSLog(#"AUTH STATUS:kCLAuthorizationStatusAuthorizedWhenInUse"); break;
};
}
#end
It's a little confusing, isn't it? When you ask for Always and the user taps Allow Once, you are told that you got WhenInUse. But that doesn't actually matter. You have provisional Always. So:
When you subsequently go into the background and start monitoring visits or regions or whatever your location monitoring usage is, this will be converted into Always authorization for purposes of usage. (Your logging should confirm this.)
And then, because you got only Once authorization, when you come back to the foreground you will be Not Determined again.
So the takeaway is, just laugh an evil laugh and move on. Your background location monitoring will work, and that's all that matters. Not only does it work, but as a bonus you get to present the user with the authorization alert again, which is the reason for all this change in iOS 13. Don't worry, be happy.

Tracking user usage time and time on UIViewController like Google Analytics

What is the best way to track App usage time and time a user spends viewing a Screen (or interacting with a UIView) for use within the App itself? Google Analytics seems to do a wonderful job, but the numbers I want to use inside the App itself to unlock items and areas of the App.
You could probably roll your own solution based on Core Data, or if your data is small you could even think of using NSDefaults.
Here's a good start. It involves having a base view controller which you should inherit from in each view controller you want to measure the time spent:
#interface BaseViewController : UIViewController
- (NSString *)screenKey;
+ (NSInteger)secondsInScreen:(NSString *)screenKey;
#end
The implementation simply measures the seconds between the appearance of the screen until it disappears. It's very important to notice the appDidEnterForeground and appDidEnterBackground notifications. When you send your app to the background or it comes back to the foreground, viewDidAppear and viewDidDisappear are not called.
#import "BaseViewController.h"
#implementation BaseViewController {
NSDate *_startDate;
}
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appDidEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self startMeasuring];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self stopMeasuring];
}
- (void)appDidEnterBackground:(NSNotification *)not {
[self stopMeasuring];
}
- (void)appDidEnterForeground:(NSNotification *)not {
[self startMeasuring];
}
- (void)startMeasuring {
_startDate = [NSDate date];
}
- (void)stopMeasuring {
NSInteger secondsInScreen = ABS([_startDate timeIntervalSinceNow]);
[self addSecondsToScreen:secondsInScreen];
}
- (NSString *)screenKey {
// Subclasses must override this method
return #"";
}
- (void)addSecondsToScreen:(NSInteger)seconds {
NSString *key = [self screenKey];
if (key.length > 0) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSNumber *s = [defaults objectForKey:key];
NSInteger addedSeconds = s.integerValue + seconds;
[defaults setObject:[NSNumber numberWithInteger:addedSeconds] forKey:[self screenKey]];
[defaults synchronize];
}
}
+ (NSInteger)secondsInScreen:(NSString *)screenKey {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSNumber *s = [defaults objectForKey:screenKey];
return s.integerValue;
}
#end
Your subclasses must override screenKey retuning a unique identifier for each screen you want to measure. There's a static method in BaseViewController that allows you to get the sum of seconds spent in each screen.
This is a simple way of doing it. From a design point of view, it would be better to store this information in Core Data and to separate the view controller logic from the storage of that data (the static method secondsInScreen should probably be in another class). But it's a good, working start.
I think you would like to get app usage time to enable new features in the app or give some gifts for your users right?
To reach this, don't use any SDK to track audience like Google Analytics and Flurry for example. Both are for a different purpose you want to do.
A very very simple approach is to store locally using NSUserDefaults the session time of some user, or you can store more detailed information about this using CoreData or SQLite.
The iOS provide a lot of options to you do that, for example, each time the user start the session or open some screen, you can store some NSDate or mach_abosulte_time to save the time of user started the session/screen and you can get the offset time when the app goes to background or when the user closes the screen.
And if you want to store this remotely (server), you can create a service to send this time while the app is visible.
I hope this first insight can help you.

Chromecast button not hiding

I am using google casting through my app. It is working good but I am not able to hide the casting button when the cast device goes offline. It hides automatically but after a long time. How can I hide it immediately. Is there a way to get the notifications from the device scanner class.
You should try adding listener
[self.deviceScanner addListener:self];
[self.deviceScanner startScan];
#pragma mark - GCKDeviceScannerListener
- (void)deviceDidComeOnline:(GCKDevice *)device {
NSLog(#"device found!! %#", device.friendlyName);
[self updateCastIconButtonStates];
if ([self.delegate respondsToSelector:#selector(didDiscoverDeviceOnNetwork)]) {
[self.delegate didDiscoverDeviceOnNetwork];
}
}
- (void)deviceDidGoOffline:(GCKDevice *)device {
[self updateCastIconButtonStates];
}
Update
Similar Question.

BOOL method is returning true when used by NSLog when it should be returning false

This is a location app. When I first run the app on my iPhone, an alert window asks me if I would like to allow location services. After selecting OK or Don't allow I can then tap a button that is setup to display my current location. The button is also setup to print an NSLog to the console with the value of a BOOL function called "locationServicesEnabled" and returns yes/1 if they are and no/0 if they are not.
My problem is that when I select the Don't Allow option, and I press the button, the NSLog still prints the value of 1 to the console which is incorrect. It should be 0 or false. A text label that is connected to my button even says "Your location is null", but for some reason the NSLog always shows a BOOL value of 1.
Here is my ViewController.m code:
#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
#interface ViewController () <CLLocationManagerDelegate>
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.gpsLM = [[CLLocationManager alloc]init];
[self.gpsLM startUpdatingLocation];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
}
-(IBAction)gpsButton{
CLLocation * currentLocation = self.gpsLM.location;
self.gpsLabel.text = [NSString stringWithFormat:#"Your Location is %#", currentLocation];
NSLog(#"Location services enabled: %hhd",[CLLocationManager locationServicesEnabled]);
}
#end
You need to check [CLLocationManager authorizationStatus], not locationServicesEnabled. Authorization status returns an app-specific value, one of the following:
typedef enum {
kCLAuthorizationStatusNotDetermined = 0,
kCLAuthorizationStatusRestricted,
kCLAuthorizationStatusDenied,
kCLAuthorizationStatusAuthorized
} CLAuthorizationStatus;
The names are hopefully self-explanatory.
I just figured out the answer to my own question. The bool function will only return a false value if the iPhone's location services option is disabled entirely. If you only have 1 single app disabled, but have the main feature enabled and have other app's using it, then it will still return a value of 1. It looks like the locationServicesEnabled method is device specific and not app specific.

My app will not stay logged into facebook

I have a small app that attempts to do basic login with FB using the exact methodology used in the Facebook tutorial for logging in here: https://developers.facebook.com/docs/howtos/login-with-facebook-using-ios-sdk/
The desired behavior is that the user be able to login to FB and stay logged in until they have explicitly chosen to log out by clicking on the logout button. They should stay logged in across launches of the app, even if the app is forcibly killed (but not deleted).
What is happening is that after clicking on the Login button, the app correctly logs the user in, but once I navigate back to the root view controller, and then back to the FacebookViewController, the button shows Login again, instead of log out. Something somewhere is killing the session.
I am not sure what code will be asked for, but here is a bunch:
#import "FacebookViewController.h"
#interface FacebookViewController ()
#end
#implementation FacebookViewController
#synthesize authButton;
#synthesize postSwitch;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(sessionStateChanged:)
name:FBSessionStateChangedNotification
object:nil];
// Check the session for a cached token to show the proper authenticated
// UI. However, since this is not user intitiated, do not show the login UX.
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
[appDelegate openSessionWithAllowLoginUI:NO];
// Set toggle
if ([[[NSUserDefaults standardUserDefaults] objectForKey:#"postToFB"] isEqualToString:#"Yes"]) {
//set to yes
[postSwitch setOn:YES animated:YES];
} else {
// set to no
[postSwitch setOn:NO animated:YES];
}
NSLog(#"postToFB is now %#",[[NSUserDefaults standardUserDefaults] objectForKey:#"postToFB"]);
}
- (IBAction)authButtonAction:(id)sender
{
AppDelegate *appDelegate =
[[UIApplication sharedApplication] delegate];
// If the user is authenticated, log out when the button is clicked.
// If the user is not authenticated, log in when the button is clicked.
if (FBSession.activeSession.isOpen) {
[appDelegate closeSession];
} else {
// The user has initiated a login, so call the openSession method
// and show the login UX if necessary.
[appDelegate openSessionWithAllowLoginUI:YES];
}
}
- (IBAction)togglePostSwitch:(id)sender
{
if ([[[NSUserDefaults standardUserDefaults] objectForKey:#"postToFB"] isEqualToString:#"Yes"]) {
//set to No
[[NSUserDefaults standardUserDefaults] setObject:#"No" forKey:#"postToFB"];
} else {
// set to yes
[[NSUserDefaults standardUserDefaults] setObject:#"Yes" forKey:#"postToFB"];
}
NSLog(#"postToFB is now %#",[[NSUserDefaults standardUserDefaults] objectForKey:#"postToFB"]);
}
- (void)sessionStateChanged:(NSNotification*)notification
{
if (FBSession.activeSession.isOpen) {
[self.authButton setTitle:#"Logout of Facebook" forState:UIControlStateNormal];
} else {
[self.authButton setTitle:#"Login to Facebook" forState:UIControlStateNormal];
}
}
#end
The MainViewController (the root VC) has the following in the ViewDidLoad, and the ViewWillAppear:
- (void)viewDidLoad
{
[[self navigationController] setNavigationBarHidden:YES animated:NO];
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
BOOL open = [appDelegate openSessionWithAllowLoginUI:NO];
if (open) {
NSLog(#"User session found (MainVC viewDidLoad)");
} else {
NSLog(#"no session detected");
}
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:NO];
[[self navigationController] setNavigationBarHidden:YES animated:NO];
}
I am not real clear on why this behavior is occurring. Any help would be greatly accepted :)
I actually traced this back to an error in labeling the login button. The session was always open even when the button said 'log in'. I managed to correct this issue, but now I am discovering that indeed the app will not stay logged in across launches, but at least the login/logout button is always correct!

Resources