BuySellAds.com

Our DNA is written in Objective-C
Jump

Category Archive for ‘Recipes’ rss

How to Fix Code Signing Errors

Andreas asks:

With Device | Debug and Code Sign “iPhone Developer” I have no problems getting my app onto the device, but with all other configurations I get this strange error message in XCode Organizer.
Unexpected Error

That’s a weird error that most of us have encountered one time or the other. Here are my hints how to get it fixed.

Read more

XIB Lazy Loading – Part 1

3 Months ago, when I started developing MyAppSales I felt confused by multiple XIB files, so I decided to put all view controllers into the MainWindow.xib. Not only does this not really gain you more overview, but this practise slows down application launch. On the iPhone you should defer work whenever possible.

That’s why I revisited my XIB files for version 1.0.1 and moved parts to external files wherever possible. Here are all the steps.

Read more

Shuffling an NSArray

For a project I am working on I needed to shuffle the contents of an NSArray without harming the items themselves. NSArray is a convenient container because it does not care about what you put inside. This is because you don’t (put objects into arrays), you only pretend.

You cannot add an object itself into an array but instead you always insert pointers to class instances. NSArray and its bigger cousin NSMutableArray will keep track of the pointers and memory management of the items so you don’t have to. This is a custom category for NSArray that is useful for shuffling the contents, regardless of their class type.

Read more

Unicode, Schmunicode!

You are listening to user feedback, especially those in Italy. You solve all their problems with a new version, in my case LuckyWheel 1.0.3. You polish it, test it (you think) and submit it to Apple for review. After a week you get this message back:

Your applications, LuckyWheel and LuckWheel Lite, cannot be posted to the App Store at this time because they do not achieve the core functionality described in your marketing materials, or release notes.  Applications must adhere to the iPhone Human Interface Guidelines as outlined in iPhone SDK Agreement section 3.3.5.

The release notes for both applications state, “Italian UI and Instructions added”.  However, in our review, when we put the device into Italian language mode and launched the applications, the application UI was still in English.  Only the instructions were changed to Italian.  See attached screenshots.

In order for your applications to be reconsidered for the App Store, please resolve this issue and upload your new binaries to iTunes Connect.

That’s a very long way to say: “Hey buddy, your Italian is English!”

When I got this message I was stumped. I though I had tested it. Thieves! Who has stolen my Italian UI?! But then I remembered something I had found out some months ago.

Read more

Anything goes … into NSArray

When switching to or beginning with Objective C you might be tempted to try to use the old c-style arrays, but that’s better left to the hard core C-enthusiasts. For programming Cocoa Touch we always use the NSArray class because of the additional intelligence it provides for us, not to mention integration with memory management.

The first thing I ever added into NSArray was string objects. And so will probably everybody who starts with Objective C.

NSString *someText = @"Static Text";  // static allocation
NSString *someMoreText = [[NSString alloc] initWithString:@"More Static Text"]; // manually allocated
NSArray *myArray = [NSArray arrayWithObjects:someText, someMoreText, nil];  // note the nil
[someMoreText release];  // don't forget to release
 
NSString *retrievedText = [myArray objectAtIndex:1]; // first index = 0
NSLog(retrievedText);

How about numbers? Generally you can only add instances of objects into NSArray. But luckily Apple has created the NSNumber class which provides a container object for any kind of number, i.e. int, float or even BOOL.

int i=123;
float f=5.0;
 
NSNumber *num_i = [NSNumber numberWithInt:i];
NSNumber *num_f = [NSNumber numberWithFloat:f];
NSNumber *num_b = [NSNumber numberWithBool:YES];
 
NSArray *myArray = [NSArray arrayWithObjects:num_i, num_f, num_b, nil];

With the methods above you are able to add strings and numbers into arrays. There is yet another wrapper class that allows to put even more complex data types and structs into arrays: NSValue. Most usefully are the UIKit additions to NSValue which give you the possibility of packaging CGRect, CGPoint, CGAffineTransform or CGSize structs into objects. And those are just as easy to put into an array.

CGRect aRect = CGRectMake(0, 0, 100.0, 100.0);
CGSize aSize = CGSizeMake(10.0, 20.0);
 
NSValue *val_rect = [NSValue valueWithCGRect:aRect];
NSValue *val_size = [NSValue valueWithCGSize:aSize];
 
