Advertisements

Make a game in a few weeks


Hey guys,

My friends at MakeGamesWithUs are creating a great opportunity to improve your iOS skills this summer. The MakeGamesWithUs Summer Academy is an 8 week program where you learn how to design, code, and ship your own iPhone game with locations in New York City and the San Francisco Bay Area. Their curriculum is used at schools like MIT, UC Berkeley, and Carnegie Mellon and their alums have gone on to work at awesome tech companies, startups, and game studios. You can find out more information and apply here:www.mgw.us/academy. The deadline to apply is this Saturday and you can use CupsOfCocoa as a discount code for 10% off!
Thanks again for visiting cupsofcocoa.com!
Make an iOS game this summer! Apply with discount code CupsOfCocoa
Advertisements

The Jungle, Part 7: Quartz Demos (Section 3 of 3)


In this last section, we will combine the drawing abilities of Quartz with the blazing fast animations that are provided by Core Animation.

Core Animation Primer

Core Animation is a framework for animating a number of properties on views. It was introduced with OS X 10.5 (Leopard) and iPhone OS 3.0. Animation is important because it conveys visual feedback , especially in state change. For example, zooming in on OS X and iOS is an animated process, rather than jumping from one zoom level to another. This shows what happened, rather than providing a visual disconnect. Core Animation handles the animation implicitly, which means that, if you choose to accept the default options, you can simply set a property and the transition will be animated. Of course, you can also have fine-grained control of the animation.

Core Animation animations are fully GPU-backed and coded through OpenGL. This allows the animations to be incredibly fast—the original iPhone could hit 30fps on UI animations while Windows XP and earlier versions of Android performed all the animation code in the CPU, which is a significant performance bottleneck (the benefits of hardware accelerated graphics).

Core Animation exists as a backing layer behind your views. The layer is a cached copy of the view stored in the graphics card. It propagates down a view hierarchy—subviews of a view are automatically backed, but parent views are not automatically backed. It might be easiest to back the topmost view, but because each layer is stored in video memory (which may be shared with the main system memory), it is best to minimize your memory footprint. Back only the layers you need to animate.

Core Animation allows you to perform some styling options that are much simpler than using Quartz. For example, you can take an image, apply a styled border, round the corners, and put a drop shadow underneath—and animate all of it, using a few lines of Core Animation code.

Let’s get started.

General-Purpose Drawing with Core Animation

Open up CustomView.m in our sample project, and go to drawOtherInContext:. First we’ll look at how to draw a rounded rectangle in Quartz. This uses a method introduced with the iPad in iOS 3.2; it was not available on the iPhone until iOS 4.2, seven months later; Core Animation was introduced in iOS 3.0.

First, make sure to import QuartzCore.h in CustomView.h:

#import <QuartzCore/QuartzCore.h>

The Quartz code:

UIBezierPath *roundedRectQuartz = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(10, 10, 70, 90) cornerRadius:8.0];
	[[UIColor orangeColor] setFill];
	[roundedRectQuartz fill];

The Core Animation code:

UIView *roundedRectView = [[UIView alloc] initWithFrame:CGRectMake(90, 10, 70, 90)];
	roundedRectView.backgroundColor = [UIColor orangeColor];
	roundedRectView.layer.cornerRadius = 8.0;
	[self addSubview:roundedRectView];

Although the Core Animation code is actually a line longer, you get to work with standard UIKit interfaces; in fact, you could perform CA-type drawing on existing UIView elements (like buttons and text fields) without having to subclass them (remember that Quartz runs through the drawRect: method, so you’d have to subclass to use Quartz). The line of interest is the third one, where we access the layer property on the newly created view. This layer is a reference to a CALayer, the class the represents the CA backing layer. The cornerRadius is a built-in property of the class.

You can manipulate CALayers as you would UIViews. So you can create another layer and add it to your existing layer:

CALayer *shadowBox = [CALayer layer];
	shadowBox.backgroundColor = [UIColor purpleColor].CGColor;
	shadowBox.shadowColor = [UIColor blackColor].CGColor;
	shadowBox.shadowRadius = 1.0;
	shadowBox.shadowOpacity = 0.3;
	shadowBox.shadowOffset = CGSizeMake(1.0, -2.0);
	shadowBox.frame = CGRectMake(120, 30, 20, 30);
	[self.layer addSublayer:shadowBox];

The results are exactly what you expect, but using easy Objective-C rather than C.

Finally, we’ll play around with an image:

CALayer *imageBox = [CALayer layer];
	imageBox.backgroundColor = [UIColor blackColor].CGColor;
	imageBox.borderColor = [UIColor whiteColor].CGColor;
	imageBox.borderWidth = 3.0;
	imageBox.cornerRadius = 10.0;
	imageBox.shadowColor = [UIColor blackColor].CGColor;
	imageBox.shadowRadius = 3.0;
	imageBox.shadowOpacity = 0.8;
	imageBox.shadowOffset = CGSizeMake(2.0, 2.0);
	imageBox.frame = CGRectMake(180, 10, 102, 64);
	CALayer *imageLayer = [CALayer layer];
	imageLayer.contents = (id)[UIImage imageNamed:@"Image Fill.jpg"].CGImage;
	imageLayer.cornerRadius = 10.0;
	imageLayer.masksToBounds = YES;
	imageLayer.frame = imageBox.bounds;
	[imageBox addSublayer:imageLayer];
	[self.layer addSublayer:imageBox];

Here, we actually need to create two layers. To force the image to have rounded corners (by default it’ll draw the image regardless of the corners), you need to set the masksToBounds property to YES. This, however, prevents the shadow from being drawn, as the shadow is outside of the bounds. Therefore, you need a second layer to hold the image; the first will contain the border and shadow.

