How To Use Blocks in iOS 5 Tutorial – Part 2
This is a blog post by iOS Tutorial Team member Adam Burkepile, a full-time Software Consultant and independent iOS developer. Check out his latest app Pocket No Agenda, or follow him on Twitter. Welcome back to our tutorial series on using blocks in iOS 5 – with some Storyboard/Interface Builder practice along the way! In […] By .
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How To Use Blocks in iOS 5 Tutorial – Part 2
50 mins
- Getting Started: An Intro to Blocks
- Back to the iOS Diner: Setting Up Model Classes
- Setting Up Basic Properties in IODItem
- Setting Up Basic Properties in IODOrder
- Setting Up Basic Properties in IODViewController
- Loading the Inventory
- Dispatch Queues and Grand Central Dispatch
- Adding Helper Methods
- Add and Remove Current Item
- UIAnimation
- Getting the Total
- Useful Blocks Cheat Sheet
- Creating Your Own Blocks
- Blocks and Autocompletion
- Where to Go From Here?
UIAnimation
Let's go back and add a little visual flair with another block method. Replace the ibaRemoveItem: and ibaAddItemMethod: code with the following:
- (IBAction)ibaRemoveItem:(id)sender {
IODItem* currentItem = [self.inventory objectAtIndex:currentItemIndex];
[order removeItemFromOrder:currentItem];
[self updateOrderBoard];
[self updateCurrentInventoryItem];
[self updateInventoryButtons];
UILabel* removeItemDisplay = [[UILabel alloc] initWithFrame:ibCurrentItemImageView.frame];
[removeItemDisplay setCenter:ibChalkboardLabel.center];
[removeItemDisplay setText:@"-1"];
[removeItemDisplay setTextAlignment:UITextAlignmentCenter];
[removeItemDisplay setTextColor:[UIColor redColor]];
[removeItemDisplay setBackgroundColor:[UIColor clearColor]];
[removeItemDisplay setFont:[UIFont boldSystemFontOfSize:32.0]];
[[self view] addSubview:removeItemDisplay];
[UIView animateWithDuration:1.0
animations:^{
[removeItemDisplay setCenter:[ibCurrentItemImageView center]];
[removeItemDisplay setAlpha:0.0];
} completion:^(BOOL finished) {
[removeItemDisplay removeFromSuperview];
}];
}
- (IBAction)ibaAddItem:(id)sender {
IODItem* currentItem = [self.inventory objectAtIndex:currentItemIndex];
[order addItemToOrder:currentItem];
[self updateOrderBoard];
[self updateCurrentInventoryItem];
[self updateInventoryButtons];
UILabel* addItemDisplay = [[UILabel alloc] initWithFrame:ibCurrentItemImageView.frame];
[addItemDisplay setText:@"+1"];
[addItemDisplay setTextColor:[UIColor whiteColor]];
[addItemDisplay setBackgroundColor:[UIColor clearColor]];
[addItemDisplay setTextAlignment:UITextAlignmentCenter];
[addItemDisplay setFont:[UIFont boldSystemFontOfSize:32.0]];
[[self view] addSubview:addItemDisplay];
[UIView animateWithDuration:1.0
animations:^{
[addItemDisplay setCenter:ibChalkboardLabel.center];
[addItemDisplay setAlpha:0.0];
} completion:^(BOOL finished) {
[addItemDisplay removeFromSuperview];
}];
}
The above might seem like a lot of code but it's really quite simple. The first new code segment we added just creates a UILabel and sets its properties. The second segment is an animation that moves the label that we just created. This is an example of the Block UIView animation system that we outlined at the beginning of this tutorial.
Compile and run and you'll see a nifty animation which shows the items being added and removed each time you tap the "+1" or "-1" buttons.
Getting the Total
The last helper method we are going to add to IODOrder.m is the method to total the order and return the result.
- (float)totalOrder {
// 1 - Define and initialize the total variable
__block float total = 0.0;
// 2 - Block for calculating total
float (^itemTotal)(float,int) = ^float(float price, int quantity) {
return price * quantity;
};
// 3 - Enumerate order items to get total
[self.orderItems enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
IODItem* item = (IODItem*)key;
NSNumber* quantity = (NSNumber*)obj;
int intQuantity = [quantity intValue];
total += itemTotal(item.price, intQuantity);
}];
// 4 - Return total
return total;
}
Let's go through the above code step-by-step:
- We define and initialize the variable that will accumulate the total. Note the __block keyword. We will be using this variable inside of a Block. If we do not use the __block keyword, the Block we create below would create a const copy of this variable and use that when referenced inside the Block, meaning we would not be able to change the value inside of the Block. By adding this keyword we are able to be read from AND write to the variable inside of the Block.
- Then, we define a Block variable and assign it a Block that simply takes a price and a quantity and returns the item total based on price and quantity.
- This code segment goes over every object in the orderItems dictionary using a Block method, enumerateKeysAndObjectsUsingBlock: and uses the previous Block variable to find the total for each item for the quantity ordered and then adds that to the grand total (which is why we needed the __block keyword on the total variable since it is being modified inside of a Block).
- Once we are done calculating the total for all the items, we simply return the calculated total.
Go back to IODOrder.h and add the prototypes:
- (float)totalOrder;
The last thing to do is to add the total calculation functionality to the app. All the heavy work will be done by the totalOrder method and so, all we have to do is show the calculated total to the user when they hit the total button and trigger the ibaCalculateTotal: action. So fill in the ibaCalculateTotal: stub in IODViewController.m with the following:
- (IBAction)ibaCalculateTotal:(id)sender {
float total = [order totalOrder];
UIAlertView* totalAlert = [[UIAlertView alloc] initWithTitle:@"Total"
message:[NSString stringWithFormat:@"$%0.2f",total]
delegate:nil
cancelButtonTitle:@"Close"
otherButtonTitles:nil];
[totalAlert show];
}
This just gets the total, creates a simple alert view, and shows it to the user.
That's it! Give it a final build and run, and maybe even grab a burger to celebrate! :]
Useful Blocks Cheat Sheet
Before you go, I wanted to let you know about a few block methods that you might find useful.
NSArray
- enumerateObjectsUsingBlock - Probably the Block method I use the most, it basically is a simpler, cleaner foreach.
- enumerateObjectsAtIndexes:usingBlock: - Same as enumerateObjectsUsingBlock: except you can enumerate a specific range of items in the array instead of all the items. The range of items to enumerate is passed via the indexSet parameter.
- indexesOfObjectsPassingTest: - The Block returns an indexset of the the objects that pass a test specified by the Block. Useful for looking for a particular group of objects.
NSDictionary
- enumerateKeysAndObjectsUsingBlock: - Enumerates through a dictionary, passing the Block each key and object.
- keysOfEntriesPassingTest: - Returns a set of the keys corresponding to objects that pass a test specified by the Block.
UIView
- animateWithDuration:animations: - UIViewAnimation Block, useful for simple animations.
- animateWithDuration:completion: - Another UIViewAnimation Block, this version adds a second Block parameter for callback code when the animation code has completed.
Grand Central Dispatch
- dispatch_async - This is the main function for async GCD code.
Creating Your Own Blocks
Also, sometimes you might want to create your own methods that take blocks. Here's some code snippets showing you how you can do that:
// Here's a method that takes a block
- (void)doMathWithBlock:(int (^)(int, int))mathBlock {
self.label.text = [NSString stringWithFormat:@"%d", mathBlock(3, 5)];
}
// Calling that method with a block
- (IBAction)buttonTapped:(id)sender {
[self doMathWithBlock:^(int a, int b) {
return a + b;
}];
}
Since a block is just an Objective-C object, you can store it in a property so you can call it later. This is useful if you want to call the method after some asynchronous task has completed, such as a network task. Here's an example:
// Declare property
@property (strong) int (^mathBlock)(int, int); // Use copy if not using ARC
// Synthesize property
@synthesize mathBlock = _mathBlock;
// Store block so you can call it later
- (void)doMathWithBlock:(int (^)(int, int))mathBlock {
self.mathBlock = mathBlock;
}
// Calling that method with a block
- (IBAction)buttonTapped:(id)sender {
[self doMathWithBlock:^(int a, int b) {
return a + b;
}];
}
// Later on...
- (IBAction)button2Tapped:(id)sender {
self.label.text = [NSString stringWithFormat:@"%d", self.mathBlock(3, 5)];
}
Finally, you can simplify your syntax a bit by using typedefs. Here's the previous example cleaned up a bit with a typedef for the block:
// Create typedef for block
typedef int (^MathBlock)(int, int);
// Create property using typedef
@property (strong) MathBlock mathBlock;
// Synthesize property
@synthesize mathBlock = _mathBlock;
// Method that stores block for use later
- (void)doMathWithBlock:(MathBlock) mathBlock {
self.mathBlock = mathBlock;
}
// Calling that method with a block
- (IBAction)buttonTapped:(id)sender {
[self doMathWithBlock:^(int a, int b) {
return a + b;
}];
}
// Later on...
- (IBAction)button2Tapped:(id)sender {
self.label.text = [NSString stringWithFormat:@"%d", self.mathBlock(3, 5)];
}