Adding event to calendar very slow - ios

I am simply wanting to add an event to the device's calendar.
I'm using:
__weak ProgramViewController *weakSelf = self;
EKEventStore *store = [[EKEventStore alloc] init];
[store requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error)
{
if (error)
NSLog(#"EKEventStore error = %#", error);
if (granted)
{
NSLog(#"EKEvent *event ");
EKEvent *event = [EKEvent eventWithEventStore:store];
event.title = weakSelf.program.title;
event.location = weakSelf.program.locationPublic;
event.startDate = weakSelf.program.startTime;
event.endDate = weakSelf.program.endTime;
[event setCalendar:[store defaultCalendarForNewEvents]];
NSError *err = nil;
[store saveEvent:event span:EKSpanThisEvent commit:YES error:&err];
if (err)
{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Calendar Error" message:err.localizedDescription delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alertView show];
}
else
{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Added" message:#"Calendar event added." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alertView show];
}
}
}];
and in iOS 6 it can take 6/7 seconds (iPhone 4) and on iOS 7 (on an iPhone 5S) it takes ~10 seconds. Is this normal behaviour? If not what am I doing wrong?

I had the same issue. Thanks to Jasper's answer, I got thinking about queues. Try this:
if (!err)
{
dispatch_async(dispatch_get_main_queue(),
^{
[[[UIAlertView alloc] initWithTitle:NSLocalizedString(#"event added", nil) message:nil delegate:nil cancelButtonTitle:NSLocalizedString(#"ok", nil) otherButtonTitles:nil] show];
});
}
Here's why this is needed (see emphasis)
Discussion
In iOS 6 and later, requesting access to an event store
asynchronously prompts your users for permission to use their data.
The user is only prompted the first time your app requests access to
an entity type; any subsequent instantiations of EKEventStore uses
existing permissions. When the user taps to grant or deny access, the
completion handler will be called on an arbitrary queue. Your app is
not blocked while the user decides to grant or deny permission.
Since UIAlertView is UIKit, and UIKit always requires the main thread, any other arbitrary thread will crash or lead to unpredictable behaviour.
https://developer.apple.com/library/ios/documentation/EventKit/Reference/EKEventStoreClassRef/Reference/Reference.html

According to the docs: "An EKEventStore object requires a relatively large amount of time to initialize and release." . . so you should dispatch this on a background queue.
Also, oddly, it takes longer on the main queue than the background queue - not sure why this is!

Related

System hangs when showing a UIAllertView inside a SLRequestHandler

I am using SLRequest to send user's video to twitter. After finishing the post request, I want to inform the user whether the upload is successful. But if I show a UIAlertView inside the SLRequestHandler, the system simply hangs and doesn't show the alert view at all. Is it a no-go to have a UIAlertView inside the SLRequestHandler? What is the better way to show a custom message based on the result of the post request?
Here is my sample code:
SLRequest *postRequest2 = [SLRequest
requestForServiceType:SLServiceTypeTwitter
requestMethod:SLRequestMethodPOST
URL:requestURL2 parameters:message2];
postRequest2.account = twitterAccount;
[postRequest2
performRequestWithHandler:^(NSData *responseData,
NSHTTPURLResponse *urlResponse, NSError *error)
{
if (error != nil) {
NSLog(#"error: %#", error);
}
else {
UIAlertView *theAlert = [[UIAlertView alloc] initWithTitle:#"Success!"
message:#"Your video is now available in your Twitter account"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[theAlert show];
}
}];
All UI related operations must be on the main thread.
Would you try to dispatch on the main thread your alert view?
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *theAlert = [[UIAlertView alloc] initWithTitle:#"Success!" message:#"Your video is now available in your Twitter account" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[theAlert show];
});
Please note that UIAlertView is deprecated since iOS 8, and the use of UIAlertViewController is recommended.
You are trying to show the alert message in a block.
Alerts are UI Thread (main thread) controls. So, modify else part and show your alert in dispatch_async, it will work.
dispatch_async(dispatch_get_main_queue(), ^{
[theAlert show];
});

Suddenly iOS App is not responsive at all

After I call a certain Google's Youtube library, my application suddenly becomes not responsive at all after one of its callback.
Not responsive means all UI components cannot be clicked.
Is there such thing in iOS that can disable entire screen to be not responsive at all?
The code:
self.uploadFileTicket = [service executeQuery:query
completionHandler:^(GTLServiceTicket *ticket,
GTLYouTubeVideo *uploadedVideo,
NSError *error) {
// Callback
_uploadFileTicket = nil;
if (error == nil) {
[_delegate videoUploadedSuccessfully:YES error:nil];
} else {
[_delegate videoUploadedSuccessfully:NO error:error.description];
}
}];
Inside my ViewController:
- (void)videoUploadedSuccessfully:(BOOL)success error:(NSString *)errorMessage{
dispatch_async(dispatch_get_main_queue(), ^{
if(success){
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Youtube"
message:#"Video uploaded successfully"
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:#"OK", nil];
[alert show];
}
else{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Youtube"
message:errorMessage
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:#"OK", nil];
[alert show];
}
});
}
Update
I have tried using Instrument and got following:
Does this mean my Working Thread are blocking Main Thread?
Finally I found the ROOT cause of this issue. There is somewhere in the code before uploading the video to Youtube:
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
I cannot pin-point the issue. But, here are some of my suggestions.
The method call [service executeQuery:query completionHandler: has a completion handler. Therefore, it's mostly an async task. Therefore the task of the service should be done in a background thread and should not block the UI.
In case you're not sure whether the call is in the main thread, use the following LOC to clarify.
[NSThread isMainThread] will return true only if the executing thread is the main thread/ UI-thread.
Can you also put a NSLog at videoUploadedSuccessfully and check whether the delegate method gets called multiple times?
You do not need the block
dispatch_async(dispatch_get_main_queue(), ^{}
The delegate method should get executed on the main thread itself as long as you're calling the service-query method on the main thread.
Finally check whether you're calling the [service executeQuery:query method from the main thread?

unable to add event using EventKit in iOS app

I am trying to add event in iOS app using Event kit with following code:
EKEventStore *store = [[EKEventStore alloc] init];
[store requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
if (!granted) { return; }
dispatch_async(dispatch_get_main_queue(), ^{
EKEvent *event = [EKEvent eventWithEventStore:store];
event.title = eventName;
event.startDate = [NSDate dateFromString:[NSString stringWithFormat:#"%# %#",[arrEvent objectAtIndex:1],[arrEvent objectAtIndex:2]] withFormat:#"MM/dd/yyyy HH:mm"];
event.endDate = [NSDate dateFromString:[NSString stringWithFormat:#"%# %#",[arrEvent objectAtIndex:1],[arrEvent objectAtIndex:3]] withFormat:#"MM/dd/yyyy HH:mm"];
[event setCalendar:[store defaultCalendarForNewEvents]];
NSError *err = nil;
[store saveEvent:event span:EKSpanThisEvent commit:YES error:&err];
NSString *savedEventId = event.eventIdentifier; //this is so you can access this event later
NSLog(#"%#",savedEventId);
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Event added to calendar" message:nil delegate:self cancelButtonTitle:#"OK" otherButtonTitles:#"View Calendar", nil];
alert.tag = 101;
[alert show];
});
}];
But I am getting granted as false so it doesn't add the event. It is not working in my simulator and device both, even after I reset my simulator. Same code works in another app of mine. Can someone please suggest me what I should do?
Edit: Error is always nil
When app launched for first time, a message asking for permissions will be shown to the user, and app will be able to access the calendar if only the user granted permission. In your case you have declined permission for calendar.
For fixing this issue, Go to Settings -> -> Allow to access calendar = YES.
Seems like you're requesting to get the access every time. You should better check the authorisation status first and if it's not determined, only then request for the authorisation. First check this using "authorizationStatusForEntityType" and create a new method to save the event, not inside "requestAccessToEntityType".

CKAccountStatus returning different values when not called from ViewController

I'm using Cloud Kit in my app which makes use of the camera and only allows the user to submit photos if they're logged into iCloud. So when the user clicks the camera button, I call a CloudKit method to get the user's icloud status, which returns a CKAccountStatus value (0-3). I implemented this initially in the view controller and it worked perfectly. Then I did some refactoring and created a CKManager class to house all CK related methods. So now when the camera is clicked instead of calling the CK method off the container directly in the VC, I'm calling it via a method from my CKManager property (which is lazy instantiated). It should only return values 0-3, but it keeps returning 448 for some reason. However, in the CKManager logging, I can see it logging correctly that I'm logged into iCloud. So there's an issue of it translating from there back to the VC. I have feeling this is a threading/callback issue, which I'm not that well versed in.
Can someone take a look at the code and see if there's something obvious I'm doing wrong? Thanks in advance!
- (IBAction)cameraBarButtonPressed:(UIBarButtonItem *)sender {
NSLog(#"Entered cameraBarButtonPressed");
//CKContainer *container = [CKContainer defaultContainer];
dispatch_queue_t fetchQ = dispatch_queue_create("check user status", NULL);
__block CKAccountStatus userAccountStatus;
dispatch_async(fetchQ, ^{ // check user's CK status on different thread
userAccountStatus = [self.ckManager getUsersCKStatus];
NSLog(#"cameraBarButtonPressed userAccountStatus: %ld", userAccountStatus);
if (userAccountStatus == CKAccountStatusAvailable) {
//NSLog(#"User is logged into CK - user can upload pics!");
UIImagePickerController *cameraUI = [[UIImagePickerController alloc] init];
cameraUI.delegate = self; // set the deleage for the ImagePickerController
// check to see if the camera is available as source type, else check for photo album
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
cameraUI.sourceType = UIImagePickerControllerSourceTypeCamera;
} else if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeSavedPhotosAlbum]) {
cameraUI.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
}
[cameraUI setAllowsEditing:YES]; // let the user edit the photo
// set the camera presentation style
//cameraUI.modalPresentationStyle = UIModalPresentationFullScreen;
cameraUI.modalPresentationStyle = UIModalPresentationCurrentContext;
dispatch_async(dispatch_get_main_queue(), ^{ // show the camera on main thread to avoid latency
[self presentViewController:cameraUI animated:YES completion:nil]; // show the camera with animation
});
} else if (userAccountStatus == CKAccountStatusNoAccount) {
//NSLog(#"User is not logged into CK - Camera not available!");
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"iCloud Not Available" message:#"You must be logged into your iCloud account to submit photos and recipes. Go into iCloud under Settings on your device to login." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
dispatch_async(dispatch_get_main_queue(), ^{
[alert show];
});
} else if (userAccountStatus == CKAccountStatusRestricted) {
NSLog(#"User CK account is RESTRICTED !");
} else if (userAccountStatus == CKAccountStatusCouldNotDetermine) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"iCloud Status Undetermined" message:#"We could not determine your iCloud status. You must be logged into your iCloud account to submit photos and recipes. Go into iCloud under Settings on your device to login." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
dispatch_async(dispatch_get_main_queue(), ^{
[alert show];
});
} else { // did not get back one of the above values so show the Could Not Determine message
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"iCloud Status Undetermined" message:#"We could not determine your iCloud status. You must be logged into your iCloud account to submit photos and recipes. Go into iCloud under Settings on your device to login." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
dispatch_async(dispatch_get_main_queue(), ^{
[alert show];
});
}
});
}
The above code is the code that does not work. Here is the code that does work. Just copying the beginning code as the rest is redundant from that point on...
CKContainer *container = [CKContainer defaultContainer];
dispatch_async(fetchQ, ^{ // check user's CK status on different thread
[container accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError *error) {
if (error) {...
Lastly, here is the code that gets called from CKManager for the code that does not work...
- (CKAccountStatus)getUsersCKStatus {
NSLog(#"Entered getUsersCKStatus...");
__block CKAccountStatus userAccountStatus;
[self.container accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError *error) {
if (error) {
NSLog(#"Error: Error encountered while getting user CloudKit status: %#", error.localizedDescription);
} else {
if (accountStatus == CKAccountStatusAvailable) {
NSLog(#"Info: User is logged into CK - camera is available!");
userAccountStatus = CKAccountStatusAvailable;
} else if (accountStatus == CKAccountStatusNoAccount) {
NSLog(#"Info: User is not logged into CK - Camera not available!");
userAccountStatus = CKAccountStatusNoAccount;
} else if (accountStatus == CKAccountStatusRestricted) {
NSLog(#"Info: User CK account is RESTRICTED - what does that mean!?");
userAccountStatus = CKAccountStatusRestricted;
} else if (accountStatus == CKAccountStatusCouldNotDetermine) {
NSLog(#"Error: Could not determine user CK Account Status: %#", error.localizedDescription);
userAccountStatus = CKAccountStatusCouldNotDetermine;
}
}
}];
NSLog(#"CKAccountStatus: %ld", userAccountStatus);
return userAccountStatus;
}
In the getUsersCKStatus you are calling the accountStatusWithCompletionHandler. That is an asynchronous method. In your case it will return the userAccountStatus before it is set by its callback method.
You could solve this by making that method synchronous by implementing a semaphore. A better way would be passing on a callback block to that method and not returning a value.

saveEvent returning "No calendar has been set"

I am attempting to save an event into the calendar, from my application.
My code works for iOS 7, but on iOS 6, it returns No calendar has been set.
The application prompts for user to grant access to the calendar, on iOS 7.
But no such prompt appears for iOS 6. Although the application is granted access in the Settings-> Privacy -> Calendar.
And yes, I have already implemented the requestAccessToEntityType:completion:.
Here is my code snippet.
EKEventStore *objStore = [[EKEventStore alloc]init];
if ([objStore respondsToSelector:#selector(requestAccessToEntityType:completion:)])
{
// iOS 6 and later
[objStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error)
{
dispatch_async(dispatch_get_main_queue(), ^{
if (granted)
{
// code here for when the user allows your app to access the calendar
EKEvent *calEvent = [EKEvent eventWithEventStore:objStore];
calEvent.title = mstrTitleEvent;
calEvent.startDate = self.dateToBeSet;
calEvent.endDate = self.dateToBeSet;
calEvent.calendar = objStore.defaultCalendarForNewEvents;
EKAlarm *objAlarm = [EKAlarm alarmWithAbsoluteDate:self.dateToBeSet];
[calEvent addAlarm:objAlarm];
NSError *error;
BOOL _bStatus = [objStore saveEvent:calEvent span:EKSpanThisEvent commit:YES error:&error];
UIAlertView *alertV;
if(_bStatus)
{
alertV = [[UIAlertView alloc]initWithTitle:#"Congratulations" message:#"Saved To Calendar" delegate:nil cancelButtonTitle:#"Right On!" otherButtonTitles:nil];
[alertV show];
}
else
{
alertV = [[UIAlertView alloc]initWithTitle:#"Alert" message:[NSString stringWithFormat:#"Error saving to calendar, with error %#.",[error localizedDescription]] delegate:nil cancelButtonTitle:#"Cancel" otherButtonTitles:nil];
[alertV show];
}
}
else
{
// code here for when the user does NOT allow your app to access the calendar
UIAlertView *alertV = [[UIAlertView alloc]initWithTitle:#"Alert" message:#"Please grant access to the calendar, and try again later." delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[alertV show];
}
});
}];
}
Just managed to somehow find a solution for my problem.
I had to navigate from one page to another, so posting the link to the two pages.
First ->
https://discussions.apple.com/message/16497282#16497282
Then, from there to ->
https://discussions.apple.com/message/16479587#16479587
I had to go into Settings>iCloud> and turn on Calendars.
After that, I tried to attempt and run my code, and it was working well and fine again.
Do attempt this, if you facing a similar problem.
I was working on iPad 2, and with iOS 6.1.3 installed on the device.
Amazing I am testing my code on two different devices, one it works fine and the other it will not work. Just looked in setting and the one with it not working has iCloud calendar and reminders turned off, just turn them on and it all works now... this has to be a bug

Resources