BuySellAds.com

My book Barcodes with iOS 7 is nearing completion. Buy it now to get early access!
Our DNA is written in Objective-C
Jump

Category Archive for ‘Recipes’ rss

App Information String Tokens

For my current project DTAboutViewController I need to be able to specify tokens in my strings that would be replaced by information about the app at runtime. Of specific interest are CFBundleDisplayName and CFBundleVersion which are both in info.plist. Usually you could hard-code these into your app strings or maybe do a global #define, but for my component I wanted to have the most flexibility with the least amount of work in subsequent project that would use that.

So I put together a category extension for NSString which also shows off how to use NSScanner to find tokens and replace them with values from your info dictionary.

NSString+Helpers.h

#import 
 
@interface NSString (Helpers)
 
- (NSString *) stringBySubstitutingInfoTokens;
 
@end

NSString+Helpers.m

#import "NSString+Helpers.h"
 
@implementation NSString (Helpers)
 
- (NSString *) stringBySubstitutingInfoTokens
{
	NSMutableString *tmpString = [NSMutableString stringWithString:self];
	NSScanner *scanner = [NSScanner scannerWithString:self];
 
	NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary];
 
 
	while (![scanner isAtEnd])
	{
		if ([scanner scanString:@"$" intoString:nil])
		{
			NSString *tokenName;
 
			if ([scanner scanCharactersFromSet:[NSCharacterSet alphanumericCharacterSet] intoString:&tokenName])
			{
				id value = [infoDict objectForKey:tokenName];
 
				if (value && [value isKindOfClass:[NSString class]])
				{
					[tmpString replaceOccurrencesOfString:[@"$" stringByAppendingString:tokenName] withString:value options:NSLiteralSearch range:NSMakeRange(0, [tmpString length])];
				}
			}
		}
 
		[scanner scanUpToString:@"$" intoString:nil];
	}
 
	return [NSString stringWithString:tmpString];
}
@end

The code works by skipping all characters until it encounters a $, which will be our token identifier. From this position the name of the token is until the first non-alphanumberic character, like a space or period. Then it tries to get a value from the bundle’s info dictionary. If it succeeds the token is replaced in a temporary string, if not it is left unchanged.

Note how I am passing nil into scan functions if I am only interested in skipping characters. If I am interested in the scanned result, then I am passing the address of a pointer to an NSString, that’s the reason for the &tokenName. The scan method creates an autoreleased copy of the scanned string and puts the pointer to this string into the parameter the address of which you have passed. That’s unusual in objective-C and seen more often in pure C code, so be aware of that.

Now with this code I gain a very useful way of displaying copyright information as the section footer of my settings table.

- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
{
	return [@"© 2010 Drobnik.com. $CFBundleDisplayName $CFBundleVersion" stringBySubstitutingInfoTokens];
}

This will look like this, the app’s bundle display name is “About” and the version string is “1.0″.

With this method it’s even thinkable to put your own information into info.plist. Like for example the ID of your app which you would use for a direct link to the review page. Some people complain that Cocoa does not have regular expression support, but actually if you know how to use NSScanner properly you can do even more with it than trying to piece together working regexps.

Making a "Follow us on Twitter" button

I am working on the ultimate “About Page” component at the moment. And of course this won’t be complete without a button to follow the developer on Twitter. Tapbots is one company that has the best role model for modeling this. They have this little button here at the bottom of their about page:

There is something amazing that happened when I tapped on this “Follow us on Twitter” button: it opened up the tapbots user right in Tweetie 2! I was astonished at first, but then it dawned on me that on the iPhone you can have your app respond to a custom URL scheme and obviously some Twitter clients support going to the user’s profile page. I have only Tweetie 2 installed and incidently this supports just that.

Read more

Understanding UIFont

Have you ever really TRULY looked at the documentation of UIFont?

I had to, because I was looking for some metrics information that I could use to custom draw UILabels. And if you want to be independent of what font is set, then you have to get certain metrics, but the Apple SDK documentation of UIFont leaves a bit to desire.

When I asked which characters would be good representatives of a font, I was sent a link to this blog “celebrating the beauty of the ampersand”. Fine, an ampersand will be there as well…

Read more

Drawing Rounded Rectangles

Once you get deeper into coding iPhone apps you find that CoreGraphics starts to become a real friend. A friend who lacks in certain areas, because you will still have to piece together some shapes with the shape drawing functions that CG provides.

For sake of reusability you want to put the creation of distinct shapes into their own respective methods. You could make those into C functions like their CG brethren, but for our purposes objC-methods suffice.

This method creates a CGPath for a rounded rect inside the given rectangle with the given radius. We are alternating adding a straight line segment and then a corner by means of AddArcToPoint.