you can also perform Quartz-like custom drawing with CALayers as well. You need to set a delegate for the layer; the delegate must implement drawLayer:inContext:, which is analogous to drawRect:. You then call setNeedsDisplay on the layer, which works just as it does with UIViews.

Animating with Core Animation

Let’s look at a quick example:

CALayer *pulsingBox = [CALayer layer];
	pulsingBox.backgroundColor = [UIColor whiteColor].CGColor;
	pulsingBox.borderColor = [UIColor blackColor].CGColor;
	pulsingBox.borderWidth = 2.0;
	pulsingBox.cornerRadius = 5.0;
	pulsingBox.frame = CGRectMake(10, 10, 80, 50);
	CABasicAnimation *pulsingAnimation = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
	pulsingAnimation.toValue = (__bridge id)([UIColor orangeColor].CGColor);
	pulsingAnimation.duration = 3;
	pulsingAnimation.repeatCount = 10;
	pulsingAnimation.autoreverses = YES;
	[pulsingBox addAnimation:pulsingAnimation forKey:@"backgroundColorPulse"];
	[self.layer addSublayer:pulsingBox];

Here we create a CALayer as before. Then we create an instance of CABasicAnimation, which allows us to animate the value of a key path. We want this to pulse, so we only need to set an ending value; setting a starting value is usually redundant anyway. The (bridge id) bit is simply an ARC-specific cast of a struct type to id. We set the duration of the animation, a repeat count, and have it automatically reverse, which gives the pulse we’re looking for. We then add the animation to the layer, which implicitly causes it to start animating. The addAnimation:forKey: method takes a string as its second argument; this is the string that is used to identify the animation. To stop the animation before it’s finished, you call removeAnimationForKey:, using the same key. You can also send removeAllAnimations to stop all animations for a layer.

In this way, you can only animate one property at a time. You can combine multiple CAAnimation objects in a CAAnimationGroup object, which contains an array of CAAnimations. You then set the animation group as the animation on a layer, and all the properties animate. This is useful for setting the timing on a group; the timing of animations within the group are clipped to the timing of the group.

Motion Paths and Repetition

You can create much more complicated animations using keyframes. Keyframes are locations in the animation where you explicitely set the values of certain parameters, and the animation system will calculate all the intermediate steps based on the animation properties and the start and end values. In Core Animation, this is represented by CAKeyframeAnimation.

CAKeyframeAnimation *bounceAnimation = [CAKeyframeAnimation animationWithKeyPath: @"position"];
bounceAnimation.removedOnCompletion = YES;
bounceAnimation.fillMode = kCAFillModeForwards;
bounceAnimation.duration = 5;
bounceAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];

We create our animation, tell it to remove itself after it’s done (to reduce processing and memory usage), retain the final position after it’s done (that’s what the fillMode specifies), make it take 5 seconds, and accelerate at the start and decelerate at the end.

Next, we have to define the path for the animation to follow (since we are animating the position). We create a mutable path object and draw to that, just as we would to a context. However, the functions we use have “path” in their names, rather than “context”. Finally, we assign the path to the animation.

CGMutablePathRef bouncePath = CGPathCreateMutable();
	CGPathMoveToPoint(bouncePath, NULL, 0, 120);
	CGPathAddArc(bouncePath, NULL, 0, 180, 60, 0.5*M_PI, 0, 0);
	CGPathAddArc(bouncePath, NULL, 120, 180, 60, M_PI, 0, 0);
	CGPathAddArc(bouncePath, NULL, 240, 180, 60, M_PI, 0, 0);
* 	CGPathAddArc(bouncePath, NULL, 360, 180, 60, M_PI, 0, 0);
	[bounceAnimation setPath:bouncePath];

Finally, we create a view to animate, and add the animation to the view.

UIView *animatingView = [[UIView alloc] initWithFrame:CGRectMake(0, 90, 48, 60)];
	animatingView.backgroundColor = [UIColor redColor];
	[self addSubview:animatingView];
	[animatingView.layer addAnimation:bounceAnimation forKey:nil];

We’ve gotten the view to animate, following a crazy path that we’ve defined. You’ll note that it’s not very smooth…but that comes down to the timing function. You can adjust the timing function just as you could the position of colors in a gradient. But that’s a topic for another time.

Sorry for the lack of images in this post: I lost a lot of data when a power surge knocked out my computer and much of my backup as well. All the screenshots I had were lost. But here’s the code from this project, so you can build and run at your leisure.

Download here

Extension: Rotation


In this post we’ll talk about how to handle rotating a UI. We’ll start by using existing constructs to allow our views to support rotation, and then discuss complications and their solutions. Start with a new Single View Application and call it AutoRotate. As usual, I’ll be using ARC. Open the main view controller’s implementation.

Enabling Rotation in Code

First, we have to tell the system that the view controller supports rotation and that it should rotate to a specific orientation. We do this by implementing an existing method on UIViewController:

#pragma mark - Rotation
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
	return (toInterfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}

The method might already exist in the file, provided by the template. In that case, simply change the method contents.

In this method, we’re returning a boolean value that tells the system whether to rotate to a specific orientation. We return YES for all supported orientations. Note though that we are not supporting UIInterfaceOrientationPortraitUpsideDown. Apple’s guidelines state that the upside-down orientation should not be supported unless necessary, because might end up being confused about which way is up, an important feature of phones. Of course, this distinction isn’t made on the iPad, and Apple strongly recommends to support all four orientations on iPad. But for now, we’ll support all except upside-down.

