free iphone app version from the same xcode project

January 27, 2009

There are more than 15,000 apps in the app store and hundreds are added daily. You need a way to show off your awesome creation to the millions of potential buyers.

One of the most effective marketing tools for small fish is releasing a free version of the app. To do that, you could copy the whole project, but then you would be duplicating code, maintaining it in two places and what if there’s an update?

In this post I’ll show you a better way: building two versions of your app from the same xcode project using targets and some other magic. Source code for the sample project is available here: twoForOne.

I’ve built a very simple app, it displays an image in UIImageView and has two buttons. Looks like this:

two for one red app

two for one red app

This is the pay-for version. The free version will have a different image (blue) and the expensive button will be hidden. This will demonstrate how to use a different set of resources (red image vs blue image) and how to modify code (hide button) at compile time based on which target is active.

First we’ll add a new build phase to the target. This will allow us to modify the background and icon resources later. It’s easier to add it now because we only need to do it for one target (it will be automatically duplicated with the target.) Right click on the target and select Add / New Build Phase / New Run Script Build Phase (you can click on the screen shot to see it better).

add build phase

add build phase

Type ./targetSetup in the script window. targetSetup is the script that does the work; we’ll create it later.

run script

run script

Take the Run Script build phase and move it to the top of the stack. We want this to run before any other build phase.

run scrip build phase order

run scrip build phase order

Now duplicate the target and name the new target twoForOneFree (right click on the target and select duplicate). (If you don’t have a right button on your mouse, go and get one. Those stupid soap bar mice are so not cool anymore… were they ever?):

duplicate target

duplicate target

Notice that xcode made a duplicate of the Info.plist file:

rename target

rename target

Info copy.plist is not a very nice name, so rename it to InfoFree.plist. You also have to tell the target to use the renamed plist, so open the info window for the twoForOneFree target (right click on the target and choose get info- you see how much nicer it is that you have a right mouse button?), make sure that you are looking at All Configurations and All Settings and type info into the search box. Change the Info.plist file setting to point at InfoFree.plist:

change info plist setting

change info plist setting

In the same info window change the product name to twoForOneFree (this will be the name of the .app and by default this is what the iphone will display under the icon.) Type product name in the search box to find the setting quickly:

change product name

change product name

The compiler doesn’t know which target is active, so we’ll define a directive that will be easy to use in code to create version specific modifications (this is what we’ll use to hide the expensive button). Still in the same window, search for other c. If you find a key called Other C Flags, you are in luck: set it to -DTWOFORONEFREE and skip the next paragraph.

If not, you need to create one. At the bottom of the window click the settings (gears) button and select Add User-Defined Setting and type OTHER_CFLAGS for the key and -DTWOFORONEFREE for the value:

add other c flag

add other c flag

This instructs the compiler to define the TWOFORONEFREE macro for this target.

Look at lines 18-20 intwoForOneAppDelegate.m. We use an #ifdef directive to hide the expensive button if this macro is defined:

#ifdef TWOFORONEFREE
[expensiveButton setHidden:YES];
#endif

Obviously you;ll need to do more than hiding a button. You can use this same approach to do other modifications to the free version. You can also define a TWOFORONE macro on the twoForOne target for more flexibility (you can use that macro to add features that the free version doesn’t have.)

Now we’ll look at modifying resources. We want the free version to have a different bacground and also a different icon. I created two versions of the background, you can see them in the Images folder:

backgrounds

backgrounds

The UIImageView uses bg.png. The other two files, bg_red.png and bg_blue.png are not added to the xcode project (you can add them, but it’s not necessary). I want to copy bg_red.png to bg.png when building the expensive version and bg_blue.png to bg.png for the free build.

Similarly, there is a Icon_red.png and Icon_blue.png at the project level, one of those will be copied to Icon.png to get the correct icon.

We already added the run script build phase that calls the targetSetup script. Now let’s create s shell script. Right click on Resources (so much milage out of that new right mouse button!) and select Add / New File. From the dialog select the Other category (all the way at the bottom) and then the Shell Script type:

new script

new script

In the next dialog enter targetSetup for the name and uncheck both targets (we don’t want this script to be copied into any of the targets at build time):

targetSetup name

targetSetup name

The script itself is very simple. It copies the correct bg file and Icon file based on the selected target (the build process conveniently sets an environment variable with the target name).

#!/bin/tcsh -f

echo .targetSetup: $TARGET_NAME

if ($TARGET_NAME == “twoForOne”) then
cp Icon_red.png Icon.png
cp Images/bg_red.png Images/bg.png
endif

if ($TARGET_NAME == “twoForOneFree”) then
cp Icon_blue.png Icon.png
cp Images/bg_blue.png Images/bg.png
endif

#

That’s it! Just set the desired target and you can build the expensive or free version from the same project:

set target

set target

Here are some screenshots. You can see that even the icons are different (as expected):

two for one iphone app screenshots

two for one iphone app screenshots

You can download the twoForOne xcode project here. As always, feel free to contact me.