- (CGPathRef) newPathForRoundedRect:(CGRect)rect radius:(CGFloat)radius
{
	CGMutablePathRef retPath = CGPathCreateMutable();
 
	CGRect innerRect = CGRectInset(rect, radius, radius);
 
	CGFloat inside_right = innerRect.origin.x + innerRect.size.width;
	CGFloat outside_right = rect.origin.x + rect.size.width;
	CGFloat inside_bottom = innerRect.origin.y + innerRect.size.height;
	CGFloat outside_bottom = rect.origin.y + rect.size.height;
 
	CGFloat inside_top = innerRect.origin.y;
	CGFloat outside_top = rect.origin.y;
	CGFloat outside_left = rect.origin.x;
 
	CGPathMoveToPoint(retPath, NULL, innerRect.origin.x, outside_top);
 
	CGPathAddLineToPoint(retPath, NULL, inside_right, outside_top);
	CGPathAddArcToPoint(retPath, NULL, outside_right, outside_top, outside_right, inside_top, radius);
	CGPathAddLineToPoint(retPath, NULL, outside_right, inside_bottom);
	CGPathAddArcToPoint(retPath, NULL,  outside_right, outside_bottom, inside_right, outside_bottom, radius);
 
	CGPathAddLineToPoint(retPath, NULL, innerRect.origin.x, outside_bottom);
	CGPathAddArcToPoint(retPath, NULL,  outside_left, outside_bottom, outside_left, inside_bottom, radius);
	CGPathAddLineToPoint(retPath, NULL, outside_left, inside_top);
	CGPathAddArcToPoint(retPath, NULL,  outside_left, outside_top, innerRect.origin.x, outside_top, radius);
 
	CGPathCloseSubpath(retPath);
 
	return retPath;
}

The method has to be called new-something so that Build&Analyze does not tell you about a memory leak. Having the method name begin with new tells the static analyzer that this method is supposed to return something that the caller has to take care of releasing.

Now, if we want to use this method, then we can do so in any view’s drawRect:

- (void) drawRect:(CGRect)rect
{
	CGContextRef ctx = UIGraphicsGetCurrentContext();
 
	CGRect frame = self.bounds;
 
	CGPathRef roundedRectPath = [self newPathForRoundedRect:frame radius:5];
 
	[[UIColor blueColor] set];
 
	CGContextAddPath(ctx, roundedRectPath);
	CGContextFillPath(ctx);
 
	CGPathRelease(roundedRectPath);
}

Having the rounded rectangle shape as a path object allows us to reuse it several times. You could for example draw a gradient inside after having used the shape for clipping, then draw a border and also add a shadow. All from the same shape. At the end we just release it to clean up.

UILabels with Neon-Effect

For a customer project I needed to have a Neon glow on some text. Being the geek that I am I would not want to settle for simple creating the effect in Photoshop since I wanted to be able to smoothly scale the text. Custom drawing comes to mind, but where?

One might be tempted to first consider create a new UIView for this effect, but then you’d have to also add all those properties that UILabel has on top of what it inherits from UIView. Second idea was to create a UILabel category, but my experiments have shown if I override a standard method like drawRect in my category then this overrides it for all UILabels.

(Background Photo “Seattle by Night” by Alan Bauer)

So the final – and successful – decision was to subclass UILabel: DTGlowingLabel was born.

Read more

Array Self-Sorting

The current Dr. Touch Part that I am working on for my store is about quickly clustering POIs if they would be too close together on a MKMapView. For this purpose I need to calculate distances between all annotation pins and add to this the distances to all newly found clusters. Currently this involves resorting the whole list of distances multiple times. So I put my thinking hat on and built this category extension for NSMutableArray to add numbers of objects in the correct place according to the specified sort order.

To cut down on search time for the insertion point I am using a “divide an conquer” approach. Split the search range in half and check if the value at this index is bigger or smaller than the one I am trying to insert. Continue to divide until the length of my search range is 0. At that point I have found the appropriate place for the insertion.

This approach means that for 1000 values/objects in the array only 10 comparisons are necessary. This is most likely way less than if you are constantly creating sorted copies of the array with one of the three standard sorting methods.

Read more

Easier Version Checking

Sometimes you may need to modify your app’s behavior depending on which OS version it’s running on. As of XCode 3.2 you can choose a different SDK to compile against than you choose for the deployment target. This allows to make an app that runs as low as 2.0, but uses all the bug fixes in the SDKs. And it forces the compiler to make dynamic links to 3.x frameworks. So you can check at run time if such a framework is available and if not present the feature that depends on it.

When working on DTAugmentedRealityController I needed to limit it’s use on devices with a magnetometer and also which run a version of iPhoneOS that’s greater than or equal to 3.1 because this is the version when Apple introduced the capability of overlaying your own view on top of an UIImagePickerController. So I needed an elegant method, I would not settle for comparing strings. That’s why I came up with this extension to UIDevice.

Read more

Adding Last Build Date & Time

