Absolute Ripple
  • Absolute Ripple
  • Contact

Block Based Completion Handler

11/8/2013

0 Comments

 
If you have used some of iOS methods that uses block you would probably agree that they are quite wonderful to use. I am talking about methods such as these:
[UIView animateWithDuration:5.0
                     animations:^{
                         // some code
                     } completion:^(BOOL finished) {
                         // some code
                     }];

[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        // some code
    }];

Blocks enable you to write more compact and flexible codes that can be easily reused in your programs. It does take awhile to get use to the most weird looking syntax though.

Then you start to think, wouldn't it be nice if I could use a block based completion handler in some of my methods. But how? It looks so confusing and you just don't know where to begin. The block syntax makes it appears harder than it really is. The basic pattern is simple.

The Pattern

1. Typedef the block
typedef void (^NameOfYourCompletionHandler)(NSString *name, BOOL finished);
This block calls 'NameOfYourCompletionHandler'. It has two arguments - an NSString and a BOOL. Obviously, you can have more or less arguments to suit your situation.

2. Add a property
@property (nonatomic, copy) NameOfYourCompletionHandler nameOfYourCompletionHandler;
This is just like how you define any other property. i.e. preceding the name of the property with the type (i.e. NameOfYourCompletionHandler here). The name of the property can be whatever you like. If the code uses only one property of this block type, it is generally preferable to keep the property name the same as the name of the type (and lower casing the first character) to make it easier to follow.

3. Create an instance method (not a class method) that takes the block as one of its argument
- (void)doSomeTask:(NSString *)taskName withCompletion:(NameOfYourCompletionHandler)completionBlock
{
    self.nameOfYourCompletionHandler = completionBlock;
    // code to do something
}
The line self.nameOfYourCompletionHandler = completionBlock is important as this copies the completionBlock code to the instance variable so that it can be invoked later.
As noted above, this cannot be a class method as a class method would have any instance variables, such as the nameOfYourCompletionHandler here. If you must use a class method, you need to use static storage instead.

4. Call the completion block when you want to invoke it.
self.nameOfYourCompletionHandler(@"some name", YES);
This simply calls the completion block code and passes in the arguments.

This is all you need. And you can use the block based instance method like this, assuming you are invoking it within the same class:
[self doSomeTask:@"some task name" withCompletion:^(NSString *name, BOOL finished) {
        // code you want to perform
        // i.e. code you want to execute when the completion block is invoked under (4) above.    
    }];

       
Looking around online you will come across slight variation to the above, but the underlying pattern is the same. Some don't use typedef, and some don't use property (using static storage instead).

Without Typedef
Without using typedef, (2) would look like:
@property (nonatomic, copy) void (^nameOfYourCompletionHandler)(NSString *, BOOL)

And (3) would look like:
- (void)doSomeTask:(NSString *)taskName withCompletion:(void(^)(NSString *name, BOOL finished))completionBlock
{
    self.nameOfYourCompletionHandler = completionBlock;
    // code to do something
}

The rest would be the same.

Typedef makes the code looks cleaner (i.e. less weird syntax). However, it is just a personal preference/ coding style thing.

Using Static Storage
If you want to use block with class method you can do so with static storage.
Instead of (2), you would declare the static storage as follows in your implementation file:
static NameOfYourCompletionHandler _nameOfYourCompletionHandler;
And (3), would look like:
+ (SomeClass*)doSomeTask:(NSString *)taskName withCompletion:(NameOfYourCompletionHandler)completionBlock
{
   
_nameOfYourCompletionHandler = [completionBlock copy];
    // code to do something
}

And (4) becomes:
_nameOfYourCompletionHandler(@"some name", YES);

Other Useful Reference About Using Block
- Working with Blocks
- Using Blocks in iOS 4: The Basics
0 Comments

Using IOS Storyboard Segues

2/8/2013

1 Comment

 
Using storyboard to set up the design of the various views is the way to go in most apps these days for iOS app development. The friendly graphical interface makes it quite easy to use. After using it for awhile, you wonder why you would ever want to go back to a code base approach (although there may be some situations where you may still want to do things purely in code).

Segues are used when you want to:
1. Go to another view controller - it does this by instantiating a view controller you set up and link to in the storyboard. The new view controller can be presented in different ways: modal, push (when embedded within a UINavigationController), popover, or embedded (such as in a container view).
2. Go back to a view controller that directly or indirectly presents the current view controller. This is the unwind segues.
3. Pass values or objects between view controllers as you are going to a view controller or unwinding to a previous view controller. Using the segues to pass values or objects between view controllers is very convenient, especially passing values or objects back to a prior view controllers. Without using segues, passing values or objects back to a prior view controller would require the use of protocol and delegates or some notification services. Segues often provide a more elegant and simpler (i.e. less code) way to achieve this.

