I have got two UIViewController:
MainVC
PickerVC
In main view controller I have IBAction method:
- showPickerView
In this method I need to create block that will present PickerVC view and wait me while I choose something value on PickerVC view and press Done button.
In this block I need to implement callback that will invoke method in MainVC after I press on button Done.
So, I have used block before, but I don't know how to implement it by myself.
I think first part will be look like this:
- (IBAction)showPickerView {
__block PickerVC *pickerVC = [[PickerVC alloc] init];
[pickerVC setFinishBlock:^{
// Do something after user press on Done button
}];
[pickerVC setFailedBlock:^{
// Do something if something wrong (but this method optional)
}];
[pickerVC showPicker];
}
Add in the header of PickerVC two typedefs
typedef void (^FinishBlock)();
typedef void (^FailedBlock)();
and your declaration of setFinishedBlock takes the FinishBlock
- (void)setFinishBlock:(FinishBlock)finishBlock;
- (void)setFailedBlock:(FailedBlock)failedBlock;
Make an iVar for each block
#interface PickerVC : UIViewController
{
FinishBlock _finishBlock;
FailedBlock _failedBlock;
}
In your definition of setFinishedBlock: and setFailedBlock: set the parameter to the iVars
As soon as PickerVC fails or finishes call _failedBlock or _finishedBlock.
The __block statement is used for variables to stay in memory if they are used in a block. So you don't need it in the above code
Related
I have a button in a cell which calls a protocol that has data that needs to be passed to the view controller by the segue. The segue is happening through storyboard. My current code uses the shouldperformsegue to return no when the button is pressed as the first segue that happens does not have the data.
Im guessing the segue and the protocol are being handled asynchronously.
But before I return NO I tell it to perform the segue at a delay. This delayed segue does have the data and works fine.
My question is there a way to wait for the protocol to finish and then perform the segue? Maybe with an execution block?
The other responders have hinted about this, but haven't stated it explicitly, so here goes.
Do not tie a segue directly to the button. Instead, control-drag from the source view controller SCENE to the destination scene to create a segue at the view controller level. Give the segue a unique identifier.
Then, in your button IBAction code, do the async network download. You may want to display a "loading, please wait" message or something while the download is taking place. Most async network calls take a completion block. In that completion block, wrap a call to performSegueWithIdentifier in a call to dispatch_async(dispatch_get_main_queue() so the segue gets invoked on the main thread. (#SantaClaus's answer shows the syntax for that.)
So your button IBAction code might look like this:
- (IBAction) buttonAction: (UIButton *) sender;
{
//Display a "please wait"message to the user if desired
doAsyncCallTakingBlock( completion: ^(NSData *data)
{
//parse the data, (or whatever)
dispatch_async(dispatch_get_main_queue(), ^
{
//This call uses the button as the sender. That might be appropriate,
//or not.
[self performSegueWithIdentifier:#"jumpToOtherViewController"
sender: sender];
});
}
}
With this approach the segue doesn't get called until the async method (Which I called doAsyncCallTakingBlock in my example code) has finished it's work. You might call an Alamofire request method, use an NSURLSession, or any other async method that takes a completion block.
Yes, I would use blocks. For example:
[Api getDataWithCompletion:^(BOOL success, NSString *data) {
if (success) {
self.data = data;
[self performSegueWithIdentifier:#"MySegue" sender:self];
} else {
NSLog(#"Failed to get data");
}
}];
Then, to pass it to the next view controller:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"MySegue"]) {
TargetViewController *targetVC = (TargetViewController*)segue.destinationViewController;
targetVC.data = self.data;
}
}
Check out performSegueWithIdentifier on UIViewController. If you set up a segue between the view controllers in your storyboard (not from the button) and give it an identifier, you can perform the segue as soon as the data is ready.
Since you mentioned that your data is being fetch asynchronously, you might need to dispatch the performSegueWithIdentifier call to the main thread like so:
dispatch_async(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:#"jumpToOtherViewController" sender:self];
});
To actually pass the data on to the next view controller, you can use prepareForSegue as described here.
Iv searched a lot for this and cant find an answer to my specific question. But basically my question is, can i pass a completion block into another view controller and redefine it in the new view controller.
So for example in view controller A i have a method to perform a download with a completion block. First i create my block property in view controller A.
#property (copy)void (^downloadCompleteBlock)(NSArray *downloadItems);
I tried changing this to strong as opposed to copy but this did not solve my problem.
Then i define the completion block as follows.
self.downloadCompleteBlock = ^(NSArray *downloadItems) {
NSLOG(#"download complete in view controller A";
};
Then i call my download method passing in this completion block.
[self download:self.downloadCompleteBlock];
However, if this completion handler is not called by the time i leave this view controller (if the download isn't complete) I would want the completion block to perfrom something different on the next view controller. So in my prepare for segue i attempted to pass in this block to view controller B.
[controllerB setCompletionBlock:self.downloadCompleteBlock];
And this method in view controller B then redefines what happens when this completion block gets called.
- (void)setCompletionBlock:(void(^)(NSArray *downloadItems))downloadFinishedBlock {
downloadFinishedBlock = ^(NSArray *downloadItems) {
self.collectionData = downloadItems;
[self.collectionView reloadData];
};
}
However, the original block in view controller a still gets called when the download finishes as opposed to the block in view controller B. Anyone know how to have the completion block in view controller B called if that view is loaded before the download completes? I know i could use a notifier but i'm curious if i can do this with blocks.
Thanks
This is kind of a tough problem. At its heart is the problem of how to keep the block around after the first view controller goes away. Your current code solves that problem unwittingly by having the block refer to self. The vc is retained by that reference, which is good news if it needs to be around when the request finishes, but it's bad news because now the vc and the block will retain each other forever. (Google 'retain cycle'.)
So how do we get a long-running process that runs a block on completion and might outlive two or more view controllers? For starters, break that process into its own object. The interface of that object would look like:
#interface DownloadThingy
#property (copy)void (^downloadCompleteBlock)(NSArray *); // note, no need for dummy param names here
- (id)initWithRequestParams:(id)whateverIsNeededToStart;
- (void)start;
#end
Now, the view controller that want to start this can declare a strong property to it, create one, give it a completion block (see below**), and start it. When it's time for a segue, it can pass the downloadThingy to another vc, who can give it a different completion block.
** Since the request object is being kept as a property in one or more vcs, and since it retains the block, you still need to look out for a retain cycle:
(vc->downloadThingy->block->vc)
In VcA, do this:
- (void)startADownloadThingy {
self.downloadThingy = [[DownloadThingy alloc] initWithRequestParams:someParams];
__weak VcA *weakSelf = self;
self.downloadThingy.downloadCompleteBlock = ^(NSArray *downloadItems) {
// don't use self in here, use weakSelf
}
}
VcB will get called on the segue; it might or might not need to follow the same precaution. The distinction is whether this second vc retains a downloadThingy property. If it doesn't plan to hand it off to any other vc, it can skip the property, and thereby skip the worry about a retain cycle.
// another vc is handing off a running downloadThingy
- (void)heresARunningDownloadThingy:(DownloadThingy *)downloadThingy {
// if we have our own property, then
self.downloadThingy = downloadThingy;
// and we need to do the weakSelf trick
__weak VcA *weakSelf = self;
self.downloadThingy.downloadCompleteBlock = ^(NSArray *downloadItems) {
// don't use self in here, use weakSelf
}
}
Or...
// another vc is handing off a running downloadThingy
- (void)heresARunningDownloadThingy:(DownloadThingy *)downloadThingy {
// we do not have our own property
downloadThingy.downloadCompleteBlock = ^(NSArray *downloadItems) {
// feel free to use self in here
}
}
One last thing: it's a good practice for the DownloadThingy to aggressively nil out its block after it's through invoking it. So when the request is done, have it do this...
// DownloadThingy.m
// request is complete
self.downloadCompleteBlock(arrayFullOfResults);
self.downloadCompleteBlock = nil;
I want to define the method which will contain the block as an argument but block should be run on the completion of the method.
For Example:
[picker dismissViewControllerAnimated:YES completion:^{
imageThumb = pickedImage;
imageViewThumb.image = imageThumb;
}];
Please have a look what i did yet.
I declared the method in .h file-
-(void)resizeImageForSmoothness: (int) imageSmoothness completion: (void (^)(void))completion;
I implemented it in .m file-
-(void)resizeImageForSmoothness:(int)imageSmoothness completion: (void (^)(void))completion
{
// Here i performed my image resizing activity
}
How can my code will know that method has been completed and then run the completion block?
How can we declare and define such method?
How to store the block depends on how you do your stuff. If it's a synchronous operation (that is, the method blocks until whole operation is complete) you simply call it like a function:
- (void)fooWithHandler:(void(^)())handler
{
// Do things.
handler();
}
If the operation is asynchronous, you might want to store the block in a variable or even a dictionary. In this case you need to copy the block. You can either do this via the low-level Block_copy and Block_release C functions, but you can also treat a block like an Objective-C object! (Xcode doesn't provide autocompletion for this, for some reason.)
#interface MyClass {
void (^myHandler)();
}
- (void)fooWithHandler:(void(^)())handler
#end
#implementation MyClass
- (void)fooWithHandler:(void(^)())handler
{
myHandler = [handler copy];
// Do things.
// Then, when you're done (this is probably in another method):
if (myHandler) {
myHandler();
myHandler = nil;
}
}
#end
You can do something like that and use the return type et parameter you might need :
- (void)doStuffAndExecute:(void (^)(void))handler
{
// do stuff
handler();
}
I have a question about passing data in iOS/Objective-C. I just started creating an app that connects and fetches data from a website, and now I'm having a problem.
My default and root view controller is called ViewController. It has the basic login UI views: 2 textfields and a button.
When the button has been clicked/touched, the ViewController calls a method from another class called LoginService. Now LoginService handles the connection to the website. I have no problem connecting to the website and fetching data from it, but I have a problem returning the fetched data, now processed as an NSDictionary, to the ViewController.
The first thing I tried was to create a setter method in the ViewController that sets the instance variable userProfile to the NSDictionary passed into it. It failed, however. I tried using it in the NSURLConnectionDataDelegate method connectionDidFinishLoading from the LoginService.
This might be a silly question, but I have no idea how can I pass the fetched NSDictionary from LoginService to the ViewController after the button is clicked. Do I need blocks, queue, or something else? I mean, for example, I need to set a label below my login button to the name of the user who logged in. How can I perform this?
Hope someone can help me. I'd greatly appreciate it.
As danh has explained blocks pattern for doing this, I will try to explain the delegating pattern. The steps for making this work:
In LoginService.h
Create a protocol definition in your LoginService like this:
#protocol LoginServiceDelegate
-(void)applicationLoggedIn:(NSMutableDictionary*) responseData;
#end
Now add a member pointer holding this delegate and add a property for this
#interface LoginService {
id<LoginServiceDelegate>delegate;
}
#property (nonatomic, assign) id <LoginServiceDelegate> delegate;
In LoginService.m
Once you got the response for login in connectionDidFinishLoading, just invoke the delegate method like below:
if ([delegate respondsToSelector:#selector(applicationLoggedIn:)]) {
[delegate applicationLoggedIn:responseDict];
}
In LoginViewController.h
Now to use this in your LoginViewController, you need to implement this protocol
#import "LoginService.h"
#interface LoginViewController<LoginServiceDelegate>
In LoginViewController.m
Assign the delegate of LoginService to LoginViewController
LoginService* loginService = [[LoginService alloc]init];
loginService.delegate = self;
Implement the protocol method as:
-(void)applicationLoggedIn:(NSDictionary*)response{
}
Hope this helps.
Two patterns to consider: delegate and block. Block is quicker to code, and I usually prefer it to delegate for network ops. To use a block, write the login service this way:
// LoginService.h
- (void)login:(NSString *)username completion:(void (^)(NSDictionary *, NSError *))completion;
It sounds like you're using NSURLConnection delegate pattern here, so I will assume that. Please realize that NSURLConnection also provides a nice one-shot block method to do the request.
// LoginService.m
#property (copy, nonatomic) void (^completion)(NSDictionary *, NSError *);
- (void)login:(NSString *)username completion:(void (^)(NSDictionary *, NSError *))completion {
// copy the block when the request begins
self.completion = completion;
// start your request, as you have it now
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSDictionary *dictionary = // parse the data you collected into a dictionary
// invoke the block with the result
self.completion(dictionary, nil);
self.completion = nil;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
self.completion(nil, error);
self.completion = nil;
}
It's polite to dispose of the block (set it to nil) after you invoke it, so it doesn't retain any part of the calling context.
Basically you need ViewController to have a public method which LoginService can call when it's done its job, and NSDictionary will be a parameter to this method. LoginService will need a reference back to ViewController in order to invoke this method, so define a public property on LoginService which will hold a ViewController reference - and set this right after instantiating LoginService.
Of course, if you want LoginService to be more reusable, and not tied to ViewController specifically, delegates are the way to go. LoginService would define the LoginServiceDelegate protocol with the method to be called on completion. ViewController would then implement the LoginServiceDelegate protocol. The public property on LoginService becomes a LoginServiceDelegate reference, and so LoginService no longer needs to import ViewController. This way, ViewController is dependent on LoginService, but LoginService is not dependent on ViewController.
I want to create a completion handler for a certain class, instead of firing off the class's main code and waiting for a delegate callback. I've read through the Apple documentation and they don't seem to give a very good example of how to directly implement something like this.
You need to treat the completion block just like a variable. The method will accept a block as part of it's parameters, then store it for later.
- (void)myMethodWithCompletionHandler:(void (^)(id, NSError*))handler;
You can typedef that block type for easier reading:
typedef void (^CompletionBlock)(id, NSError*);
And then store your block as an instance variable:
In your #interface: CompletionBlock _block;
In the myMethod.. _block = [handler copy]
Then when you want the completion block to execute you just call it like a regular block:
_block(myData, error);
If it was for an asynchronous method you could do it like this
- (void)asynchronousTaskWithCompletion:(void (^)(void))completion;
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Some long running task you want on another thread
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) {
completion();
}
});
});
}
this would be invoked with
[self asynchronousTaskWithCompletion:^{
NSLog(#"It finished");
}];
Something to note is the guard to make sure that completion is pointing to something otherwise we will crash if we try to execute it.
Another way I often use blocks for completion handlers is when a viewController has finished and want's to be popped from a navigation stack.
#interface MyViewController : UIViewController
#property (nonatomic, copy) void (^onCompletion)(void);
#end
#implementation MyViewController
- (IBAction)doneTapped;
{
if (self.onCompletion) {
self.onCompletion();
}
}
#end
You would set the completion block when pushing this view onto the stack
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender;
{
MyViewController *myViewController = segue.destinationViewController;
myViewController.onCompletion = ^{
[self.navigationController popViewControllerAnimated:YES];
};
}
Heres an example for a method that takes a String and a completion handler as variables. The completion handler can also receive a String.
Swift 2.2 Syntax
Defintion:
func doSomething(input: String, completion: (result: String) -> Void {
print(input)
completion(result: "we are done!")
}
Calling the function:
doSomething("cool put string!") { (result) in
print(result)
}
Chris C's answer is correct (and was very helpful to me) with one caveat:
Placing the declaration CompletionBlock _block; in #interface is not thread safe.
Put CompletionBlock _block = [handler copy]; in myMethod… instead if there is any possibility that myMethod… will be called from multiple threads (or dispatch queues).
Thanks #Chris C.