location manager working on Always but not When In Use - objective-c

I have an app that worked great before Apple insisted users be given a choice between "Always" and "When In Use" for Location Manager.
The app used iBeacons to send invitations to play games and accept.
When "Always" is selected the beacons work fine but when I switch to "When In Use" they don't work at all.
I started out using "Always" but change the following code to give users the choice.
In the app's plist I added "Privacy-Location Always and When In Use Usage Descriptions and Privacy-Location When In Use Usage Description" and removed the "Privacy-Location Always Usage Description".
In the app's Delegate I have this
- (void)locationManager:(CLLocationManager *)manager
didChangeAuthorizationStatus:(CLAuthorizationStatus)status{
if([CLLocationManager authorizationStatus]==kCLAuthorizationStatusAuthorizedAlways){
NSLog(#"Always");
AlertView2 = [[UIAlertView alloc] initWithTitle:#"Dual player on two devices is enabled."
message:#"To save battery power go to Settings/Privacy/Location Services and choose \"Never\" when not using I'M GAME. Two people can still play on one device when in \"Never\" mode. To recieve invitations to play only when the app is open select \"When In Use\"."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[AlertView2 show];
[[NSUserDefaults standardUserDefaults] setObject:#"YES" forKey:#"accessKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
if([CLLocationManager authorizationStatus]==kCLAuthorizationStatusAuthorizedWhenInUse){
NSLog(#"WhenInUse");
AlertView2 = [[UIAlertView alloc] initWithTitle:#"Dual player on two devices is enabled."
message:#"To save battery power go to Settings/Privacy/Location Services and choose \"Never\" when not using I'M GAME. Two people can still play on one device when in \"Never\" mode. To recieve invitations to play while app is in background select \"Always\"."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[AlertView2 show];
[[NSUserDefaults standardUserDefaults] setObject:#"YES" forKey:#"accessKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
if([CLLocationManager authorizationStatus]==kCLAuthorizationStatusRestricted){
NSLog(#"restricted");
}
if([CLLocationManager authorizationStatus]==kCLAuthorizationStatusDenied){
NSLog(#"denied");
AlertView2 = [[UIAlertView alloc] initWithTitle:#"Dual player on a single device Only."
message:#"To play on two devices go to Settings Privacy/Location Services and choose \"Always\" or \"When In Use\" for I'M GAME. In \"Always\" you can recieve invites while app is in background, in \"When In Use\" invites only appear when the app is on screen. To preserve battery choose \"Never\" when not using I'M GAME."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[AlertView2 show];
[[NSUserDefaults standardUserDefaults] setObject:#"YES" forKey:#"accessKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
if([CLLocationManager authorizationStatus]==kCLAuthorizationStatusNotDetermined){
NSLog(#"undetermined2");
[locationManager requestAlwaysAuthorization];
[locationManager requestWhenInUseAuthorization];
}
}
Does iBeacon need to have Privacy-Location set to "Always" to work?
So I just found out that in "When In Use" you can't monitor for beacon region is entered or exited only find its range. So I guess the question is how would I use range to send a notification to my user.

When you have your app authorized to do beacon ranging only when it is in the foreground, it's easy to simulate entry/exit logic with just a didRangeBeacons callback.
Set up one class variable:
var beaconLastSeen: Date? = nil
Add this code to your didRangeBeacons method:
if beacons.count > 0 {
if beaconLastSeen == nil {
// call didEnterRegion logic here
}
beaconLastSeen = Date()
}
else {
if beconLastSeen != nil && Date() - beaconLastSeen > 30 {
beaconLastSeen = nil
// call didExitRegion logic here
}
}
You will get an exit event 30 secs after the last beacon detection. You will get an enter event when one is first seen.
EDIT: Here's the same code in Objective C:
NSDate *beaconLastSeen = nil;
...
if (beacons.count > 0) {
if (beaconLastSeen == nil) {
// call didEnterRegion logic here
}
beaconLastSeen = [[NSDate alloc] init];
}
else {
if (beaconLastSeen != nil && [[[NSDate alloc] init] timeIntervalSinceDate: beaconLastSeen] > 30 ) {
beaconLastSeen = nil;
// call didExitRegion logic here
}
}

Related

Can't retrieve EKCalendars from EKEventStore in iOS7

I've recently found out, that I do not receive any EKCalendar objects from EKEventStore in iOS7. In iOS 6.x.x there are no problems with same code snippet. When I'm trying to access defaultCalendarForNewEvents - I do receive a single EKCalendar object (as expected).
I do request access to entity type EKEntityTypeEvent.
The snippet:
__block NSMutableArray *calendarsArray = nil;
if ([self.eventsStore respondsToSelector:#selector(requestAccessToEntityType:completion:)]) {
[self.eventsStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
if (granted) {
EKAuthorizationStatus status = [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent];
if (status == EKAuthorizationStatusAuthorized) {
calendarsArray = [[NSMutableArray alloc] initWithArray:[self.eventsStore calendarsForEntityType:EKEntityMaskEvent]];
}
} else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error!" message:#"You haven't granted access to calendars. Expected things are not going to happen." delegate:nil cancelButtonTitle:#"I understand" otherButtonTitles:nil];
[alert show];
}
}];
} else {
calendarsArray = [NSMutableArray arrayWithArray:[self.eventsStore calendarsForEntityType:EKEntityTypeEvent]];
}
I do receive 0 objects into calendarsArray. I've also tried to get it by "running through" all EKSources that are of type Local or CalDAV ([source calendarsForEntityType:] - got the same empty (0 object containing) set).
By the way - access to calendars IS granted.
Any suggestions?
After a brief investigation I have found out, that the problem was not in the code. It appears that the problem is in iOS 7.0.3 itself.
After deleting all the sync'ed calendars from the iDevice and adding it back all of the calendars were displayed both within the native Calendar application, and the one I made. After taking this action my code was able to retrieve the calendars from EventStore not depending on the method I would access calendars (via EKSources or EKEventStore itself).

How to get the name/id of default calendar in iOS 6

I have two (2) calendars (iCal) on my iPad (one personal, one for the app). They are sync'd to my iMac for testing only. (Saves me time making entries to the specific app calendar).
I am currently writing an app that needs to access the app's calendar. It is the primary calendar on the iPad. I am trying to get Apple's SimpleEKDemo (unmodified) to work with the app's calendar, but so far I can't even get it not crash, much less to return anything. I have been looking at Google and SO questions for hours now, and decided it's time to call in the big guns.
This is the code where it's crashing:
- (void)viewDidLoad
{
self.title = #"Events List";
// Initialize an event store object with the init method. Initilize the array for events.
self.eventStore = [[EKEventStore alloc] init];
self.eventsList = [[NSMutableArray alloc] initWithArray:0];
// Get the default calendar from store.
self.defaultCalendar = [self.eventStore defaultCalendarForNewEvents]; // <---- crashes here --------
// Create an Add button
UIBarButtonItem *addButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:
UIBarButtonSystemItemAdd target:self action:#selector(addEvent:)];
self.navigationItem.rightBarButtonItem = addButtonItem;
[addButtonItem release];
self.navigationController.delegate = self;
// Fetch today's event on selected calendar and put them into the eventsList array
[self.eventsList addObjectsFromArray:[self fetchEventsForToday]];
[self.tableView reloadData];
}
This is the output from the "crash":
2012-10-05 14:33:12.555 SimpleEKDemo[874:907] defaultCalendarForNewEvents failed: Error Domain=EKCADErrorDomain Code=1013 "The operation couldn’t be completed. (EKCADErrorDomain error 1013.)"
I need to make sure I'm on the correct calendar... how do I do that?
You need to ensure you ask permission before trying to access the Event Store. Note that you need to only call this once. If the user denies access, they need to go to iOS Settings (see comment in code) to enable permissions for your app.
/* iOS 6 requires the user grant your application access to the Event Stores */
if ([eventStore respondsToSelector:#selector(requestAccessToEntityType:completion:)])
{
/* iOS Settings > Privacy > Calendars > MY APP > ENABLE | DISABLE */
[eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error)
{
if ( granted )
{
NSLog(#"User has granted permission!");
}
else
{
NSLog(#"User has not granted permission!");
}
}];
}
In iOS 5, you are only allowed to access Events (EKEntityTypeEvent) in the Event Store, unlike in iOS 6, where you can access Reminders (EKEntityTypeReminder). But you need the above code to at least get granted 1 time.
I should also mention that you need to be granted permission BEFORE you access the EventStore, in your case: [self.eventStore defaultCalendarForNewEvents];.
Also, defaultCalendarForNewEvents would be the correct way to access the users Default Calendar. If you wish to access a calendar with another name, then you need to iterate through the calendars and choose the appropriate one based on the results returned.
//Check if iOS6 or later is installed on user's device *********
if([eventStore respondsToSelector:#selector(requestAccessToEntityType:completion:)]) {
//Request the access to the Calendar
[eventStore requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted,NSError* error){
//Access not granted-------------
if(!granted){
NSString *message = #"Hey! I Can't access your Calendar... check your privacy settings to let me in!";
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:#"Warning"
message:message
delegate:self
cancelButtonTitle:#"Ok"
otherButtonTitles:nil,nil];
//Show an alert message!
//UIKit needs every change to be done in the main queue
dispatch_async(dispatch_get_main_queue(), ^{[alertView show];});
//Access granted------------------
}
else
{
self.defaultCalendar=[self.eventStore defaultCalendarForNewEvents];
}
}];
}
//Device prior to iOS 6.0 *********************************************
else{
self.defaultCalendar=[self.eventStore defaultCalendarForNewEvents];
}

Sharekit not showing Twitter, Email, TextMessage Options

I'm using ShareKit 2 on an iOS app for version 5 or later. I've got the app configured to use Facebook correctly, and when the action sheet is activated, that's the only option that appears. I want to use the built-in Twitter sharing system, but the option doesn't come up in the action sheet. Here's my code:
SURL *url = [NSURL URLWithString:launchUrl];
SHKItem *item = [SHKItem URL:url title:#"Title" contentType:SHKShareTypeURL];
SHKActionSheet *actionSheet = [SHKActionSheet actionSheetForItem:item];
[SHK setRootViewController:self.view.window.rootViewController];
[actionSheet showInView:self.view.window.rootViewController.view];
As per the instructions. I've got a custom DefaultSHKConfigurator class, in which I suppose these are the relevant methods:
- (NSString*)sharersPlistName {
return #"MySHKSharers.plist";
}
- (NSArray*)defaultFavoriteURLSharers {
return [NSArray arrayWithObjects:#"SHKTwitter",#"SHKFacebook", #"SHKMail", #"SHKTextMessage", nil];
}
Again, only Facebook comes up as an option. For completeness, here's my "MySHKSharers.plist" file (the relevant part):
<dict>
<key>actions</key>
<array>
<string>SHKMail</string>
<string>SHKTextMessage</string>
</array>
<key>services</key>
<array>
<string>SHKTwitter</string>
<string>SHKFacebook</string>
</array>
</dict>
So any ideas why I can't get Twitter, Mail and TextMessage to show up in the action sheet?
If you want to configure the sharers shown in the actionsheet I found this post helpful:https://gist.github.com/1181552
I ran into trouble getting actions (email, text message, etc) to show in the default actionsheet. My solution was to create a custom actionsheet and then to call Sharekit like this:
- (IBAction) getSharingSheet:(id)sender{
UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:#"Share"
delegate:self
cancelButtonTitle:#"Cancel"
destructiveButtonTitle:nil
otherButtonTitles:#"Facebook", #"Twitter", #"Save to Photo Library", #"Email", nil];
[sheet showInView:self.view];
}
and then my didDismissWithButtonIndex:
- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
{
switch(buttonIndex)
{
case 0:
SHKItem *item = //some item
[SHKFacebook shareItem:item];
break;
case 1:
SHKItem *item = //some item
[SHKTwitter shareItem:item];
break;
case 2:
SHKItem *item = //some item
[SHKPhotoAlbum shareItem:item];
break;
case 3:
SHKItem *item = //some item
[SHKMail shareItem:item];
break;
default:
break;
}
}
Hope this helps!
I had this same issue and just solved it. This may or may not be the same issue that you are facing.
After poking around a bit to see how the action sheet was being built, I found that in SHK.m it was pulling stored favourites out of NSUserDefaults.
+ (NSArray *)favoriteSharersForType:(SHKShareType)type
{
NSArray *favoriteSharers = [[NSUserDefaults standardUserDefaults] objectForKey:[NSString stringWithFormat:#"%#%i", SHKCONFIG(favsPrefixKey), type]];
....
}
These stored results are preferred even over what you've defined as the "default" favourite sharing services. Clearing out these favourites solves the issue.

Filter the friend returned by FBFriendPickerViewController in new Facebook 3.0

I have an Facebook app on http://developer.facebook.com of type "Native iOS" which is connected to the actual iOS app using the new Facebook iOS SDK 3.0.
How can I know if the multiple people that already install the the app and authorize the Facebook app too so I can put them in some list, but this not my question because I know that there is a field called "installed" that return true when the user install the app.
So if I want to filter the returned friend by the FBFriendPickerViewController based on if the user use the app, so how I can use the installed field or something else to filter the friends.
Note: I know where to filter the friend (*) all I need is the field or the property that I need to check to make sure the user is install my app.
*- (BOOL)friendPickerViewController:(FBFriendPickerViewController *)friendPicker
shouldIncludeUser:(id<FBGraphUser>)user
I found a way to do this :
I create a method that send an FQL to those I need (for example my friends that they are male)
and then compare those returned by this request with the already exists by the FBFriendPickerViewController using the method :
- (BOOL)friendPickerViewController:(FBFriendPickerViewController *)friendPicker shouldIncludeUser:(id<FBGraphUser>)user
for the request :
NSDictionary *queryParam = [NSDictionary dictionaryWithObjectsAndKeys:#"query", #"q", nil];
[FBRequestConnection startWithGraphPath:#"/fql" parameters:queryParam HTTPMethod:#"GET"
completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (error)
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Oops" message:#"Some thing go wrong !, Try again" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alert show];
}
else
{
NSArray *friendInfo = (NSArray *) [result objectForKey:#"data"];
for(int i = 0; i < [friendInfo count]; i++)
{
[self.friendsNames addObject:[[friendInfo objectAtIndex:i] objectForKey:#"name"]];
}
}
}];
As you see I have an NSArray that store the filtered friend, the next step is to compare them in the delegate method above.
Hope you find it useful.

Change the page after an action is done

I have 2 pages with xib files. One is a login page and the other is a welcome page.
Here is the login function:
-(IBAction)login{
NSLog(#"login begin");
NSString *loginResponse = [self tryLogin];
NSLog(#"%#", loginResponse);
loginError.text = loginResponse;
NSUserDefaults *session = [NSUserDefaults standardUserDefaults];
[session setInteger:1 forKey:#"islogged"];
}
After the user is logged in successfully I need to redirect them to another xib file which is called Notifications. Now how exactly should I do this?
Also for some reason when I put an if statement inside the function, it doesnt go into the statements.
-(IBAction)login{
NSLog(#"login begin");
NSString *loginResponse = [self tryLogin];
NSLog(#"%#", loginResponse);
if(responseInt == #"2"){
NSLog(#"login error 2");
UIAlertView *alertURL = [[UIAlertView alloc] initWithTitle:#"Error!"
message:#"Username or password is wrong!"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles: nil];
[alertURL show];
[alertURL release];
///////////////////////////////////////////////////
}else if(responseInt == #"0"){
NSUserDefaults *session = [NSUserDefaults standardUserDefaults];
[session setInteger:1 forKey:#"islogged"];
}
}
As you can see I try to nslog something as soon as one of the conditions are met but it does not go into any of the statements (the loginResponse is sent back as 2).
How to change views
If you app is navigation-based then you can add new fullscreen views using method [self.navigationController pushViewController:controller animated:YES];.
Everywhere you can add new fullscreen view using next method : [self presentModalViewController:controller animated:YES];.
Issue with 'if-statement'.
When you want to compare to objects you should use one of following methods:
compare:, this method return one of following values: NSOrderedSame, NSOrderedDescending, NSOrderedAscending. For example, if ([responseInt compare:#"2"] == NSOrderedSame) {// equal}
'isEqual:', for example, if ([responseInt isEqual:#"2"]) {// equal}
or some specific comparators for concrete class
Don't forget that when you are comparing using == or > and etc you are comparing addresses of objects in memory.
Have you tried doing isEqualToString: instead of the equal sign? That is a more reliable string comparator. Also it seems strange that your login method is returning a string and not an integer. Is there a reason for that?