NSArray *myArray = [NSArray arrayWithObjects:val_rect, val_size, nil];

Soon you will find it odd that NSArray does not have any method to add and remove objects. The reason for this is that most standard objects are non-changable (aka “immutable”) as such. To gain such modification features you have to use the mutable cousin NSMutableArray. This gives you methods like addObject or removeObject.

NSMutableArray *myArray = [[NSMutableArray alloc] init];
[myArray addObject:@"first string"];
[myArray addObject:@"second string"];
[myArray addObject:@"third string"];
 
[myArray removeObjectAtIndex:1];
 
NSLog([myArray description]);  // a quick way to show contents
 
[myArray release];

NSArrays are the meat and bones of most Objective-C apps. Anybody trying to master this language has no way around them.

Getting Standard Paths

These are the standard methods of getting the app directory and the documents directory. Remember that on the iPhone the app as well as the documents directory are “sandboxed” i.e. each app has it’s own two directories.

Here’s how you can get the path for files in your app bundle/directory:

// a regular file
NSString *plistPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"File.plist"];
 
// this works also for localized files, gets the best matching localized variant
NSBundle *thisBundle = [NSBundle bundleForClass:[self class]]; // alternate method to get bundle
NSString *path = [thisBundle pathForResource:@"File" ofType:@"plist"];

One common mistake is to pass a file name including extension in the first parameter  of the pathForResource method. This won’t work, pass only the name without extension as first parameter and only the extension as second parameter.

And to get something from the documents directory you can do it like this:

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *plistPath = [documentsDirectory stringByAppendingPathComponent:@"File.plist"];

You’ll find yourself copy/pasting these lines all the time basically for every file that you are reading or writing.

Drawing Text in Graphics Contexts

Sometimes you might need to draw text instead of just using a comfortable UILabel. For example if you want to show it a a specific angle. This takes a lot of code, but once you understand it, it’s just copy and paste.

- (void)drawRect:(CGRect)rect {
	CGContextRef ctx = UIGraphicsGetCurrentContext();
 
	CGContextSetRGBFillColor(ctx, 1, 0, 0, 1);   // inside red
	CGContextSetRGBStrokeColor(ctx, 0, 1, 0, 1);  // outline green
	double text_angle = -M_PI/4.0;  // 45 Degrees counterclockwise
 
	CGAffineTransform xform = CGAffineTransformMake(
		cos(text_angle),  sin(text_angle),
		sin(text_angle), -cos(text_angle),
		0.0,  0.0);
 
	CGContextSetTextMatrix(ctx, xform);
	CGContextSelectFont(ctx, "Helvetica", 20.f, kCGEncodingMacRoman);
	CGContextSetTextDrawingMode(ctx, kCGTextFillStroke);
	CGContextShowTextAtPoint(ctx, 100, 100, "Test Text", 9);
}

Of special importance is the application of a tranformation matrix via CGContextSetTextMatrix. Without it, your text will be drawn upside down. This is a feature, not a bug, because internally the iPhone still wishes that (0,0) is in the lower lefthand corner, but for most UIView-releated uses this is the “right way around”, i.e. (0,0) in the upper lefthand corner.

Angle of a Vector

When dealing with a multi-touch rotation I was searching for a simple method to calculate the angle of two fingers. Well, actually more precisely the angle between the vector from one finger to the second finger and the horizontal X Axis.

While I was still searching my friend Christian Pfandler came up with this function:

- (CGFloat)angleFromPoints:(CGPoint)pos1 otherPoint:(CGPoint)pos2
{
    CGPoint vector = CGPointMake(pos1.x-pos2.x, pos1.y - pos2.y);
    double angleCalc;
    if (vector.y < 0)
    {
        // upper Half
        angleCalc = atan2(-vector.y,vector.x);
    }
    else
    {
        angleCalc = atan2(vector.y,-vector.x)+M_PI;
    }
 
    return angleCalc;
}

The regular atan(x) function seem to have a limitation by only returning values between 0 and +/- PI. Because of this there is also the atan2(y,x) function which is orders of magnitude more useful. To get a continuos angle for the full circle we have to treat the upper half and the lower half seperately though. I found this here.

I don’t quite understand it, but it seems to work. :-) Please keep in mind though that like all angles in Cocoa Touch this is in radians as well. So not 0…360, but 0…2*PI. If you know an even simpler method, please let me know.