Now we’ll build the interface and implement the actual rotation logic—open the XIB. Design an interface like this one. It doesn’t matter exactly what you use, but keep it simple with some of the basic UI elements. The bar at the bottom is a UIToolbar. I put three UIBarButtonItems on it and two Flexible Spacers in between.

Initial View

Initial View

We don’t need to hook up any of the elements, because we’re just concerned with rotating the view. Build and run the app.

Initial View in Simulator

Initial View in Simulator

You can rotate the iPhone Simulator by 90 degrees at a time. Go to the Hardware menu, and then select Rotate Left or Rotate Right. You can also use Command-LeftArrow or Command-RightArrow. The rotation will be accompanied by a corresponding animation, and you’re left with a view that look like this:

Mangled Rotation

Mangled Rotation

While the background rotated (along with the bottom toolbar, which is handled by the system, the rest of the view didn’t change. We can fix that with a few different ways.

Struts and Springs

Struts and springs are a simple IB construct that gives you a few options to stretch and position views. Select the “1” button and go to the Size Inspector (the one with the Ruler icon). You’ll see a section called Autosizing. If you mouse over the Example area to the right, it’ll animate to show you the changes.

Autosizing UI

Autosizing UI

The autosizing area is where you make the changes. You’ll see a square with I-beams (struts) on the outside and double arrows (springs) on the inside. The I-beams on the outside acts as “anchors” to the sides of the containing view. The arrows on the inside tell the subview to expand with the containing view. Behavioral conditions:

  • If all the I-beams are enabled, the subview will stay the same size and anchored near (0,0) in the containing view. On the iPhone, that would be the top-left corner.
  • If no I-beams or double arrows are enabled, the subview will stay in the same size in the center of the containing view.
  • If all the double arrows are enabled but no I-beams, the subview will expand proportionally to the containing view.
  • If all the double arrows and I-beams are enabled, the view will expand with the subview, keeping the same distance around all the edges.

You can see all of this happening in the Example.

We can use these struts and springs to position some of the UI. All the buttons and the label should have both springs enabled. The progress view and slider should have the horizontal spring enabled. Button 1 should have the top and left struts enabled; button 2 should have top and right. Button 3 should have just left; button 4 should have just right. The label should have no struts enabled.

The progress view should have just the left strut; the slider just the right strut. The textview at the bottom should have both springs, the bottom, and left and right struts enabled.

Build and run again, and we see something like this:

Struts & Springs UI

Struts & Springs UI

It’s almost perfect. Springs and struts give you some basic flexibility—it moved our buttons and label nicely—but for more complex situations, like the lower part of our view, we need something more robust.

Swapping Views

Swapping views as necessary gives you the flexibility to structure your views any way you want using the convenience of Interface Builder. Begin by adding two outlets to the view controller’s header:

@property (strong, nonatomic) IBOutlet UIView *portraitView;
@property (strong, nonatomic) IBOutlet UIView *landscapeView;

Synthesize the properties and go over to the XIB. Drag out a new view and go to the Attributes Inspector. Under Orientation in Simulated Metrics, select “Landscape”. Build a view similar to this:

Manual Landscape View

Manual Landscape View

Connect the new view as landscapeView, and the old view as portraitView. Go to the implementation file, where we will handle the swap. Add the following code to the bottom of the file, before the @end:

#define degreesToRadians(x) (M_PI * (x) / 180.0)
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
	if (toInterfaceOrientation == UIDeviceOrientationPortrait) {
		self.view = self.portraitView;
		self.view.transform = CGAffineTransformIdentity;
		self.view.transform = CGAffineTransformMakeRotation(degreesToRadians(0)); 
		self.view.bounds = CGRectMake(0.0, 0.0, 320.0, 460.0);
	}
	else if (toInterfaceOrientation == UIDeviceOrientationLandscapeRight) {
		self.view = self.landscapeView;
		self.view.transform = CGAffineTransformIdentity; 
		self.view.transform = CGAffineTransformMakeRotation(degreesToRadians(-90)); 
		self.view.bounds = CGRectMake(0.0, 0.0, 480.0, 300.0);
	}
	else if (toInterfaceOrientation == UIDeviceOrientationLandscapeLeft) {
		self.view = self.landscapeView;
		self.view.transform = CGAffineTransformIdentity; 
		self.view.transform = CGAffineTransformMakeRotation(degreesToRadians(90)); 
		self.view.bounds = CGRectMake(0.0, 0.0, 480.0, 300.0);
	}
	else
		return;
}

We start with a pre-processor macro that converts degrees to radians. iOS uses radians in its graphics work, but it’s easier for us people to think in degrees. Note that in this case, we will have to do some custom graphics work, because we will have to transform the view to match the rotation.

Inside the delegate method, we check for the corresponding orientation and swap the view in the first line of each condition. Then we reset the view’s transformation. We’ll cover transformations in a future post. We use a provided function to make a rotation transformation and apply it to the view. We also change the size of the view to fit the screen. All of these changes happen in the will method, so they are complete before the actual rotation happen, and the correct view will be displayed in time. Note that animating all aspects of the transition would require additional code, which is beyond the scope of this post.

Rotating Tips

On the iPhone, not all apps support all orientations, or even rotation at all. On iPad, apps should support as many orientations as possible—at least both variants one orientation; preferably all four orientations.

Note that landscape and portrait views don’t necessarily have to present the same information, or even the same appearance. The Music app on the iPhone displays a UIKit-based tab bar and table interface in portrait view, but a custom coverflow interface in landscape.

If you’re doing some custom views/drawing, make sure the view animates when you rotate, especially if you’re displaying content such a grid of icons or text. Otherwise, it is a very disorientating experience for the user and might discourage use of your app.