25 Responses to “free iphone app version from the same xcode project”

  1. Hey,

    Nice tutorial! I am getting an error when I try to build and run the free version in my setup:

    :1:12: error: missing ‘(’ after predicate

    Do you have any insight?

    Thanks,
    Matt

  2. I figured it out… For others, you have to have “-D” before your variable. I had -AMBI… and to fix it I had to do -DAMBI…

    On another note, the target copying had issues on my version of xcode. After I set everything up, I have private frameworks which were not being found. After looking at both the original target info and the new one, I found that the entries for “FRAMEWORK SEARCH PATHS” and “LIBRARY SEARCH PATHS” had quotes that were autoescaped. After removing the leading \, it compiled and everything works great!

  3. @Matt Thanks for pointing this out. I probably should’ve been more clear about this. The format for defining a directive as a compiler option is: -D{MY_DIRECTIVE}, so to define TWOFORONE you need to type -DTWOFORONE in the other c options setting.

  4. I m getting this error, can you tell me whats wrong i m doing here? I also copied your libraries in to my project as well.

    >>
    library not found for -lMMTrackingSimulator
    <<

  5. Adeem-

    Not sure why you are getting that error. I don’t think it’s coming from the sample code, it works fine on the SDK. Make sure you have the latest version.

  6. Hi Lajos,
    Just to correct myself again, this was not an error from your code it was an issues with linking one of my library. IF someone got somthing like this, then he should delete the library reference and add it again :)

    Thanks once again great tutorial (y)

  7. btw i have one more issues here, i made 3 different version from same code with different icons. When i started any version, the previous version is updated. I thought it should create another icon for new application? I am not using your script as its not required on my side. Can you please guide me though?

    thanks once again.

  8. its work great :) thax lot for your wonderful blog :)

  9. This was a great tutorial. I had been messing around with the custom script stuff trying really complicated solutions like trying to copy the different files to the intermediate directory then trying to get XCode to add them to the package. Very succinct and useful.

  10. I also have the same problem as Adeem. How did you resolve it? Each time I build, the last app is overwritten. Each target has different product names and icons.

  11. You should get two different products, look at the example project.

    The product name setting defines the output .app name. Make sure that you don’t have multiple values set (debug, release and distribution should all match) and that you are not setting the product name at the project level

  12. @Turbolag,Adeem

    When you are shipping more than one app in a group,
    you should use a wildcard distribution profile.

    To make one, put a star in the right text box when
    you create the profile in the program portal.

    The bundle identifier in the info plist files should look
    like this:

    com.yourcompany.${PRODUCT_NAME:identifier}

    (of course you can replace yourcompany with whatever,
    but you can also keep it the default value. I always keep
    the default. Less typing is better.)

  13. To create a second target can be even easier.

    You don’t need to create a script at all to copy resources. Remember that all included resources in your project will be copied to the bundle no matter where they are located.

    So for each target you create, add a new folder to house just that target’s resources, like images and such. Then “Get Info” on the folder and select Target and only check the target the resource folder belongs to.

    This way you can have different Default.png splash screens and don’t have to think about adding it to a script.

  14. Hi Lajos,

    I there a way to do conditional inclusion of frameworks too? Something like MessageUI.framework inclusion for 3.0 and not for 2.2.1? Maybe I’m missing out something very basic in the settings?

    Thanks

  15. Hello,
    thanks for sharing this (I’m totally new in XCode & ObjC)
    Jürgen

  16. Very nice tutorial, best one I’ve found so far on the subject. I did have one problem though, in the shell script, I had to remove the quotes around the name of the target.

    if ($TARGET_NAME == twoForOne) then

    instead of

    if ($TARGET_NAME == “twoForOne”) then

    With the quotes, my script never executed anything inside either ‘if’ statement. I added echo’s to test it even. Once I remove the quotes, its working fine. I’m a newbie to shell scripting so I don’t know if this is expected or not.

    Thanks again for a great tutorial!

  17. Thanks for such a wonderful tutorial. I’m getting an error when I try to compile. ./targetSetup: bad interpreter: No such file or directory. What can be causing this error?

  18. Nevermind! The problem was solved by specifying the correct file name: targetSetup.sh

  19. [...] http://www.codza.com/free-iphone-app-version-from-the-same-xcode-project « iPhone Silent Mode Detection [...]

  20. Like Sophtware replied, you don’t have have a script in this case. Uncheck all the files you don’t want, like the full version icon.png. Right click on Groups & Files (right under the ‘Overview’ on the left hand side underneath your configuration). Make sure ‘Target membership’ is selected. Then you can check/uncheck all the files for your current target.
    And for the icon, just add the key ‘Icon file’ into your plist file. It doesn’t have to be necessarily named ‘icon.png’.

  21. thank you sooo much for this tutorial !!!

  22. How can we make sure one application updates other? In the example shown you can install both application on the device. I want to release a lite and a paid version on app store and when I update Lite with a paid version, I want to migrate data from Lite to Paid.

  23. @Idea: this tutorial is for creating two separate apps. You can’t upgrade from lite to paid. If that’s what you are looking for, create an in app purchase to upgrade to full version.

  24. Thanks for the detailed instructions!

    I ended up calling a python script instead and it is working great. For anyone interested, I learned that you can access the same environmental variables in python and call shell commands with the following syntax:

    import os

    target = os.environ['TARGET_NAME']

    os.system(’echo TargetName: ” + target)

    # ………

  25. Awesome tutorial. I can’t imagine how long it would have taken to figure this out…

    FWIW, in step 2-ish, I had to specify ./targetSetup.sh rather than ./targetSetup (i.e., include the “.sh”) or I’d get a build error.

    Thanks! Thanks! Thanks

Leave a Reply