If you need or want the date and/or time the last time your app was built then there are two handy macros you can use. Consider the following example:

char *date = __DATE__;  // e.g. 'Dec 15 2009'
char *time = __TIME__;  // e.g. '15:25:56'
 
NSLog(@"Build date: %s", date);
NSLog(@"Build time: %s", time);

I tested it with GCC 4.0, GCC 4.2 and even the new LLVM GCC 4.2. It worked fine with all three compilers.

Those are precompiler macros which work by getting the precompiler to replace them with the current date and time when the file in which they are located in gets built. Bear in mind that generally building is incremental and therefore these will only get updated if there really IS a new build happening. That’s a drawback if you forget it, but you can or should always do a build – clean before you build a release or distribution version to make certain all got updated.

Since those macros date back to C times they get replaced with C-style strings. That’s a pointer to a char array with a binary zero at the end to terminate the string. To convert them to obj-C NSStrings is simple by means of the %s formatter or by using one of the more complicated initializers of NSString.

char *date = __DATE__;
char *time = __TIME__;
 
NSString *myDate = [NSString stringWithCString:date encoding:NSASCIIStringEncoding];
NSString *myTime = [NSString stringWithFormat:@"%s", time];

Finally if you are really so much “pro” that the build time of your app matters, then you will probably also ask if there is a way to force building of certain files to forego the incremental building. Sure you can. All you need to do is to add an extra script to your target to set the last modified time of the file using these macros to the future.

touch -t 2012310000 "${PROJECT_DIR}/Classes/CalendarAppDelegate.m"

Touching Script

It’s not enough to simply set the modified time once because then the next modification sets it back to the current date. Thus the need for this simple unobtrusive script, in this example I am setting the modified time for CalendarAppDelegate.m to Dec 31st 2020 which is sufficiently far away so that this blog article will work for the next 11 years. :-)

How to make a Pull-To-Reload TableView just like Tweetie 2

When I started on Twitter, I tried out a few Twitter clients both on Mac and iPhone until I quickly settled on Tweetie. When Loren Brichter made the bold move to sell Tweetie 2 as a seperate app I also purchased it because I am convinced this guy means quality and Tweetie 2 is on the first page of my springboard.

One thing that’s cool about Tweetie 2 is the fresh paradigm to refreshing the contents of a table view. Up until now we had been looking for space to mount a reload button on, sometimes having to resort to adding an extra tool bar for just one view so that you can have enough space. Now if you have a tableview that it sorted reverse chronologically, then you have a natural urge to make new items appear at the top by pulling down the table with extra force.

Loren recognized this need and innovated the Pull-To-Reload paradigm. If you want to refresh a tableview in Tweetie 2 then you simply pull down the table far enough for an additional cell to appear at the top with the instruction “Pull down to refresh”. If you do, then at a certain point the arrow rotates and the text changes to “Release to refresh”. All accompanied by two distinct wooshing sounds and a pop once the reloading action has ceased. The Intuitiveness of this paradigm is so compelling in fact that people who use Tweetie 2 start to try to refresh ALL tableviews like this.

Might be a good case to make this the standard way from now on because it feels more logical and natural than to tap on a small button with a circular arrow on it. A user of MyAppSales requested that I add this mechanism for reloading reviews of individual apps. At first I thought this to be advanced magic, probably using forbidden techniques. But after a bit of research and lots of hints coming from my Twitter friends (thanks Thomas and Fabian) I figured it out. This article explains how I did it.

Read more

Getting the User's Language

Geppy Parziale wrote about this topic on his “Invasive Code” blog. The method he proposes is to be found in Apple’s documentation and looks like this:

NSUserDefaults* defs = [NSUserDefaults standardUserDefaults];
NSArray* languages = [defs objectForKey:@"AppleLanguages"];
NSString* preferredLang = [languages objectAtIndex:0];

This is a good method to retrieve the set language, BUT it has two major drawbacks when used in regular apps:

  • Changes are not visible to the app by simply changing the language in settings. It seems you also have to change the region for it to be effective.
  • This really returns the user’s set language but for most cases you instead want to know which localization is used.

What good does it get you if you know that the user has set Klingon as his iPhone language? Not much because most of the time you then still want to know which localization is the currently active one.

You know if the iPhone is set to any language that is not amongst your localizations then the auto-localization feature falls back to the app’s default language, usually English. If you add German localization and the user chooses German then this is automatically used.

I learned this the hard way when we added multiple languages to LuckyWheel. Now here’s the method that I found to be way more useful:

NSString *selectedLocale = [[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0];

If you read Apple’s page to the end you get to a box where it says:

“Although you can get the user’s preferred settings from the defaults database, it is recommended you use the CFBundleRef functions or NSBundle class instead. The associated functions and methods of those objects return the preferred language or locale that is also supported by your application.”

That’s exactly what I am saying …