Finally, make sure there is some meaningful change when the user rotates. If you’re just stretching the UI, consider whether it makes sense to rotate, or if rotation is worth the effort. If you have text input, the larger keyboard might be worth it—but you also loose a large portion of the rest of the content. Otherwise, rotating might not be necessary.

Download AutoRotate here.

Extension: Advanced Tables


In this post we’re going to take a step away from our existing project and look at other things UITableView will allow us to do. We’ll load in data from a plist, add some more elements to our table view, including images, subtexts, and allowing editing. These features allow us and the user to customize table views beyond the default appearance. Table views are a very important part of the iOS SDK and are found in many apps; fortunately, they are easy to customize—you can even create your own cells in anyway you’d like!

Open Xcode and create a new Single View application. Call it “AdvancedTables”, set the class prefix to “AT”, Device Family to iPhone, and Use Automatic Reference Counting. Save the project somewhere and create it.

Next, click here to download a file which contains a list of 51 cities and their population. The file is a basic XML-based plist, which is a file type used throughout iOS to store simple data structures like this. Add the file into the Xcode project.

Setting up the View Controller

Open ATViewController.h and have it adopt UITableViewDelegate and UITableViewDataSource. In ATViewController.xib, drag out a Table View from the Library and place it inside the existing view. Control-Drag from the table back to File’s Owner, connecting the table’s data source and delegate outlets.

Next, go to ATViewController.h. Create two strong properties of type NSMutableArray; call them names and populations. In the .m file, synthesize them. We’ll load in data from the plist in the viewDidLoad method:

- (void)viewDidLoad {
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
	NSString *filePath = [[NSBundle mainBundle] pathForResource:@"Cities" ofType:@"plist"];
	NSData *data = [NSData dataWithContentsOfFile:filePath];
	NSPropertyListFormat format;
	NSString *error;
	id fileContents = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&error];
	self.populations = [[fileContents objectForKey:@"City Population"] mutableCopy];
	self.names = [[fileContents objectForKey:@"City Names"] mutableCopy];
}

Notice that we don’t do any sort of checking on the fileContents result. It would may seem like a good idea to at least check if the dictionary had the two keys; if it only had one, the app would crash when trying to access one or both of them. However, this is a special design consideration. The data source is the driving force of the entire app; it wouldn’t make much sense if some of this data doesn’t exist. We don’t really want the app to continue if the data isn’t valid, so letting it crash might be a good idea in this case.

Next, we implement the data source methods like we did in the last post. Our table will only one section; with a more robust data source such as Core Data, it becomes much easier to implement multiple sections and an index down the side like you’d see in the Music app. For now though, we’ll settle for one section. The number of rows will be determined by the number of elements in either one of the data arrays, as they should correspond—and they do!

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
	return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
	return [self.names count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
	static NSString *cellID = @"CellID";
	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
	if (!cell) {		// Create new cell
		cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellID];
		cell.showsReorderControl = YES;
	}
	cell.textLabel.text = [self.names objectAtIndex:indexPath.row];
	cell.detailTextLabel.text = [NSString stringWithFormat:@"Population: %@", [self.populations objectAtIndex:indexPath.row]];
	cell.imageView.image = [UIImage imageNamed:@"CaliforniaIcon.png"];
	cell.imageView.highlightedImage = [UIImage imageNamed:@"CaliforniaIconPressed.png"];
	return cell;
}

Notably here, we create a cell with a different style, one with a subtitle. The iOS SDK comes with four styles, shown below (click for larger version):

iOS Table View Cell Styles

iOS Table View Cell Styles

The default style doesn’t contain any detail text; nothing will happen if you set the detailTextLabel property.

The showsReorderControl property is a boolean; if true, it will display a reordering control that appears in editing mode. We’ll get into that in a little bit.

Adding an Image to Cells

If you want to add an image to the left of the cell (as with album art in the Music app, or video previews in the YouTube app), it takes very little work. Download the icons and add the following lines within the if (!cell) block:

cell.imageView.image = [UIImage imageNamed:@"CaliforniaIcon.png"];
cell.imageView.highlightedImage = [UIImage imageNamed:@"CaliforniaIconPressed.png"];

The image property is what gets displayed normally; the highlightedImage is swapped in if the cell is highlighted.

If you want the image anywhere else in the cell, you’ll have to create your own cells, which will be a topic for another post—there’s a lot involved!

Editing Table Views

First we’ll need some UI to enable editing. Go into the XIB, lower the top margin of the table view, and drag out a normal Navigation Bar and place it at the top of the view, filling the gap. You can have a navigation bar without a nav controller; in that case, it just becomes an “anchor” of sorts for a few commands. You use nav bars at the top of the screen and toolbars at the bottom. Drag out a Bar Button Item and place it on the left of the nav bar; a “well” will appear as you drag over the location. In the Attributes Inspector, set the Title to “Edit”. Connect the button to a new property called editingToggle. In addition, create an outlet for the table view; call it tableView. Wire it up.

Create a new method called toggleEdit and wire it up to the button. First, we’ll set the table’s editing mode to whatever it’s currently not—if it’s not in editing, make it enter editing mode and vice versa. Then we’ll adjust the button to reflect this change in state. In iOS, the Done button has a different tint; we can use a system-defined parameter rather than having to approximate it with our own.

- (IBAction)toggleEdit:(id)sender {
	[self.mainTable setEditing:!self.mainTable.isEditing animated:YES];
	if (self.mainTable.isEditing) {
		[self.editingToggle setStyle:UIBarButtonItemStyleDone];
		[self.editingToggle setTitle:@"Done"];
	}
	else {
		[self.editingToggle setStyle:UIBarButtonItemStyleBordered];
		[self.editingToggle setTitle:@"Edit"];
	}
}