NSArray Sorting

If you need the contents of an NSArray in a different order than they where entered into the array you have several options available. By far the simplest is to use descriptions if you have a standard data type you wish to sort by. You first set up an array with the names of fields to sort by. Then you sort.

// first you need to set up the descriptor
 
NSSortDescriptor *firstDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"firstField"
							 ascending:YES] autorelease];
NSSortDescriptor *secondDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"secondField"
							ascending:YES] autorelease];
 
NSArray *sortDescriptors = [NSArray arrayWithObjects:firstDescriptor, secondDescriptor, nil];
NSArray *unsortedObjects = [NSArray arrayWithObjects:oneObject, secondObject, nil];
 
// new and shiny sorted array
NSArray *sortedObjects = [unsortedObjects sortedArrayUsingDescriptors:sortDescriptors];

A little more involved but way more powerful is the method of using a selector. Here you add a function to your special class that you are keeping in the aray which takes care of deciding which of two object comes first: self or the other one.

Note that the sort method needs to be part of the class itself. You can make your sorting logic as complex as you like to using multiple sort methods. Cocoa Touch calls this method every time it needs to know the order of two objects. Having access to all the other object’s properties you can have your function return NSOrderedAscending, NSOrderedDescending or NSOrderedSame depending on what order you determine to be correct.

//  MyOwnClass sort function
- (NSComparisonResult)compareByTime:(MyOwnClass *)otherObject
{
	NSTimeInterval diff = [self.timestamp timeIntervalSinceDate:otherObject.timestamp];
	if (diff>0)
	{
		return NSOrderedDescending;
	}
 
	if (diff<0)
	{
		return NSOrderedAscending;
	}
 
	return NSOrderedSame;
}

Once you have it set up like this sorting is as easy as:

// original unsorted array
NSArray *unsortedObjects = [NSArray arrayWithObjects:oneObject, secondObject, nil];
 
// new and shiny sorted array
NSArray *sortedObjects = [unsortedObjects sortedArrayUsingSelector:@selector(compareByTime:)];

Here is another example that uses the compare method that’s built into NSString:

NSArray *unsortedArray = [NSArray arrayWithObjects:@"String3", @"String2", @"String1", nil];
NSArray *sortedArray = [unsortedArray sortedArrayUsingSelector:@selector(compare:)];

Sorting is very simple with the two methods shown above. If you just want simple sorting then use descriptors. If you need more control over the sorting method then define your own sorting method.

Retain, Release … repeat

In Objective-C you have to make sure that you match your alloc and retains with the appropriate number of release calls. Otherwise you might be leaking objects. Here’s an example that I faced today.

The normal pattern if you create an object that you then add to an array is the following:

Player *tmpPlayer =  [[Player alloc] initWithName:player1Name.text color:[UIColor greenColor]];
[tmpArray addObject:tmpPlayer];
[tmpPlayer release];

Because adding an object to an NSArray causes the object to be retained you have to religiously follow any addObject with a release. You only want to keep the object retained once because when you later release the array, the array in turn releases all it’s objects and if the retain count is higher then 1 then these objects will leak. I.e. their dealloc won’t be called because they still have a retain count higher than zero.

To visualize the dance of alloc, init, retain, release and dealloc you could do the following:

- (id) initWithName:(NSString *)aName;
{
		if (self = [super init])
		{
			self.name = aName;
			NSLog(@"player %@ init, retain count is now %d", name, [self retainCount]);
 
			return self;
		}
 
	return nil;
}
 
- (void) retain
{
	NSLog(@"player %@ retain, retain count is now %d", name, [self retainCount]+1);
	[super retain];
	// cannot log here, causes crash
}
 
- (void) release
{
	NSLog(@"player %@ release, retain count is now %d", name, [self retainCount]-1);
	[super release];
	// cannot log here, causes crash
}
 
- (void) dealloc
{
	NSLog(@"player %@ dealloc", name);
	[name release];
	[super dealloc];
}

As you can see it is easy to override retain, release and dealloc and inject an NSLog to output the current retainCount. But don’t leave those log statements in the final product! NSLog are executed even if you strip all other debug info via a release build and I have seen them dramatically decrease your app’s performance.

In my case today I had forgotten the release after the addObject and thus the Instruments tool showed a couple of bytes leak that I spent an hour looking for in the wrong place.