Setting up the actual storyboard in Xcode is easy enough, but getting your code to work with it or talk to it is something else; it is not hard, you just need to know how. There are many very useful tutorials online about various aspect of it. The aim here is not to recreate those tutorials. This is a brief summary of the various things that are commonly performed and codes snippets that are often used.

Going to Another View Controller

The example here assumes the following setup:
  • There are three view controllers - ViewControllerOne, ViewControllerTwo  and ViewControllerThree.
  • These are all embeded within an UINavigationController.
  • Clicking a button on ViewControllerOne will go to ViewControllerTwo via a push segue. Likewise, clicking a button in ViewControllerTwo will go to ViewControllerThree via a push segue.
The Storyboard looks like this:
Storyboard
The approach described below also work without the UINavigationController and where modal segues are used to go from ViewControllerOne to ViewControllerTwo and from ViewController Two to ViewControllerThree.

Creating the segues: control drag from the relevant button to the view controller you want to show.
Code in the source view controller: (i.e. the controller you are originating from)
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"ViewControllerTwoSegue"]) {
        ViewControllerTwo *vc2 = (ViewControllerTwo *)segue.destinationViewController;
        vc2.name = self.textField.text;
    }
}

where:
- 'ViewControllerTwoSegue' is the name of the segue set in storyboard
- 'ViewControllerTwo' is the class of the destination view controller (i.e. the view controller you are going to)
 
Passing values or objects: set the relevant property in the destination view controller inside the prepareForSegue method
In this example, ViewControllerTwo has a public property called 'name' (an NSString in this case). Here it is set to the value of a text field called 'textField' in ViewControllerOne.

Note: when the prepareForSegue method is called, the views in the destination view controller may not have been fully instantiated yet. This means it is often not a good idea to directly set the values of views (such as a text filed or label) in the destination view controller inside the prepareForSegue. e.g. if View Controller Two has a text field called 'textField', this may not work: vc2.textFeild.text = self.textField.text, or may work unreliably. It is often better to set the value of a property in the destination view controller and then set the text filed or label inside viewDidLoad of View Controller Two.

Going to Another View Controller Conditionally

Most of the time you want to go directly to a view when a button is clicked, but this is not always the case. Sometimes you may want to go to a view only under certain condition. Obviously, one way is to disable the button unless the condition is satisfied, but another is to stop the segue.
Stopping the segue: return NO for shouldPerformSegueWithIdentifier:
The code:
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
{
    if ([identifier isEqualToString:@"ViewControllerTwoSegue"]) {
        return NO;
    }
    return YES;
}

where:
- 'ViewControllerTwoSegue' is the name of the segue set in storyboard

This method is invoked automatically by iOS before it calls prepareForSegue. This allows you to put conditional codes in it, to determine whether to actually perform the segue. The above code will prevent the user from going to ViewControllerTwo ever. This is probably not what you want to do all the time. In real code, you need to change 'return No' to something else that return NO or return YES based on some conditions that suit your app.

Passing Values or Objects

Values or objects can be passed along to the view controller that you are going to (i.e. the destination view controller) by simply setting one or more public property in the destination view controller, such as the 'name' property in the example above.
The above code only works in situation where the view you are going to is a UIViewController. The above won't work if you are going to a UINavigationController. Consider, for example, you start from a  ViewController and the segue is connected to another view controller (say ViewControllerOne) that is itself embedded within a UINavigationController. The code above won't work because the object return by the destinationViewController property of the segue is not a UIViewController; it is in fact a UINavigationViewController. For this type of setup, use this code inside prepareForSegue:

    if ([segue.identifier isEqualToString:@"ViewControllerOneSegue"]) {     
            if ([segue.destinationViewController isKindOfClass:[UINavigationController class]]) {
                UINavigationController *navController = segue.destinationViewController;
               
                ViewControllerOne *vc1 = [navController.viewControllers objectAtIndex:0];
                vc1.name = self.textField.text;
            }       
        }

where:
- 'ViewControllerOneSegue' is the name of the segue set in storyboard
The above code snippet also did a bit of introspection to confirm that the destination view controller is in fact a UINavigationController, but this is optional and not required if you know that the segue will always go to a UINavigationController.


Going Back To Another View Controller - aka Unwinding

With segues we can go back to another view controller just as easy. Not only can we go back to the previous view controller (i.e. the view controller that was shown immediately before the current one), we can go back to any other view controllers. This is done via the unwind segue.