Next we implement a few data source methods to allow editing, then to handle the edits.

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
	return YES;
}

- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
	return YES;
}

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
	NSUInteger fromRow = [fromIndexPath row];
	NSUInteger toRow = [toIndexPath row];
	id name = [self.names objectAtIndex:fromRow];
	id pop = [self.populations objectAtIndex:fromRow];
	[self.names removeObjectAtIndex:fromRow];
	[self.populations removeObjectAtIndex:fromRow];
	[self.names insertObject:name atIndex:toRow];
	[self.populations insertObject:pop atIndex:toRow];
}

- (void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
	NSUInteger row = [indexPath row];
	[self.names removeObjectAtIndex:row];
	[self.populations removeObjectAtIndex:row];
	[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}

The first two methods tell the table that all the rows can be edited (in this case, deletion is allowed; the alternative is None or Insertion), and that they can be moved. Then we declare the methods that handle the move or delete (in the latter case, it falls under the commitEditingStyle: method). In those methods, we remove (and insert) objects from our backing arrays as necessary.

These edits will remain until the memory is cleared (when the app quits). We’ll look at persistence—saving these changes back to the file—in a later extension.

Other Actions

The UITableViewDelegate declares some methods to support some other actions, including accessory views (views on the side of the cell, which you can wire up to trigger additional actions). Now, we’ll handle the selection, and allow you to put a check mark next to the cell that the user selects.

First, we’ll need to create a new property of type NSIndexPath that will hold the current selection.

@property (strong, nonatomic) NSIndexPath *lastIndexPath;

Next, we need to do some checks in the cellForRow… method—because the method will recycle cells as you scroll, we don’t want the checkmarks to get recycled as well. We check to see if a selection has been made, and if the rows are the same. If they are, then we display the checkmark (this is useful when you scroll back to your selection). Else, we display no checkmark (this is useful if you scroll down or up past your existing selection).

We handle the selection like this:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
	NSUInteger row = indexPath.row;
	NSUInteger oldRow = lastIndexPath.row;
	if (oldRow != row) {
		UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath]; 
		newCell.accessoryType = UITableViewCellAccessoryCheckmark;
		UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:lastIndexPath];
		oldCell.accessoryType = UITableViewCellAccessoryNone;
		lastIndexPath = indexPath;
	}
	[tableView deselectRowAtIndexPath:indexPath animated:YES];
}

If the selections are different, we put a checkmark on the new cell and put nothing on the old cell. If they’re the same, nothing changes. In either case, we deselect the cell to prevent it from being highlighted. Build and run, and you can see the checkmark appearing as you click on each cell.

Row Heights

You can change the height of one or more rows using a simple delegate method:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
	return 88;
}

The default height is 44; this method would make the cells twice as high.

Indenting Rows

You can control the indent of each row with a delegate method:

- (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath {
	return indexPath.row;
}

This example would create a cascade of cells being indented further with each row. Going beyond a level of 5 or 6 looks really weird, so don’t go too far.

That’s the primary abilities that standard table views can offer. The data source and delegate protocols declare a few other features; we’ll touch upon some of them including sections and the index when we start working with files and persistence.

Download the project here.

Objective-C Lesson 12: Exception Handling


Because of Objective-C’s dynamic runtime there are cases where errors go unnoticed until runtime. These might include sending messages an object doesn’t respond to, or going out of bounds of an array. Sometimes you may not even catch these errors while you’re testing—for example, say you had an application that allowed users to access a value in an array by entering an index value. If the user entered ’20’ when there are only 5 values in the array, the index would be out of bounds; in your testing, you may never have thought to check such numbers.

Granted, the above example is rather contrived, but you should still try to anticipate such issues and do something about them, so your program doesn’t crash. Resolving these issues generally includes logging them and informing the user—an incorrect value, in the above example, or if it’s an error the user can’t fix (sending the wrong message), notify the user and then gracefully exit.

Runtime issues such as these are typically exceptions. If uncaught, your program will quit. Let’s look at an example.

#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	NSArray *array = [[NSArray alloc] initWithObject:@"A string"];
	[array nonExistentMethod];
	NSLog(@"Object at index 5 is %@", [array objectAtIndex:5]);
	[array release];
	NSLog(@"No issues!");
	[pool drain];
	return 0;
}

Building the program nets you a warning that your array may not respond to nonExistentMethod (obviously). If we run it, we get a whole bunch of info:

2011-05-02 19:18:59.492 Exceptions[760:707] -[__NSArrayI nonExistentMethod]: unrecognized selector sent to instance 0x100113e90
2011-05-02 19:18:59.525 Exceptions[760:707] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI nonExistentMethod]: unrecognized selector sent to instance 0x100113e90'
*** First throw call stack:
(
	0   CoreFoundation                      0x00007fff8f509406 __exceptionPreprocess + 198
	1   libobjc.A.dylib                     0x00007fff941a09ea objc_exception_throw + 43
	2   CoreFoundation                      0x00007fff8f584ade -[NSObject doesNotRecognizeSelector:] + 190
	3   CoreFoundation                      0x00007fff8f4db8a3 ___forwarding___ + 371
	4   CoreFoundation                      0x00007fff8f4d8178 _CF_forwarding_prep_0 + 232
	5   Exceptions                          0x0000000100000e33 main + 195
	6   Exceptions                          0x0000000100000d64 start + 52
	7   ???                                 0x0000000000000003 0x0 + 3
)
terminate called throwing an exception

Wading through that, we see some indications that array does indeed not respond to nonExistentMethod. For education’s sake, we’re going to keep that line, and instead use the @try…@catch() blocks. This is what our program now looks like:

