Did you find an error? Please submit it to the official reporting page at Apress.
General Notes
PDF is not searchable with Apple Preview in 10.6 Snow Leopard
There is a bug in Preview.app in 10.6 where password protected PDFs are not searchable. Please file a bug report on Apple's Bug Reporter (refer to Bug ID: 8055759). Other PDF viewers do not seem to have this problem. And Preview.app on 10.5 Leopard seems fine.
Note: Apress has just switched over to password-free PDFs. If you have protected PDF and would like an unprotected PDF, you may get a password-free version of the book by logging into your Apress account and re-downloading the book. (This is all free for existing purchasers.)
Watch out for “Smart Quotes” when copying & pasting from the eBook
When copying & pasting code from the eBook, be aware that depending on the software and settings you are using, you might accidentally get smart quotes (curly/curvy style quotes) instead of straight quotes when you paste. Smart quotes will confuse the compiler if they get into your source code. The most common place in the book where quotes are used are with the #import and #include statements. The second most common place are with string literals. There are also some places in the book which display smart quotes in code instead of straight quotes in code. Please report these to Apress here.
Updated download for Xcode 4 available off-site
Because Xcode 4 changed so many things from Xcode 3, I updated all the Xcode projects so they would work better out-of-the-box with Xcode 4. Unfortunately, Apress is ignoring my pleas to update the files on their server so I placed a copy of it at Google Code.
iPhone 4 Retina Display Discussion
Unfortunately, this book was written and went to publication before iPhone 4, so users running the examples particularly from Chapter 4 may experience resolution/sizing problems issues when running at higher resolutions. Industrious reader, Cyril Wei, took the initiative and wrote a blog article on how to fix these problems. You may find the article at http://cyrilwei.me/blog/?p=61, entitled: Quartz 2D Programming for iPhone 4 Series: Drawing at High-Resolution
Chapter 3
p. 56 Listing 3-20, p. 62 Listing 3-25 (credit: TokyoDan)
The code for the paddle collision in Listing 3-20 and 3-25 should contain the same code shown in Listing 3-16. Specifically:
if(paddleCollision) { ballMovement.y = -ballMovement.y; if (ball.center.y >= paddle.center.y - 16 && ballMovement.y < 0) { ball.center = CGPointMake(ball.center.x, paddle.center.y - 16); } else if (ball.center.y <= paddle.center.y + 16 && ballMovement.y > 0) { ball.center = CGPointMake(ball.center.x, paddle.center.y + 16); } else if (ball.center.x >= paddle.center.x - 32 && ballMovement.x < 0) { ball.center = CGPointMake(paddle.center.x - 32, ball.center.y); } else if (ball.center.x <= paddle.center.x + 32 && ballMovement.x > 0) { ball.center = CGPointMake(paddle.center.x + 32, ball.center.y); } }
p. 74-75 Listing 3-36 (credit: CodeTalks)
There are several bugs in the code section. First in the startAnimating
method, instead of creating the NSTimer with timerWithTimeInterval:
, a better method to use is scheduledTimerWithTimeInterval:
. The latter method automatically adds the timer to the runloop, whereas the former does not. Since this code does not show the timer explicitly added to the runloop, this is a bug which will prevent the timer from ever actually being used and going off.
In addition, the timer callback method should have a parameter associated with it.
- (void) changeAnimationImage
should be
- (void) changeAnimationImage:(NSTimer)the_timer
This actually may work either way, but to be pedantic, the latter is the official documented way because Apple is supposed to pass you the timer that fired.
Finally, the retain and releases for the NSTimer are incorrect. After you create the _theTimer = [NSTimer scheduledTimerWithTimeInterval: ...],
it needs to be retained.
[_theTimer retain];
Alternatively, you could use the dot-syntax to invoke the setter method:
self.theTimer = [NSTimer scheduledTimerWithTimeInterval:...]
Similarly, the stopAnimating
method needs to be corrected:
[_theTimer release];
or
self.theTimer = nil;
Here are the fixed methods in their entirety:
- (void)stopAnimating { if (_theTimer) { if (_theTimer) { [_theTimer invalidate]; [_theTimer release]; _theTimer = nil; } } - (void)startAnimating { if (self.animationDuration > 0 && self.animationImages && [self.animationImages count] > 0) { _frameCounter = 0; _repeatCounter = 0; _timeElapsed = 0; _theTimer = [NSTimer scheduledTimerWithTimeInterval:_animationInterval target:self selector:@selector(changeAnimationImage:) userInfo:nil repeats:(self.animationRepeatCount > 0)]; [_theTimer retain]; } } - (void)changeAnimationImage:(NSTimer)the_timer { self.image = [self.animationImages objectAtIndex:frameCounter++]; _timeElapsed += _animationInterval; if ( (_timeElapsed >= self.animationDuration || _frameCounter >= self.animationImages.length) && (0 < self.animationRepeatCount && _repeatCounter <= self.animationRepeatCount) ) { _repeatCounter++; _frameCounter = 0; } if (_repeatCounter >= self.animationRepeatCount) { [self stopAnimating]; } }
Chapter 4
p. 94 Listing 4-2 (credit: kmdowns, northkyut, baronholbach)
The getter methods for rotation and angle both end with a colon which shouldn't be there. Also, the angle methods incorrectly used the rotation variables.
- (CGFloat) rotation: { return rotation*180.0/3.141592; } - (void) setAngle: (CGFloat) degrees { rotation = degrees*3.141592/180.0; cosTheta = cos(rotation); sinTheta = sin(rotation); } -(CGFloat) angle: { return rotation*180.0/3.141592; }
Should read:
-(CGFloat) rotation { return rotation*180.0/3.141592; } - (void) setAngle: (CGFloat) degrees { angle = degrees*3.141592/180.0; cosTheta = cos(angle); sinTheta = sin(angle); } - (CGFloat) angle { return angle*180.0/3.141592; }
p. 100 (credit: rtrunck)
In the code example, there is a call to a method called updateBox which hasn't been defined. The method implementation should look like this:
-(void)updateBox { CGSize bsize = box.size; bsize.width = width; bsize.height = height; box.size = bsize; }
Also on this page, there is a #
character after the end of the drawRect: method. That character is a typo and should not be there.
Derek Huby also suggests this one line alternative for updateBox
-(void)updateBox { box.size = CGSizeMake(width, height); }
p. 106, Figure 4-27 (credit: threebrain)
@interface VectorSprite : Sprite { CGFloat *points; int count; CGFloat vectorScale;7 }
The 7
after CGFloat vectorScale;
should not be there.
p. 115 getSpriteAtlas memory leak (credit: Derek Huby)
The UIImage is over-retained which will result in a memory leak. One solution is as follows:
+ (UIImage *) getSpriteAtlas: (NSString *)name { NSMutableDictionary *d = [AtlasSprite sharedSpriteAtlas]; UIImage *img = [d objectForKey:name]; if (!img) { img = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:name ofType:nil]]; // img is greated with a retain count of 1 [d setObject:img forKey:name]; //img now has a retain count of 2 [img autorelease]; //img now has a retain count of 1 //which seems reasonable, as only the dictionary d knows about it } return img; //Anything calling this method will need to retain the received object, //if they want to be sure it will hang around. }
Also please note that the word 'get' in getSpriteAtlas may not have been the best term to use. In Cocoa, 'get' usually has a special meaning that parameters are returned by reference. Perhaps 'spriteAtlasWithName:' would have been a been a better name.
p. 116 AtlasSprite fromFile:withRows:withColumns: would probably be better as an autorelease method (credit: Derek Huby)
Derek Huby rightly points out that because the method does not start with alloc, copy, or new, the method should return an autoreleased object to follow Cocoa conventions and guidelines.
To make this change, we merely need to change the last line of the method from:
return s;
to
return [s autorelease];
But remember that any method that calls this method now must retain the object or your program will not work correctly (probably crash).
p. 121 Mario.jpg missing (credit: rtrunck)
Currently there are supplemental files not included in the main source bundle. Please download the additional figures.tar.gz.
p. 120 Listing 4-9 Number of columns for mario.png is incorrect (credit: threebrain, bassplayinMacFiend)
The mario.png was swapped out at the last moment for a different image. The source code assumes the Atlas image has 8 columns from the line:
#define kSteps 8
But the supplied image may only have 6 columns. If this is the case, change the number in the source code from 8 to 6:
#define kSteps 6
p. 128 Figure 4-39 (credit: demason)
For clarification, the method moveTo: which is called in this figure is defined in the bundled source code, but not explicitly listed in the book. The moveTo: method is defined in Sprite.m. And for those wondering why the method moveTo: is located in Sprite.m and not the file TextSprite.m in which the method in the figure is for, keep in mind that TextSprite is a subclass of Sprite so it inherits the moveTo: method from Sprite.
For convenience, the moveTo: method looks like this:
- (void) moveTo:(CGPoint)p { x = p.x; y = p.y; [self updateBox]; }
p. 129 (credit: alanedwards)
#import “TextView.h”
should be:
#import "TextSprite.h"
Chapter 5
p. 146 Listing 5-6 (credit: idelovski)
The method signature for the animationDidStop selector should use a NSNumber* instead of a BOOL for the finished: flag.
- (void)theRoadAnimationDidStop:(NSString *)theAnimation finished:(BOOL)flag context:(void *)context
should read:
- (void)theRoadAnimationDidStop:(NSString *)theAnimation finished:(NSNumber*)flag context:(void *)context
Chapter 7
p. 212 (credit: Derek Huby)
The method at the bottom of page 212 is called initWithVertexes: vertexCount: vertexStride: However, the method defined in BBMesh is actually called initWithVertexes: vertexCount: vertexSize:
p. 220 (credit: jmurff)
Due to a behavior change in OS 3.1.2, the handleTouches method needs to be slightly modified.
The original line was:
if (touch.phase == UITouchPhaseBegan) [self touchDown];
The new line should read:
if ((touch.phase == UITouchPhaseBegan) || ((touch.phase == UITouchPhaseStationary))) [self touchDown];
For convenience, the whole method looks like
-(void)handleTouches { NSSet * touches = [[BBSceneController sharedSceneController].inputController touchEvents]; if ([touches count] == 0) return; BOOL pointInBounds = NO; for (UITouch * touch in [touches allObjects]) { CGPoint touchPoint = [touch locationInView:[touch view]]; if (CGRectContainsPoint(screenRect, touchPoint)) { pointInBounds = YES; if ((touch.phase == UITouchPhaseBegan) || ((touch.phase == UITouchPhaseStationary))) [self touchDown]; } } if (!pointInBounds) [self touchUp]; }
Note that Chapter 8 and beyond already have this fix, but it was accidentally omitted here.
Chapter 10
p. 378
The line alGenBuffers(1, &laserOutputBuffer);
should not be crossed-out. (The bundled source code correctly leaves this line in.)
Chapter 11
p. 443-443 Apple Bug with AL_CONE_OUTER_GAIN set to 0.0
In iOS 4.1, Apple introduced a bug that causes errors when you set the AL_CONE_OUTER_GAIN to 0. This bug will cause the example code to print lots of OpenAL errors to the console.
Technically, the code is correct because the OpenAL specification says 0.0 is a valid value. So until Apple fixes the bug, you either need to ignore it or work-around it. Possible work arounds include ignoring the OpenAL error or checking for 0 and then setting to a small value such as 0.01.
The updated April 2011 code for Xcode 4 includes some workarounds.
I have filed a bug with Apple (8543491) and have mirrored it on Open Radar at: http://openradar.appspot.com/8543491
Chapter 12
p. 524
The dealloc method should also release the IBOutlet instance variables declared in the header. Take note that this is true on iPhone OS, but not on Mac OS X. Generally on Mac OS X, you do not explicitly release IBOutlet instances. But for iPhone OS which this example is for, the method should read:
- (void) dealloc { [self removeObserver:self forKeyPath:@"avRecorderSoundController.playing"]; [self removeObserver:self forKeyPath:@"avRecorderSoundController.recording"]; [avRecorderSoundController release]; [playButton release]; [recordButton release]; [super dealloc]; }
Chapter 15
p. 591 (credit: idelovski)
Two init methods don't send init to their super as most init methods are supposed to do. They should look something like the following:
- (id)initWithInputStream:(NSInputStream *)istr outpusStream:(NSOutputStream *)ostr { if (self = [super init]) { self.inputStream = istr; self.outputStream = ostr; } return (self); } - (id)initWithNativeSocketHandle:(CFSocketNativeHandle)nativeSocketHandle { CFReadStreamRef readStream; CFWriteStreamRef writeStream; if (self = [super init]) { CFStreamCreatePairWithSocket (kCFAllocatorDefault, nativeSocketHandle, &readStream, &writeStream); inputStream = (NSInputStream *)readStream; outputStream = (NSOutputStream *)writeStream; } return (self); }
p. 627 (credit: idelovski)
There is a memory leak with player. It should be released.
Player* player = [[[Player alloc] init] autorelease];
p. 627 (credit: idelovski)
GameControllerServer *game = [[GameController alloc] init];
should be:
GameControllerServer *game = [[GameControllerServer alloc] init];