iOS: Grand Central Dispatch and ViewControllers - ios

At this point I'm pretty frustrated but I'm sure it is something I'm missing. In this code my segue to my new viewController is showing up after the rest of the function is executed. How do I get my viewController to be the code being executed? Basically stop the tweetText function from happening until that view is closed I'm trying to give the user an option to select a twitter account if there is more than one. I have tried many different ways. In Apples own example code they suggest to give the user an option but give nothing on how to do it without blowing through the rest of the code.
Here is the code:
[accountStore requestAccessToAccountsWithType:accountType withCompletionHandler:^(BOOL granted, NSError *error) {
if(granted) {
dispatch_sync(dispatch_get_main_queue(), ^{
// Get the list of Twitter accounts.
self.accountsArray = [accountStore accountsWithAccountType:accountType];
if([self.accountsArray count] > 1 /* Check method to see if preference is still a valid account */) {
// Display user accounts if no preference has been set
[self performSegueWithIdentifier:#"TwitterAccounts" sender:self.accountsArray];
[tweet tweetText:tweetString account:self.twitterAccount type:AchievementTweet];
} else {
[tweet tweetText:tweetString account:[self.accountsArray lastObject] type:AchievementTweet];
}
});
} else {
[tweet performSelectorOnMainThread:#selector(displayText:) withObject:[NSNumber numberWithInt:403] waitUntilDone:NO];
}
}];

I am a little confused on what is your purpose but you got 2 options.
There is really no need for GCD here.
I would suggest to register for a notification which will perform your second action once your first action has been accomplished. (Similar to how apple handles the image picker, on their code they inform of when the picture has finally been saved to execute another function).
The other way is using delegates, on this approach you would put the second part of the code on a separate function, then declare that controller as the delegate of the viewcontroller you want executed first, then after whatever you want acomplished is done on the viewcontroller you would ask the delegate to perform the second code and if necessary ask the delegate to close the view as well.

Related

Do any iOS Action extensions really return a value?

To understand this question, return with me now through the WWDC time machine to the distant past, 2014, when Action extensions were introduced and explained in this video:
https://developer.apple.com/videos/play/wwdc2014/217/
About halfway through, in slide 71, about minute 23:30, the presenter gives instructions for returning a value back to the calling app (the app where the user tapped our Action extension's icon in an activity view):
- (IBAction)done:(id)sender {
NSData *data = self.contents;
NSItemProvider *itemProvider =
[[NSItemProvider alloc] initWithItem:data typeIdentifier:MyDocumentUTI];
NSExtensionItem *item = [[NSExtensionItem alloc] init];
item.attachments = #[itemProvider];
}
A moment later, slide 75, about minute 26, we see how the app that put up the activity view controller is supposed to unwrap that envelope to retrieve the result data:
- (void)setupActivityViewController {
UIActivityViewController *controller;
controller.completionWithItemsHandler =
^(NSString *activityType, BOOL completed,
NSArray *returnedItems, NSError *error) {
if (completed && (returnedItems.count > 0)) {
// process the result items
}
}];
}
So my question is: is that for real? Has anyone within the sound of my voice ever done either of those things? Namely:
Does your app have an Action extension that returns a value to the caller?
Does your app put up an activity view controller that receives the result of some arbitrary unknown Action extension and does something with the value?
I ask because (1) I have never seen (on my iPhone) an Action extension that actually returns a value, and (2) the code elided in "process the result items" seems to me to be complete hand-waving, because how would my app even know what kind of data to expect?
I have come to believe that this code is an aspirational pipe dream with no corresponding reality. But I would be delighted to be told I'm wrong.

iOS - Show SVProgressHUD message on screen without interrupting user interaction

I'm making a synchronize function that syncs local Core Data with the server. I want to make the synchronizations happen in the background without disrupting user interaction. When I receive the response (whether success or failure) the app should display a message somewhere on the screen to notify the user about the outcome.
UIAlertController is not a good choice because it will block user action.
Currently I'm using SVProgressHUD:
__weak StampCollectiblesMainViewController *weakSelf = self;
if ([[AppDelegate sharedAppDelegate] hasInternetConnectionWarnIfNoConnection:YES]) {
[_activityIndicator startAnimating];
[Stamp API_getStampsOnCompletion:^(BOOL success, NSError *error) {
if (error) {
[_activityIndicator stopAnimating];
[SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeClear];
[SVProgressHUD setAnimationDuration:0.5];
[SVProgressHUD showErrorWithStatus:#"error syncronize with server"];
}
else {
[_activityIndicator stopAnimating];
[featuredImageView setImageWithURL:[NSURL URLWithString:[Stamp featuredStamp].coverImage] usingActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[yearDropDownList setValues:[Stamp yearsDropDownValues]];
[yearDropDownList selectRow:0 animated:NO];
[weakSelf yearDropDownListSelected];
[SVProgressHUD dismiss];
}
}];
}
Is there a modification I can make so the user can still interact with the app? I just want to show the message without taking up too much space. Any help is much appreciated. Thanks.
Looks like the easiest thing will be to use SVProgressHUDMaskTypeNone.
Also check out this issue.
Sorry but you gonna have to build your own custom view.
In fact it's not that difficult. What I would do is simply add a small view on the top of the screen with your custom message and a close button (to allow user to hide quickly the message). This is usually done by adding this new view to the current window, so that it will be on the top of every view and won't block the UI (except the part hidden by that view :) )

How to hold queue execution until get response from web service API in Objective-C

I am going to build lazy loading functionality which has dynamic size records in database, so i am doing this in following manner
Initially i fetch say 20 records ,
Then i process one,two, or any number of records based on user's clicks (I am not holding user to wait for finish some service call or pending task, so user can do anything with the list like scroll or click etc.)
Now to maintain consistency i am fetching few records first and then process users record, here i am doing so because the records which i processed are removed from the actual list of records, so here the records at Database and app side are not fixed list.
I am doing right now with following ways :
-(void)viewDidLoad {
// Here i fetch first 20 records and then show in UITableView
// After user see the list he can click on any button to process that records
// And i am trying to queue that service call and maintain consistency in records
}
-(void)processOneRecord {
dispatch_queue_t queue = dispatch_queue_create("com.developer.serialqueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
// To get further records service call
[self getMoreRecordsCompletionBlock:^{
[self ProcessOneRecordServiceCall];
}];
});
}
Here i am creating a method processOneRecord which calls every time user clicks on button on cell
In processOneRecord method i create completion block for first service call, so second service call definitely run after first service all response. But now when user click on button on another cell i want to wait that first service call until second service call response return to application.
Thanks for the response guys.
I have achieved desired functionality with help of Rob's answer, I have created one function in which i write one NSBlockOperation which later i added to NSOperaiotnQueue. And every time when this function calls i cheated whether previously added operation is finished, if yes then add another operation else it will wait previous operation until its completion.
In below code snippet below variable are global to my current UIViewController and also they are initialised in my UIViewController's ViewDidLoad and other respective methods.
Bool isRecordProcessed, isLoadMoreCallFinihsed;
NSOperationQueue *operationQueue;
-(void)callTwoAPIServiceCalls {
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
// To get further records first
[self getMoreRecordsCompletionBlock:^{
[self ProcessOneRecordServiceCall];
}];
}];
if (operationQueue.operations.count == 0 && isRecordProcessed && isLoadMoreCallFinihsed) {
[operationQueue addOperation:operation];
}
else {
[self performSelector:#selector(callTwoServiceCalls:) withObject:nil afterDelay:0.1];
}
}

Return a value from a block

I have been reading up on Objectice-C blocks as I have been running into them more and more lately. I have been able to solve most of my asynchronous block execution problems, however I have found one that I cannot seem to fix. I thought about making an __block BOOL for what to return, but I know that the return statement at the end of the method will be executed before the block is finished running. I also know that I cannot return a value inside the block.
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
if ([identifier isEqualToString:#"Reminder Segue"]) {
eventStore = [[EKEventStore alloc] init];
[eventStore requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted, NSError *error) {
if (!granted) {
UIAlertView *remindersNotEnabledAlert;
remindersNotEnabledAlert = [[UIAlertView alloc] initWithTitle:#"Reminders Not Enabled" message:#"In order for the watering reminder feature to function, please allow reminders for the app under the Privacy menu in the Settings app." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
//I would like to put a simple return NO statement here, but I know it is impossible
}
}];
}
return YES;
}
How do I create a simple return statement from a block?
While the immediate idea might be to make your asynchronous request synchronous, that's rarely a good idea, and do to so in the middle of a segue, such as this case, is likely to be problematic. It's almost never a good idea to try to make an asynchronous method synchronous.
And, as smyrgl points out, the idea of "can't I just return a value from the block" is intuitively attractive, but while you can define your own blocks that return values (as Duncan points out), you cannot change the behavior of requestAccessToEntityType such that it returns a value in that manner. It's inherent in its asynchronous pattern that you have to act upon the grant state within the block, not after the block.
So, instead, I would suggest a refactoring of this code. I would suggest that you remove the segue (which is likely being initiated from a control in the "from" scene) and not try to rely upon shouldPerformSegueWithIdentifier to determine whether the segue can be performed as a result of a call to this asynchronous method.
Instead, I would completely remove that existing segue and replace it with an IBAction method that programmatically initiates a segue based upon the result of requestAccessToEntityType. Thus:
Remove the segue from the button (or whatever) to the next scene and remove this shouldPerformSegueWithIdentifier method;
Create a new segue between the view controllers themselves (not from any control in the "from" scene, but rather between the view controllers themselves) and give this segue a storyboard ID (for example, see the screen snapshots here or here);
Connect the control to an IBAction method, in which you perform this requestAccessToEntityType, and if granted, you will then perform this segue, otherwise present the appropriate warning.
Thus, it might look something like:
- (IBAction)didTouchUpInsideButton:(id)sender
{
eventStore = [[EKEventStore alloc] init];
[eventStore requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted, NSError *error) {
// by the way, this completion block is not run on the main queue, so
// given that you want to do UI interaction, make sure to dispatch it
// to the main queue
dispatch_async(dispatch_get_main_queue(), ^{
if (granted) {
[self performSegueWithIdentifier:kSegueToNextScreenIdentifier sender:self];
} else {
UIAlertView *remindersNotEnabledAlert;
remindersNotEnabledAlert = [[UIAlertView alloc] initWithTitle:#"Reminders Not Enabled" message:#"In order for the watering reminder feature to function, please allow reminders for the app under the Privacy menu in the Settings app." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[remindersNotEnabledAlert show];
}
});
}];
}
You CAN return a value from a block, just like from any function or method. However, returning a value from a completion block on an async method does not make sense. That's because the block doesn't get called until after the method finishes running at some later date, and by then, there is no place to return a result. The completion method gets called asynchronously.
In order to make a block return a value you need to define the block as a type that does return a value, just like you have to define a method that returns a value.
Blocks are a bit odd in that the return value is assumed to be void if it's not specified.
An example of a block that returns a value is the block used in the NSArray method indexOfObjectPassingTest. The signature of that block looks like this:
(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate
The block returns a BOOL. It takes an object, an integer, and a pointer to a BOOL as parameters. When you write a block of code using this method, your code gets called repeatedly for each object in the array, and when you find the object that matches whatever test you are doing, you return TRUE.
If you really want to make a block synchronous (although I question the validity of doing so) your best bet is to use a dispatch_semaphore. You can do it like this:
dispatch_semaphore_t mySemaphore = dispatch_semaphore_create(0);
__block BOOL success;
[eventStore requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted, NSError *error) {
success = granted;
dispatch_semaphore_signal(mySemaphore);
}];
dispatch_semaphore_wait(mySemaphore, DISPATCH_TIME_FOREVER);
However again I don't think you want to do this, especially in a segue as it will stall the UI. Your better bet is to rearchitect what you are doing so that you don't have a dependency on the async process being completed in order to continue.

Is it possible to perform an API call in a delegate method that returns a BOOL?

Strange title, I know, but here is the quickest way to explain my issue. The Parse service has a prebuilt Sign Up controller for letting new users sign up for whatever service you may have. There is no way to edit it's implementation, you can only handle events in it's delegate methods. So I can't just place this in the IBAction of the "Sign Up" button. What I want to do is, when a user touches "Sign up", is that I need to make a call to some API to check if something exists or not, and then if it does already exist then don't let the user sign up. Here is the delegate method meant to handle validation when the button is pressed:
// Sent to the delegate to determine whether the sign up request should be submitted to the server.
- (BOOL)signUpViewController:(PFSignUpViewController *)signUpController shouldBeginSignUp:(NSDictionary *)info {
Here is what I'm trying to place in it:
[self processJSONDataWithURLString:[NSString stringWithFormat:#"https://www.someapi.com/api/profile.json?username=%#",username] andBlock:^(NSData *jsonData) {
NSDictionary *attributes = [jsonData objectFromJSONData];
// Check to see if username has data key, if so, that means it already exists
if ([attributes objectForKey:#"Data"]) {
return NO; // Do not continue, username already exists
// I've also tried:
dispatch_sync(dispatch_get_main_queue(), ^{ return NO; } );
}
else
return YES; //Continue with sign up
dispatch_sync(dispatch_get_main_queue(), ^{ return YES; } );
}];
I get errors when I try to return anything, though. When I just do a straight return YES, "^(NSData *jsonData)" is underlined in yellow and I get "Incompatible block pointer types sending BOOL (^)NSData *_strong to parameter of type void(^)NSData *_strong".
Basically, in there any way to make an API call in this method to check something and then return YES or NO depending on the result?
Thanks!
No.
You are invoking an asynchronous method that is using the block as a callback. The processJSON… method is invoked and immediately returns from the call. After it runs in the background the block is invoked. You cannot "return" from within the block. The method was popped off the stack and returned some time previously.
You need to re-architect the logic of this. Invoking a refresh on the main queue is in the right direction.
Try this:
[self processJSONDataWithURLString:[NSString stringWithFormat:#"https://www.someapi.com/api/profile.json?username=%#",username] andBlock:^(NSData *jsonData) {
NSDictionary *attributes = [jsonData objectFromJSONData];
BOOL status=YES;
// Check to see if username has data key, if so, that means it already exists
if ([attributes objectForKey:#"Data"]) {
status=NO; // Do not continue, username already exists
[self performSelectorOnMainThread:#selector(callDelegate:) withObject:[NSNumber numberWithBool:status] waitUntilDone:YES];
}];
-(void)callDelegate:(NSNumber*) status
{
BOOL returnStatus = [status boolValue];
//now retutn returnStatus to your delegate.
}
But this is not the right way to do it, you gotta change the logic you have written to support asynchronous communication. You could consider mine, only if you want to do it your way.

Resources