Showing posts with label COCOA. Show all posts
Showing posts with label COCOA. Show all posts

A Simple Localization Example for the iPhone

Today we are going to take a very simple application (really just a couple of strings), and show how to translate it into German. This will cover both translating strings stored inside XIB files and translating strings accessed in code. First, let's take a look at the application in English:
The second line of text, as you might expect from reading it, is set in the XIB file that represents that view (the "SOTC_LocalizationExampleViewController.xib"). The third line of text is just a line of text set in code. The first line of text is a bit more interesting - it is also set in code, but it displays the currently active region - the "Region Format" set on the Settings->General->International page:
This settings page is also where you can set the current language of the iPhone. One thing to note - I recommend changing the region before you change the language. Changing the language will kick you back out of the settings application to the main screen as the iPhone resets to the new language setting.
Ok, to start off, we are going to localize the XIB file. Bring up the "Info" screen for the XIB file to localize (in this case "SOTC_LocalizationExampleViewController.xib"):
At the bottom left, you will see a button with the name "Make File Localizable". When you click it, a new Xcode sub-project will be created, named "English.lproj":
This project manifests itself in Xcode as a sub item of the "SOTC_LocalizationExampleViewController.xib":
Double clicking that "English" entry won't get you anything, since it is essentially the same XIB as the regular entry. The fun comes in when you start adding translations. If you go back to the XIB info window, you will see that the button "Add Localization..." is now enabled. Click it and add a localization - in my case I added German, or "de".
You might be tempted to name your localization using the full language name - don't do it! You should use the two letter language ISO codes. It is currently a known bug in Xcode that the English translation gets named "English" when you start the localization process - it really should be named "en". While the project will still work with English as "English" instead of "en", you don't want to compound the problem by misnaming the other translations.
Ok, once you add the "de" translation (or whatever language you decided to translate to), you will see that there is a new sub-entry under "SOTC_LocalizationExampleViewController.xib", named whatever you named your translation. If you open that up, you can just go ahead and change strings to your heart's content. In this case, I translated the middle string using Google Translate (so forgive to horrible German translation - I really have no idea what it actually says :P).
We don't really care about the first and third labels, since we are going to be setting them in code in a little bit - so translating them here wouldn't be useful (the values would just get overridden).
And that is it for translating a XIB file! Pretty nice, right? You get in place translation, and you don't have to worry about it at all from the original XIB.
Translating strings in code is slightly more painful, and probably more along the lines of what you have seen in localizing apps in other frameworks. First, let's take a look at the code behind the app above:
//
//  SOTC_LocalizationExampleViewController.h
//  SOTC-LocalizationExample


#import

@interface SOTC_LocalizationExampleViewController : UIViewController {
        IBOutlet UILabel *localeNameLabel;
        IBOutlet UILabel *labelToSetInCode;
}

@end
A very simple header file, with just two IBOutlets. The first one (localeNameLabel) is hooked to the first label where we display the locale, and the second (labelToSetInCode) is hooked to the third label.
//
//  SOTC_LocalizationExampleViewController.m
//  SOTC-LocalizationExample

#import "SOTC_LocalizationExampleViewController.h"

@implementation SOTC_LocalizationExampleViewController

- (void)viewDidLoad {

    labelToSetInCode.text = NSLocalizedString(@"This is my default value.",
        @"This is a comment about my default value string.");

    NSLocale* curentLocale = [NSLocale currentLocale];

    localeNameLabel.text = [NSString stringWithFormat:
        NSLocalizedString(@"The current locale is: %@",
                          @"String used to display the current locale."),
        [curentLocale displayNameForKey:NSLocaleIdentifier
                                  value:[curentLocale localeIdentifier]]];

    [super viewDidLoad];
}