#import 

int main(int argc, char *argv[]) {
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	NSArray *array = [[NSArray alloc] initWithObject:@"A string"];
    @try {
        [array nonExistentMethod];
    }
    @catch (NSException *exception) {
        NSLog(@"Caught exception %@", exception);
    }
	NSLog(@"Object at index 5 is %@", [array objectAtIndex:5]);
	[array release];
	NSLog(@"No issues!");
	[pool drain];
	return 0;
}

The code in the @try block is your usual program code. The code in the @catch block is your error handling code—here, we just log the exception. In fact, the next NSLog will also cause an exception—an out of bounds exception. We can therefore move that line into the @try block.

int main(int argc, char *argv[]) {
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	NSArray *array = [[NSArray alloc] initWithObject:@"A string"];
    @try {
        [array nonExistentMethod];
        NSLog(@"Object at index 5 is %@", [array objectAtIndex:5]);
    }
    @catch (NSException *exception) {
        NSLog(@"Caught exception %@", exception);
    }
	[array release];
	NSLog(@"No issues!");
	[pool drain];
	return 0;
}

The results are now

2011-05-02 19:33:09.308 Exceptions[872:707] -[__NSArrayI nonExistentMethod]: unrecognized selector sent to instance 0x100113e90
2011-05-02 19:33:09.312 Exceptions[872:707] Caught exception -[__NSArrayI nonExistentMethod]: unrecognized selector sent to instance 0x100113e90
2011-05-02 19:33:09.313 Exceptions[872:707] No issues!

Note now that our program no longer crashes, but instead catches the exceptions, and continues. Note that the @try…@catch block is exited after the first exception is caught, regardless of if there are any others.

You can also append a @finally { } block that executes regardless of whether there has been an exception or not.

Throwing Exceptions

Unlike in Java, Objective-C exceptions should not be thrown whenever there is an error—try to handle the error, or use something like NSError or the NSAssert() method.

As Apple states, “You should reserve the use of exceptions for programming or unexpected runtime errors such as out-of-bounds collection access, attempts to mutate immutable objects, sending an invalid message, and losing the connection to the window server. You usually take care of these sorts of errors with exceptions when an application is being created rather than at runtime.
If you have an existing body of code (such as third-party library) that uses exceptions to handle error conditions, you may use the code as-is in your Cocoa application. But you should ensure that any expected runtime exceptions do not escape from these subsystems and end up in the caller’s code. For example, a parsing library might use exceptions internally to indicate problems and enable a quick exit from a parsing state that could be deeply recursive; however, you should take care to catch such exceptions at the top level of the library and translate them into an appropriate return code or state.”

Throwing exceptions is a rather “expensive” procedure, and of course uncaught exceptions will cause your program to crash. That being said, throwing an exception is straightforward:

NSException* myException = [NSException
        exceptionWithName:@"IndexOutOfBoundsException"
        reason:@"Attempted to access an array index that is out of bounds"
        userInfo:nil];
@throw myException;
// [myException raise]; /* equivalent to throwing the exception, above */

A more popular usage is in the case where you can’t recover from the issue (invalid data, for example, or where you tried to access an index that is beyond the size of an array)—you could catch the exception to perform some cleanup, then throw it back to the system for something else to catch, or to quit at that point.

Exceptions provide a powerful and flexible way to handle issues in your code. Use them wisely—don’t make the runtime system a massive juggling round.

Data Encapsulation


A central tenet of object-oriented programming is that the objects act as “black boxes”—that is, they hide, or encapsulate, their contents. When you call a method, you don’t care how the object does what you are asking it to do; you simply expect that the object will get the task done. The same concept applies to instance variables. The use of properties, and the accessor methods in particular, help to hide the instance variables themselves. You are indirectly accessing them; you get to control their access (such as making them readonly). By using methods, you also don’t have to worry about how the instance variables themselves are set up; you simply access the methods that are provided. An additional benefit of data encapsulation is that the creator of the class can change the internal workings (change the way the instance variables are set up, or the way a method operates) without breaking code that relies on the class. It’s like a car—most people don’t care how the car works, whether it burns gas or runs on gerbil power, as long as it gets them to the grocery store and back.

 

To be honest, it’s a difficult concept to explain; perhaps this page over at Stack Overflow (great site, BTW) does a better job.

Objects (Part 2): Properties


Every object is made up of instance variables (iVars) and methods. Our Fraction class, which we really began to build in the last post, contains two iVars, both NSIntegers, called numerator and denominator. Fraction also has two methods, setNumerator: and setDenominator:. These methods will set a value to the corresponding instance variable. In most cases, you would also have a method, with the same name as the instance variable (yes, this is permitted, and in this case, encouraged), which would return the value of the instance variable. These setter and getter methods are referred to as mutator and accessor methods, respectively. These methods are a major concept of object-oriented programming—data encapsulation, the notion that objects hide their instance variables.

At this moment, our Fraction class defines and implements the setter methods (note that we simply have not needed the getter methods so far). However, this would be difficult to do when we start working with large classes, which have many instance variables. This is also not very scalable; the same code to retrieve values for a small block of data stored on an iPhone should probably not be the same code used to retrieve a long string of data from a powerful desktop Mac or even from the internet. Fortunately, there is a very convenient concept that Objective-C provides, and this construct should be used wherever possible.

Properties

Properties replace the accessor methods for an object. Quite simply, our Fraction class’s interface now looks like this:

#import 
@interface Fraction : NSObject { 
	NSInteger numerator; 
	NSInteger denominator; 
}
@property NSInteger numerator; 
@property NSInteger denominator;
- (void)setNumerator:(NSInteger)value; 
- (void)setDenominator:(NSInteger)value;
- (void)display; 
@end

