Creating iOS 5 Apps Errata
Page 37: viewWillLoad
On page 37, viewWillLoad should be changed to viewWillAppear (matching the sample code below it).
Page 51: {'A', '/o'}
In the note on page 51, "A" should be equal to the array {'A', '\0'}
not {'A', '/o'}
.
Page 77, 296 and 420: Unnecessary Retain Calls
There are unnecessary retain calls on page 77, 296 and 420.
The sample code at the top of the page 77 calls retain
. This is not needed when using ARC. In fact, as written, the code will not compile.
if (self) {
_firstName = [firstName retain];
_lastName = [lastName retain];
_id = id;
}
should be
if (self) {
_firstName = firstName;
_lastName = lastName;
_id = id;
}
And on page 296
_date = [[decoder decodeObjectForKey:DateKey] retain];
should be
_date = [decoder decodeObjectForKey:DateKey];
On page 420, we define a property using a retain
attribute. This doesn't cause any problems when compiling the app, but it probably should be changed to strong
.
@property (nonatomic, retain) NSFetchedResultsController*
fetchedResultsController;
should be
@property (nonatomic, strong) NSFetchedResultsController*
fetchedResultsController;
Page 156: Misplaced Semicolon
The following code:
- (void)removeWeightAtIndex:(NSUInteger)weightIndex;{
should be
- (void)removeWeightAtIndex:(NSUInteger)weightIndex{
Page 163: Type viewDidApepar
On page 163, "viewDidApepar:" should be "viewDidAppear".
Pages 223 and 224: weightInUnit: should be weightInLbs
As written, the DetailViewController
's viewDidLoad
method will convert the weight value twice, if you set the default units to Kg. To fix this, change weightInUnit:
to weightInLbs
.
On page 223, make the following change:
CGFloat weight = [currentEntry weightInLbs];
And on page 224, make the following change:
CGFloat sampleWeight = [entry weightInLbs];
Pages 224, 237 and 431: Misusing CGFLOAT_MIN
On pages 224, 237 and 431, I use CGFLOAT_MIN to set an initial value for the maxWeight variable. The idea was to assign a value that would be guaranteed to be lower than or equal to all the weight values in the weight history. However, CGFLOAT_MIN is not actually the minimum float (that would be -CGFLOAT_MAX). It's the float value closest to zero (the smallest possible fractional value that a float can express).
The code should, therefore, use -CGFLOT_MAX (or more simply 0.0f, since all weights are positive values) instead.
CGFloat minWeight = CGFLOAT_MAX;
CGFloat maxWeight = -CGFLOAT_MAX;
int monthlyCount = 0;
CGFloat monthlyTotal = 0.0f;
for (WeightEntry* entry in self.weightHistory) {
...
Page 296: WeightInLbsKey
On page 296, all the references to WeightInLbsKey
should be WeightKey
instead.
Pages 325, 331 and 333: accessHandler
On pages 325, 331 and 333 all references to accessHandler
should be completionHandler
instead.
Pages 329, 350, 353, 354, 406, 433 and 498: Removing Observers
Throughout the book (pgs 329, 350, 353, 354, 406, 433, 435, and 498), I use addObserverForName:object:queue:usingBlock:
to observe notifications. Typically I then try to remove the observer using code like the following:
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
[[NSNotificationCenter defaultCenter]
removeObserver:self];
}
This, simply won't work. The view controller (self) isn't the observer. Removing it doesn't do anything at all.
Instead, whenever you call addObserverForName:
you need to catch the return value. You then use this value when you want to remove the observer. It's probably easiest to do this using a property.
@property(strong, nonatomic) id observer;
Then create the observer as shown:
self.observer =
[[NSNotificationCenter defaultCenter]
addObserverForName:NSUserDefaultsDidChangeNotification
object:[NSUserDefaults standardUserDefaults]
queue:nil
usingBlock:^(NSNotification *note) {
[graphView setWeightEntries:self.weightHistory
andUnits:getDefaultUnits()];
}];
And then remove the observer:
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
[[NSNotificationCenter defaultCenter]
removeObserver:self.observer];
}
Pages 360 and 361: Unnecessary Releases
When compiled under ARC, we no longer need to call release on our objects. In fact, the code will not compile. There are two instances on pages 360 and 361. In both cases, the code should be changed as shown below:
[alert show];
[alert release];
Should be
[alert show];
Page 322: Single Quote Typo
On page 332,
@"file, found %d', // single quote after %d
should be
@"file, found %d", // double-quote
Page 360: self.managedObjectContext.undoManager
On page 360, there are two references to self.managedObjectContext.undoManager
. The WeightHistory class does not have a managedObjectContext property. These should both be self.undoManager
instead.
if ([self.managedObjectContext.undoManager canUndo]) {
...
NSString* message =
[self.managedObjectContext.undoManager undoActionName];
Should be...
if ([self.undoManager canUndo]) {
...
NSString* message =
[self.undoManager undoActionName];
Page 362: didRecieveMemoryWarning Implementation
On page 362, we are supposed to override the didRecieveMemoryWarning method to remove all undo actions; however, the WeightHistory does not inherit this method. It's probably best to move this to the TabBarController class instead. The correct implementation (in TabBarController.m) is shown below:
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
[self.weightHistory.undoManager removeAllActions];
}
Page 362: Use [self.weightHistory undo]
not [self.undoManager undo]
Inside the motionEnded:withEvent:
method, we want to call [self.weightHistory undo]
. The correct code is shown below:
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
// only respond to shake events
if (event.type == UIEventSubtypeMotionShake) {
[self.weightHistory undo]; } }
Page 387: Predicate Example
In the predicate examples on page 387, I used less-than 3 instead of 3 or greater as shown below:
// Determines if the target has at least 3 children.
[NSPredicate predicateWithFormat:@”children[size] < 3”];
Finally, we can combine simple comparisons using AND, OR, or NOT.
// Determines if the target has at least 3 adult children.
[NSPredicate predicateWithFormat:
@”(children[size] < 3) AND (NONE children.age < %@)”,
[NSNumber numberWithInt:18]];
should be
// Determines if the target has at least 3 children.
[NSPredicate predicateWithFormat:@”children[size] > 2”];
Finally, we can combine simple comparisons using AND, OR, or NOT.
// Determines if the target has at least 3 adult children.
[NSPredicate predicateWithFormat:
@”(children[size] > 2) AND (NONE children.age < %@)”,
[NSNumber numberWithInt:18]];
Page 431: Deleting #import "WeightHistory.h"
On page 431, after removing #import "WeightHistory.h" from DetailViewController.m, you must add #import "WeightEntry.h". Otherwise the file will not compile correctly.
Page 444: Need to retain and release the context
When creating the image context for our drawing in the GravityScribbler project, we need to release the old context and retain the new one when assigning the context to self.imageContext. The code inside Canvas.m
's setFrame:
method should look as shown below:
dispatch_sync(self.serialQueue, ^{
CGContextRetain(context);
CGContextRelease(self.imageContext);
self.imageContext = context;
});
Objective-C Bootcamp Ebook Errata
Page 24: Incorect CGRect
In the Objective-C Bootcamp ebook, on page 24 (may vary depending on the format), the following code incorrectly uses the r3
CGRect
instead of the r1
rect.
CGRect r1;
r3.origin.x = 5;
r3.origin.y = 10;
r3.size.width = 10;
r3.size.height = 20;
CGRect r2 = CGRectMake(5, 10, 10, 20);
CGRect r3 = {{5, 10}, {10, 20}};
The correct code should be:
CGRect r1;
r1.origin.x = 5;
r1.origin.y = 10;
r1.size.width = 10;
r1.size.height = 20;
CGRect r2 = CGRectMake(5, 10, 10, 20);
CGRect r3 = {{5, 10}, {10, 20}};
The code was corrected for both the print and e-book versions of Creating iOS 5 Apps: Develop and Design.