@end
The simpler example is setting the text of that third label (labelToSetInCode), so we will start with that first. Here we use a function that you have probably never seen before, NSLocalizedString. This function returns a localized version of a string based on the localized resources in the app.
The first argument serves two purposes - it is both the default value and the lookup key for the localized versions of the string. The second argument doesn't serve any purpose when the app is running, but it is used as an informative comment about the string and its use for the purposes of translation.
For the other label, we need to do a bit more work to get the text. First off, we have to get the current locale, using theNSLocale class. We are able to pull the display name for the current locale out using the methoddisplayNameForKey:value:. Then we piece it together with a string pulled out of NSLocalizedString using the NSString method stringWithFormat:.
Ok, now for the odd part of this whole process - we have to leave Xcode and run a command line app calledgenstrings. This app comes with Xcode, so you don't have to worry about not having it - but I still find it odd that it can't be run through Xcode itself. We have to run it against the ".m" files - in this case, all we really care about is "SOTC_LocalizationExampleViewController.m":
mkuehl@Shrike SOTC-LocalizationExample $ genstrings Classes/*.m
Running this command creates a file called "Localizable.strings":
If you open it up, the contents might look quite familiar:
/* String used to display the current locale. */
"The current locale is: %@" = "The current locale is: %@";

/* This is a comment about my default value string. */
"This is my default value." = "This is my default value.";
The genstrings command essentially extracted the information for every NSLocalizedString call. In this case, the key is equal to the value, but that is because we haven't translated anything yet.
To do so, we first have to add this file to the Xcode project:
This file is created by genstrings in UTF-16 text encoding, so make sure that you tell Xcode that in the Add File dialog.

Using SQLite on the iPhone

Most of the time, your iPhone application data storage needs will be taken care of by simple object serialization and flat file storage. However, there comes a point when that type of storage just won't work anymore - when you start storing thousands or tens of thousands of objects. Apple recognized that fact, and so gave app developers the ability to use SQLite inside their applications.
What is SQLite? Well SQLlite is a small but powerful database engine that takes virtually no configuration to set up and lives in a single file. It is extremely handy for quick and easy databases. We have actually talked about SQLite here before at Switch On The Code a couple of times. For a thorough understanding of how to interact with SQLite, I would suggest reading through the Writing a .NET Wrapper for SQLite tutorial - because we won't be going too deep in to SQLite today in this tutorial.
Ok, so first things first - we need to get an iPhone Xcode project set up to use SQLite. This isn't terribly hard - we just need to add a library to our project. So right click on the Framworks folder in Xcode and choose "Add Existing File". This is because while what we are adding is a library, it isn't a "Framework" in the standard frameworks folder. You will want to navigate to the following convoluted path:/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSilumator3.0.sdk/usr/liband choose the file libsqlite3.dylib:

How To Integrate iAd into Your iPhone App

With the iOs SDK 4 now public and the advent of iAds just a few days away, I thought we’d celebrate with a tutorial on how to integrate iAd into your iPhone app!
In this tutorial, not only will we show you how to get started with iAd, but we’ll also show you how to deal with some complex issues you may run into along the way such as:
  • Supporting both Portrait and Landscape ads in the same app
  • Integrating into a Universal app
  • Maintaining backwards compatibility with iOs 3.0
  • What to do if you are using a UITableViewController!
We’re actually going to start with where we left off in the How To Port an iPhone Application to the iPad and use the universal app we developed in that tutorial in the starting point.
So grab a copy if you haven’t already, and let’s get to adding some iAds!

Base SDK vs. Deployment Target

The first step to use iAd is to make sure our project has the right Base SDK and iPhone OS Deployment Target selected.
For those of you confused about the difference between the Base SDK and Deployment Target (like I was for quite some time!), here’s what they mean:
  • The Base SDK is the version of the SDK you are linking against. Your app can use any classes or functions available in the version of the SDK you choose here – as long as they are available on the actual device the code runs on.
  • The Deployment Target is the earliest possible version of the SDK your code can run on. This can be an earlier version than the Base SDK – in fact you often want to set it to be earlier to ensure that as many different versions of the OS can run your code as possible!