The general form of the property statement is

@property type name

Note that the setter (and if we had them, the getter) methods are no longer needed. We will have the compiler synthesize these methods for us, but placing this line in our implementation, right after the @implementation line:

#import "Fraction.h" @implementation Fraction
@synthesize numerator, denominator 
- (void)setNumerator:(NSInteger)value { 
    numerator = value; 
} 
- (void)setDenominator:(NSInteger)value {
    denominator = value; 
} 
- (void)display { 
    NSString *numeratorString = [[NSString alloc] initWithFormat:@"%d", self.numerator]; 
    NSString *denominatorString = [[NSString alloc] initWithFormat:@"%d", self.denominator]; 
    NSLog(@"%@/%@", numeratorString, denominatorString); 
    [denominatorString release]; 
    [numeratorString release]; 
}
@end 

Of course, the identifiers after the @synthesize have to match the names you declared as properties in the interface. However, the identifiers that you declared after @property do not have the match the instance variables; instance variables will be created at compile time if this happens.

You do not have to change your method calls in your main() routine; the methods still exist. However, there is an easier (and sometimes controversial) way to access these synthesized properties.

The Dot Operator

As previously mentioned, a getter method returns the value of an iVar; a setter sets a value to the iVar.

[myFraction setNumerator: 2] // Set numerator to 2 NSInteger 
numeratorValue = [myFraction numerator]; // returns the value of the numerator, and set to the new variable called numeratorValue

The dot operator, introduced in Objective-C 2.0 (yes, there was a 1.0, but that was a long time ago), allows you to invoke the getter method by writing

iVar.property

and the setter by

iVar.property = value

Therefore, the above example can be re-written as

myFraction.numerator = 2; // Set numerator to 2 
NSInteger numeratorValue = myFraction.numerator; // set value of numerator to numeratorValue 

C programmers might recognize this syntax as being used to access members of a struct—because that is exactly what you are doing. Behind the scenes, an object’s instance variables are stored as a struct. And if you have no idea what this line means, don’t worry—structs are a basic C data type that we may cover in a later lesson.

Property Attributes

In a property declaration, you can specify any number of (non-contradictory) attributes:

@property (attribute1, attribute2, etc.) type name;

A list of possible attributes follows (adapted from Apple’s developer documentation, The Objective-C Programming Language: Declared Properties)

Accessor Method Names

The default names for the getter and setter methods associated with a property are propertyName and setPropertyName: respectively—for example, given a property “foo”, the accessors would be foo and setFoo:. The following attributes allow you to specify custom names instead. They are both optional and may appear with any other attribute (except for readonly in the case of setter=).

  • getter=getterName: Specifies the name of a different (custom) getter method for the property. The method must return a value matching the iVar’s type, and it takes no arguments.
  • setter=setterName: Specifies the name of a different (custom) setter method for the property. The method must return void, and it takes one argument of the same type as the iVar.If you specify that a property is readonly then also specify a setter with setter=, you will get a compiler warning.

Writability

These attributes specify whether or not a property has an associated set accessor. They are mutually exclusive.

  • readwrite: This is the default; this indicates that the property will have read and write capabilities. When synthesized, both the getter and setter will be generated.
  • readonly This indicates that the property can only be read; no data can be assigned to it (through the property). When synthesized, only a getter will be generated; when you try to assign the property a value using dot syntax, a compiler error will be generated.

Setter Semantics

These attributes specify the semantics of the setter. They are mutually exclusive.

  • assign: This is the default; it tells the setter to directly assign the value. This is typically delineated with primitive data types (ints, floats, etc.)
  • retain: Specifies that a retain should be called on the object upon assignment; the previous value is released. This is only valid for objects. This has to do with memory management, a topic that will be fully explored in a later lesson.
  • copy: Specifies that the argument passed in should be copied, and the copy will be assigned. The previous value is released. This is only valid for objects. It is used in the case where the original property (perhaps stored on a shared server across the network) should not be modified by any other code, but when other code requests the value, for potential modification.

Atomicity