This mechanism is only to go back to one of the previous view controller, hence the name unwind segue; it is not design to go to any other view controller that you may have on the Storyboard. Unlike other segues, unwind segues do not instantiate a view controller; they go to an existing view controller only. There is no limit to how far back you can unwind, but the view controller that you are going back to must be one that lies along the chain of view controllers leading to the presentation of the current view controller.

Setting up the unwind segue is slightly less intuitive than setting up a segue that go to another view controller. The steps are:
1. Implement an IBAction method in the view controller that you want to unwind to;
2. Link this method to a button in the view that you want to unwind from.

The following code example demonstrates going from ViewControllerThree to ViewControllerOne by clicking a button in ViewControllerThree.

Add an IBAction: (put this inside the view controller that you want to return or unwind back to)
    - (IBAction)backToViewControllerOne:(UIStoryboardSegue *)segue
    {
        NSLog(@"from segue id: %@", segue.identifier);
    }

This method essentially provides the means for ViewControllerThree to talk to ViewControllerOne. The method name (i.e.'backToViewControllerOne'  here) can be anything you want so long that it takes one argument of the type UIStoryboardSegue and the return type is IBAction This is all that is required in terms of code (you don't need the NSLog of course). Additional codes are only required if you want to pass values or objects back.

Link it up in StoryBoard:
Unwind segue
In the Storyboard, assuming you have a button that you want to click to start the unwind action, control-drag from that button to the green 'exit' icon on the bottom of the view controller that you are unwinding from. In the example here, it is ViewControllerThree. A popup menu will appear, and one of the selection would be the name of the IBAction method. Here, it would be backToViewControllerOne.

Sometimes, however, the method name you have entered just won't show up. And this makes it somewhat confusing especially when you first learn to use this unwind segue thing. Not sure why this is the case; but it sure feels like a bug in Xcode. The fix is to simply close the project and reopen it; occasionally, restarting Xcode may also be required.

Once it is all link up, it should work. In the example here, clicking the button will then take you directly back to ViewControllerOne.

Passing values or objects back: simply access the relevant public property in the view controller you are returning from (referred to as the source view controller).
For example, you can use code like:
    - (IBAction)backToViewControllerOne:(UIStoryboardSegue *)segue
    {
        NSLog(@"from segue id: %@", segue.identifier);
        if ([segue.sourceViewController isKindOfClass:[ViewControllerThree class]]) {
            NSLog(@"from view controller three");
       
            ViewControllerThree *vc3 = segue.sourceViewController;
            self.textField.text = vc3.name;
        }
    }

This example simply sets the value of the text field called textField in the current view controller (ViewControllerOne) to the value of a property called 'name' in ViewControllerThree.

The above code uses introspection to determine which view controller the segue is returning from. Introspection is not essential, but can be handy for writing generic code that can handle unwinding from multiple view controllers.

If you need to set the value of the relevant property in ViewControllerThree to a particular value, this is can be done by the prepareForSegue method in ViewControllerThree. Suppose you want to set the property called 'name' above, you can do the following:
    - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {
        if ([segue.identifier isEqualToString:@"Vc3ToVc1Segue"]) {
            self.name = self.textField.text;
        }
    }

Document outline with unwind segue
where:
- Vc3ToVc1Segue is the name of the unwind segue. Setting the name of this segue gets a bit confusing. You set the name in the Storyboard by clicking on the segue in the so called 'Document Outline' on the left hand side of the Storyboard. This segue is not shown in the diagrammatic representation. Once the unwind segue is linked, it will be shown in the Document Outline, usually beginning with the words "Unwind segue from...". Clicking on that will allow you to set its name in the attribute inspector.

This simply sets the name property to the value in the text field called 'textField'. Together with the code in the backToViwControllerOne method above, this passes the value of the text field in ViewControllerThree back to ViewControllerOne via the unwind segue. This approach is much simpler than a protocol and delegate based approach.

1 Comment

    Archives

    August 2013
    July 2013
    June 2013
    May 2013
    January 2013
    October 2012
    September 2012
    March 2012
    February 2012

    Categories

    All
    Apple Policy
    In App Purchase
    Ios Programming
    Our Apps

    RSS Feed

    Disclaimers

    Here are some resources that I find useful in writing iOS apps. These are not necessarily unique as they are based on various sources online.
    This is mainly to provide a source of reference to myself and my associates but if it can be of use to someone else, that is good.
    Note that information is provided here as is. Use at your own risk. There is no guarantee that it is accurate. We will update the information as time and resources permit.

    Powered by Create your own unique website with customizable templates.