The tricky bit is what happens when you want to use a class, function, or framework available in one version of the OS if it’s available, but still work on the old version of the OS if it isn’t. We already did some of this in How To Port an iPhone Application to the iPad, and we’ll do even more in this tutorial!
For this tutorial, we want to set things up so that our code can use stuff available in iOS 4.0 (such as iAd), but still run on as many devices as reasonable (3.0+).
So first let’s set iOs 4.0 as the base SDK. To do this, expand the Targets directory, right click on PortMe, and choose “Get Info”. Click the Build tab, make sure “All Configurations” is selected, navigate to Architectures\Base SDK, and change the value to iPhone Device 4.0.
Screenshot of Setting Base SDK
Then, let’s set iPhone OS 3.0 as the iPhone OS Deployment Target. To do this, still in the Target Build tab, navigate to Deployment\iPhone OS Deployment Target, and change the value to iPhone OS 3.0.
Screenshot of setting Deployment Target
You should now be able to compile and run your app (use the iPhone simulator), and try it out on an iPhone 4 simulator. Once you run your code, in the simulator choose Hardware\Device\iPhone OS 4 and re-run your app. The simulator window will look a little different, and say iPhone 4 in the toolbar, so you’ll know it’s working!
Screenshot of PortMe on iOS4

Linking Against the iAd Framework

The next thing we need to do is add the iAd framework to the project. You can do this by right clicking on Frameworks, choosing “Add\Existing Frameworks…”, and choosing “iAd.framework”.
The problem is, if that is all we do our code will break on older devices that don’t have the iAd framework.
You can verify this by trying to run your code in the iPad Simulator 3.2 – boom! The app will crash on startup and you’ll see the following error log:
dyld: Library not loaded: /System/Library/Frameworks/iAd.framework/iAd
  Referenced from: /Users/rwenderlich/Library/Application Support/
    iPhone Simulator/3.2/Applications/
    3ACB1BDA-26F6-43A6-84EA-9FB637B8CDCD/PortMe.app/PortMe
  Reason: image not found
To fix this, we need to weak link against the iAd framework. Expand the Targets directory, right click on PortMe, and choose “Get Info”. Click the Build tab, make sure “All Configurations” is selected, and navigate to Linking\Other Linker Flags. Double click on that entry, click the “+” button, and type “-weak_framework iAd”.
Click OK, and then try your app on the iPad simulator again and viola – it should work!

Preparing our XIB

In this tutorial, we’re going to integrate iAd into both the PortMeGameListController and the PortMeGameDetailsController. However, the integration is a bit easier in the PortMeGameDetailsController because it is a subclass of UIViewController, so we’re going to start there first.
Open up PortMeGameDetailsController.xib. You’ll see that all of the controls are children of a single view:
Details View Controller Settings - Before
What we’re going to need to do with iAd is scroll an ad view onto the screen when an ad is available, and shrink the rest of the content to fill the remaining space. As currently designed, this isn’t that easy because all of the controls are direct children of the root view. But there’s an easy way to fix it – we’ll simply move the controls into a subview instead!
The easiest way to do this is to drag another view from the library into the XIB, and change its size to be the same as the existing view’s size (320×416). Then drag the existing view as a subview of the new view. When you’re done, it should look like the following:
Details View Controller Settings - After
Then, control-drag from the File’s Owner to the new view (which is now the root view) to connect it to the view outlet. Save your XIB, and run the project and verify that everything still works OK with the details view (in particularly that orientation resizing works correctly). If all works well, we’re one step closer to integrating iAd!

Simple iAd Integration

Ok, now let’s get to the fun part – integrating iAd!
First, make the following changes to PortMeGameDetailsController:
// In the import section
#import "iAd/ADBannerView.h"
 
// Modify the PortMeGameDetailsController interface
@interface PortMeGameDetailsController : UIViewController 
     {
 
// Inside the PortMeGameDetailsController interface
UIView *_contentView;
id _adBannerView;
BOOL _adBannerViewIsVisible;
 
// After the interface
@property (nonatomic, retain) IBOutlet UIView *contentView;
@property (nonatomic, retain) id adBannerView;
@property (nonatomic) BOOL adBannerViewIsVisible;
We first include the iAd headers and mark the view controller as implementing the ADBannerViewDelegate. This way, we can receive events as ads become available or not.
We then declare a property to keep track of the content view that contains all of the controls (basically the inner UIView). We also declare a variable to keep track of our iAd banner view, and whether or not it’s currently visible.
Note that we declare the iAd banner view as an id variable rather than as a ADBannerView. This is because we want to ensure backwards compatibility all the way to OS 3.0, and the ADBannerView class is only available on 4.0+, so we need to weak link against it.
Before we forget, let’s hook up our content view to the new outlet we just made. Make sure you save PortMeGameDetailsController.h, go back to PortMeGameDetailsController.xib, control-drag from the File’s Owner to the inner (second) UIView, and connect it to the contentView outlet.
Then switch over to PortMeGameDetailsController.m and make the following changes:
// In the synthesize section
@synthesize contentView = _contentView;
@synthesize adBannerView = _adBannerView;
@synthesize adBannerViewIsVisible = _adBannerViewIsVisible;
 
// In the dealloc section
self.contentView = nil;
self.adBannerView = nil;
Next, we’re going to add the meat of the code. But there’s a lot of it – so let’s break it down into 6 steps.
1) Add helper functions to get height of iAd banner
- (int)getBannerHeight:(UIDeviceOrientation)orientation {
    if (UIInterfaceOrientationIsLandscape(orientation)) {
        return 32;
    } else {
        return 50;
    }
}
 
- (int)getBannerHeight {
    return [self getBannerHeight:[UIDevice currentDevice].orientation];
}
There are several places in the rest of the code where we’re going to want to know how large the banner view should be given a particular orientation. Currently iAds have two possible sizes: 320×50 for landscape, or 480×32 for portrait. So we simply retrieve the proper height based on the passed in orientation.
2) Add helper function to create the iAd view
- (void)createAdBannerView {
    Class classAdBannerView = NSClassFromString(@"ADBannerView");
    if (classAdBannerView != nil) {
        self.adBannerView = [[[classAdBannerView alloc] 
            initWithFrame:CGRectZero] autorelease];
        [_adBannerView setRequiredContentSizeIdentifiers:[NSSet setWithObjects: 
            ADBannerContentSizeIdentifier320x50, 
            ADBannerContentSizeIdentifier480x32, nil]];
        if (UIInterfaceOrientationIsLandscape([UIDevice currentDevice].orientation)) {
            [_adBannerView setCurrentContentSizeIdentifier:
                ADBannerContentSizeIdentifier480x32];
        } else {
            [_adBannerView setCurrentContentSizeIdentifier:
                ADBannerContentSizeIdentifier320x50];            
        }
        [_adBannerView setFrame:CGRectOffset([_adBannerView frame], 0, 
                -[self getBannerHeight])];
        [_adBannerView setDelegate:self];
 
        [self.view addSubview:_adBannerView];        
    }
}

Debugging memory based crashes on iPhone

On my Atalasoft blog, I wrote some tips for debugging unmanaged crashes in .NET that I figured out by debugging our .NET Imaging SDK. The idea is the same for iPhone -- namely: 

[...] crash as early as possible. It's no fun to figure out a crash bug once the culprit function has already returned. You really want the root cause somewhere on the call stack when it's detected.
In Xcode, it's actually really easy to get information about how you might be managing memory incorrectly.
Tip 1: Set Deallocated objects to Zombies

Go to Project->Edit Active Executable, go to the Arguments tab and in the environment variables section, add

NSAutoreleaseFreedObjectCheckEnabled
NSZombieEnabled
NSDebugEnabled

And set each to YES. You can leave them there unchecked, but if you check them, then your application will now do some extra checking on autorelease and release and give you a good stack trace when you have done it wrong. A common problem is to think you need to call release when the object is already set to autorelease (see
 yesterday's post on what the rules are for that). 
Tip 2: Enable Guard Malloc