Atomicity means that the value is written to a temporary location in memory first, and then written to the intended location. This ensures that there is always a valid copy of the data somewhere. If an assignment was not atomic, and something happened during the writing period, the data could become fatally corrupt—the user would lose data. By writing data atomically, the data will either be the original, or the new value—never in a partially written state.

  • nonatomic: Specifies that the object should not be assigned atomically. By default, accessors are atomic (this is more beneficial in a multi-threaded environment, where multiple threads may be reading/writing value to a piece of memory.

CS193P Material

Properties are a very powerful notion, and used throughout the language. If this explanation was not clear enough, please see Lecture 3 of CS193P. The relevant content begins around the 16 minute mark, and ends with a demo around the 45 minute mark. Of course, feel free to view the whole lecture.

The slides for the lecture are available on the CS193P website; for a direct link, click here.

The code for the Fraction class at this moment can be downloaded by clicking here.

Supplement: CS193P


Stanford University has offered the CS193P class for a few quarters now. It has become the ubiquitous source of beginning iPhone programming material for many people. The lectures are filmed and uploaded to iTunes U, and they are now available in 720p HD video. However, the lectures, which are the only option for non-enrolled students, are rather impersonal, and sometimes move too quickly (in my opinion). I have learned a lot from them, but I don’t think they’re the only source. In fact, I find that they are more helpful once I got the basics down. Nevertheless, there are real gems, and so I will be occasionally referring links as supplementary material.

To access the lectures:

  1. Launch iTunes and head into the iTunes Store (don’t worry, all the lectures are free).
  2. Click on iTunes U on the black bar near the top of the storefront.
  3. At the time of this writing, the lectures were under Staff Favorites. Click on the link, and you’ll be led to the main listing.

    CS193P Fall 2010 Lectures Listing

    iTunes Listing

Direct link (will open iTunes, if it is not already running):
http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewPodcast?id=395605774

The lectures are available for streaming; alternatively, you can download them just like regular songs from iTunes. Note that file sizes are around 500MBs for the SD version and around 700MBs for the (linked) HD version.

The slides can be downloaded from the main website, listed above, as well as the sample code.

Extension 7: Loop Aids


In a loop, you can use the keywords continue; and break; to control the execution of the loop.

The continue Keyword

In a loop, continue tells the program to begin the next iteration, ignoring any code that comes after it. For example, if we had a simple program that reads in input and checks if the input is 5, we could use the following code:

for (int times = 1; times <= 10; times++) {
	int input;
	scanf("%d", &input);
	if (input != 5)
		continue;
	NSLog(@"Five!");
} 

In this loop, if the input is not 5, then the word “Five” is not printed. The program moves on to the next iteration of the loop.

The break Keyword

The break statement is used in the same context as continue, except that the break statement immediately ends the execution of the loop and moves on to the code after the loop. For example:

for (int times = 1; times <= 10; times++) {
	int input;
	scanf("%d", &input);
	if (input != 5)
		break;
}
NSLog(@"Not Five!");

“Not Five” will be printed as soon as the user enters something that is not five.

Pitfalls

Keep in mind that continue and break only apply to the “closest” loop—that is, in the case of nested loops, they will only apply to the “deepest” loop.

Extension 6: Incrementation


In the last Lesson, I used the following expression on Line 11 of the first example:

n = n + 1

This is perfectly valid code. The associativity of the equals operator (=) is right-to-left—this means that whatever is on the right of the operator is evaluated first, then the result is assigned back to the variable or identifier on the left side. So in this case, the program first determines the value of n + 1, and assigns this new value back to n. This method is fine, but there are more elegant solutions.

Operator-Equals

The following is completely equivalent to the above example:

n += 1;

What this is saying is to evaluate the result of n + 1 and assign it back to n—the identical meaning to the above example. The difference is that in this case the code is clearer, and much more concise. The following are all valid:

n += 3;    // Same as n = n + 3;
n -= 3;    // Same as n = n - 3;
n *= 3;    // Same as n = n * 3;
n /= 3;    // Same as n = n / 3;
n %=3;     // Same as n = n % 3;

In each case, the value of n operator 1 is evaluated, then the result is assigned back to n. This construct is highly recommended, and is used almost exclusively in place of the original.

++ and ––

That should be two minus signs; WordPress is over-optimizing the typography.

Returning to the concept of loops, most loops execute a specific number of times; this number is often determined by an upper limit value, and a counter is used to keep track of the increments until the maximum is reached. In these cases, the counter would be incremented by 1. In the original example, the code to do this was

n = n + 1;

As described above, n += 1; is also valid.

But incrementing (or decrementing) by 1 is such a popular thing to do in code (believe it or not) that Objective-C (and plain C) has a special construct to do just that:

n++;     // Increment by 1
n--;     // Decrement by 1 

As with the above construct, the double-plus or double-minus is used almost exclusively when the program needs to increment or decrement by 1. At the very least, it saves you typing; at best, compilers can produce more concise and efficient code when the double-plus or double-minus is used (Prata, C Primer Plus, 5th Edition, SAMS: Page 146). Note that these operators increment by exactly 1, and can only be used with integers.

One thing to note is that the increment and decrement operators have a prefix and postfix version.

Prefix versus Postfix

Compare the following statements:

int n = 4;
if (n++ < 5)     // Case 1
   NSLog(@"n is %d", n);
n = 4;           // "Reset" value of n
if (++n < 5)     // Case 2
   NSLog(@"n is %d", n);
NSLog(@"n is %d", n);     

The output is:

n is 5
n is 5 

There are three NSLogs in the code, yet only two lines of output. This reflects a subtlety in the increment/decrement operators. Why does this happen?

In Case 1, the value of n is compared with 5 first, and then incremented. n was initially set to 4; 4 is less than 5, so the initial NSLog executes. However, by that point, n had been incremented to 5.

In Case 2, the value of n is first incremented, then compared to 5. So n first becomes 5; 5 is not less than 5, and so the second NSLog does not execute. However, the final NSLog displays the value of n, which is now 5.

In the majority of cases, where only the end result is concerned, using the prefix or postfix does not matter—in either case, the end result is 5. However, for intermediate cases, such as the code within the if() statements, the choice does matter.

Finally, happy holidays to all my readers! Thanks for stopping by, and I hope you’ll come back for more. We’ll be getting into the good stuff (objects and more advanced programs) soon—probably before 2011 rolls around. Merry Hanukkah (and Kwanzaa, and whatever else is politically correct)! 😀

Advertisements
  • Welcome

    My goal is to make CupsOfCocoa into a beautiful source for beginners to the iPhone platform to get started. Subscribe below for more, and stay tuned!

  • Contact Me

    If you need to contact me for any reason, feel free to send me an email.
  • The Giving Spirit

    If you've found this site helpful, would you consider donating a little sum? Any amount is appreciated...Thanks so much!

  • Roadmap

  • Enter your email address to follow this blog and receive notifications of new posts by email.

    Join 223 other followers

  • Back to the Past

    September 2017
    S M T W T F S
    « Apr    
     12
    3456789
    10111213141516
    17181920212223
    24252627282930
  • Time Machine

  • You count!

    • 620,387 views
  • Worldwide Stats

    free counters
%d bloggers like this: