How to Create Both a Paid and Lite Version of an iPhone App

Learn how to create both a paid and lite version of an iPhone app in the same project, using Xcode build settings. By Dani Arnaout.

Leave a rating/review
Save for later

There are a ton of different ways you can monetize your app, and the most straightforward way is to make it paid. Unfortunately, a large number of users are not willing to purchase an app without trying it first.

One way to solve this problem on the App Store is to offer two different versions of your app:

  • A free/lite version. These are usually the noisy kind of apps that remind you to buy the paid version to unlock all of the amazing premium features. They commonly monetized by integrating ad networks such as iAd or AdMob.
  • A paid/full version. These usually cut the ads and enable features to further enhance the usefulness of the app.

In this tutorial, you will learn how to create both a paid and lite version of an iPhone app in a single Xcode project, using Xcode target settings.

Specifically, you will create an app focused on iOS interview questions, where the lite version has ads and the paid version does not.

Note: An alternate way to accomplish this is to have a single app that is free, which includes an in-app purchase to hide ads or unlock extra features or content. This was done in the popular app Letterpress.

Which option is best for you depends on your particular app. If you decide to go down the in-app purchase route, we have a tutorial for that.

The Bad Approach: Multiple Projects

The bad approach to creating both a paid and lite version of an app is to have multiple projects:

  1. Create a free version of the app.
  2. Copy the project folder and rename it to something like “Paid Version”.
  3. Add your extra features to the paid app, and Voila! You have both a free and paid version that you can build and deploy to the App Store.

That’s nice and simple, and it will work, but pretty soon you’re going to run into some big headaches. You now have two completely separate projects to update – so if you find a bug, you need to update it in both places! That’s a hassle, it’s error prone, and it’s not good programming practice.

So how can you change your approach to managing two versions that will avoid this issue?

The Better Approach: Multiple Targets

The better approach is to have just a single project, with multiple targets instead – one for each version of your app. So what are targets, and how do they help us achieve this goal?

Apple describes targets as follows:

“A target specifies a product to build and contains the instructions for building the product from a set of files in a project or workspace. A target defines a single product; it organizes the inputs into the build system—the source files and instructions for processing those source files—required to build that product. Projects can contain one or more targets, each of which produces one product.”

In plain english, this means that targets define products that each have their own configurations and source files within a single project. This sounds like a perfect solution for creating a free and paid version of an app while avoiding multiple projects and duplicate code!

Each target has its own plist that defines values like the bundle identifier, version number and build number which allow you to build unique products within a project. The target also has settings accessible from the project editor in XCode, including Preprocessor Macros found under Build Settings:

Target Settings

For this tutorial, you’ll use Preprocessor Macros to identify the product version in your code, thus enabling logical decisions based on the version. You’ll learn how to configure the Plist and Build Settings (most importantly the Preprocessor Macros) to enable product version differences under the same codebase. You’ll also learn how to easily swap assets used by different targets – in this case icons – to further customize product versions.

With that knowledge, your new plan for multiple versions will look something like this:

  1. Create the Free version of your app using a single target.
  2. Create and configure a second target for the paid version.
  3. Tie common source and unique assets (such as icons) to the new target.
  4. Add features for the paid version to your code, wrapped in version checks.

Getting Started

To get you started, I’ve created a simple application that will serve as a cree version of an app focused on iOS interview questions. You will enhance it by adding a paid version.

Download the starter project, unzip it and open it in XCode. Run your project and you’ll see a welcome screen that has some static text and a button:

 Welcome Screen

Once the user presses the Test my skills! button, the quiz will start:

3- Quiz Screen

Play a little bit and see how well you do. Once you hit the question limit, you’ll see an alert with your score:

Game Over

I was able to answer all 5 questions easily, how about you?

Code Tour

Now that you’ve tried the app, let’s take a tour of the code.

IQViewController is the quiz view controller and contains all of the custom code in this simple project. Open up IQViewController.m and take a look at setupQuiz:

- (void)setupQuiz {
  self.navigationItem.title = @"Free Version";
  self.maxAllowedQuestions = 5;
  [self setupAds];
  NSString* plistPath = [[NSBundle mainBundle] pathForResource:@"Questions" ofType:@"plist"];
  self.questions = [NSArray arrayWithContentsOfFile:plistPath];
  self.currentQuestionIndex = 0;
  [self showNextQuestion];

This is called by viewDidLoad, and configures the UI as well as loading the questions from a plist into an array called questions. Note that along with kicking off iAds, it sets things like the controller title and number of allowed questions. These would all be different in the paid version of the app, so you’ll be seeing this again later in the tutorial.

Since it’s driving much of the app, take a moment to look at Questions.plist:
7- Questions Plist

This is the plist that was stored in the questions array. Each question is a dictionary with the following keys:

  • question: Contains a string with the question to be asked.
  • answer1 .. answer4: These contain strings with the possible answers.
  • correctAnswer: Contains an NSNumber that holds the number of the correct answer.

Head back to IQViewController.m and look at showNextQuestion:

- (void)showNextQuestion {
  //1 - handle last question
  if (self.currentQuestionIndex + 1 > self.maxAllowedQuestions) {
    [[[UIAlertView alloc] initWithTitle:@"Game Over"
                                message:[NSString stringWithFormat:@"Your score is %ld",(long)self.score]
                      otherButtonTitles:nil] show];
  //2 - update view for next question
  NSDictionary *questionDetail = self.questions[self.currentQuestionIndex];
  self.questionLabel.text = questionDetail[@"question"];
  for (int buttonCount = 1; buttonCount <= 4; buttonCount++) {
    UIButton *answerButton = (UIButton *)[self.view viewWithTag:buttonCount];
    [answerButton setTitle:questionDetail[[NSString stringWithFormat:@"answer%d",buttonCount]]

This method is called each time a new question needs to be displayed, and it does a couple of things:

  1. Once you've hit the question limit stored in maxAllowedQuestions, this displays a UIAlertView with your score.
  2. This grabs the current question from the questions array and then uses it to set the label with the question as well as configuring the buttons with the four possible answers. Note that the answer buttons are referenced via their tag (set in Main.storyboard), and are numbered 1 - 4 to match the question numbers.

Finally, take a look at answerButtonPressed:

- (IBAction)answerButtonPressed:(UIButton *)sender {
  int correctAnswer = [(NSNumber *)self.questions[self.currentQuestionIndex][@"correctAnswer"] intValue];
  if (sender.tag == correctAnswer) {
    self.scoreLabel.text = [NSString stringWithFormat:@"%ld",(long)self.score];
  [self showNextQuestion];

Once a user presses an answer button, this checks if the answer is correct, adds the score accordingly, and then loads the next question.

Creating a New Target for the Paid Version

Now that you're familiar with the project and the functionality of the free version, it's time to implement the paid version using the same project and codebase. The key to accomplishing this is the creation of a second target. This will enable two important things:

  1. You'll be able add files (such as icon images) to the project that are tied specifically to this new target.
  2. You'll be able to configure each target with a different Preprocessor Macro (more on this later), which you'll use to identify the currently running app version in your code.

Creating a new target is easiest if you duplicate an existing target and make some modifications. This saves a lot of time versus configuring a target from scratch. Start by selecting your project from the navigator:

8- Navigator

Now right click your target InterviewQuestionsFree, and choose duplicate:

9- Duplicate Targets

Choose Duplicate Only on the popup:

10- Duplicate Only

Note: The reason this dialog exists is to provide you with an easy way to create both iPhone and iPad versions of an app within a single project, using two different targets. If this is what you wanted to do, you could select "Duplicate and Transition to iPad".

However, before you go down this route you should consider making your app universal rather than making separate iPhone and iPad versions of your apps - IMHO it's a nicer experience for users because they don't have to buy your app twice. For more information on creating a universal app, check out this tutorial.

You want to be able to easily identify this new target as the paid app version target - so you have some renaming to do! First, select the new target, hit return, and rename it to InterviewQuestionsFull:
11- Rename Target

Next there's a naming issue to fix. If you look up at the top part of XCode where you usually choose the device you want to run your app on, you'll find something called a scheme just to the left of that:

Scheme Selection

You'll use the scheme in just a bit to select with target you want to build. Currently you have InterviewQuestionsFree selected, but if you expand the popup, you'll find a terrible thing:

14- Wrong Scheme

The other scheme is called InterviewQuestionsFree copy. You'll want to rename that. Press on the current InterviewQuestionsFree to present the popup, then select manage schemes.

15- Manage Scheme

Click on InterviewQuestionsFree copy, hit return to make it editable then rename it to InterviewQuestionsFull. Hit enter once more and then press OK.

16- New Scheme

Everything looks neat now. I can sleep soundly tonight!

Modifying the Paid Target's Plist

You might have noticed that after you created the new target for the full version, a new plist was added to your project:

17- New plist

This plist has a problem. Its name isn't consistent with our target name anymore!

Rename it to InterviewQuestionsFull.plist and move it just under the InterviewQuestionsFree.plist by dragging it in the navigator.

20- Drag Plist

Now open your newly renamed InterviewQuestionsFull.plist. Update the Bundle display name - which is the app's name that you see under it's icon on the Springboard - to Interview:

18- Change Bund Display

You've made a small mistake. What was it? Select your project from the navigator then select your InterviewQuestionsFull target:

Missing Plist

Your target was pointing to the old plist name, and now that you renamed it, the target has no plist! You need to link it back to the target. Press on Choose Info.plist File and choose InterviewQuestionsFull.plist from the popup:

22- Choose Plist

For the final piece of renaming fun, hop over to the Build Settings for this target. In the Packaging section, update Product Name to be InterviewQuestionsFull. Although you've already set a display name in the plist, the product name will appear in the bundle identifier, and you should update it for consistency.

product name

It’s time to finally run the app, and test this new target. To identify which product (free or paid) you want to run, you need to select the appropriate scheme followed by a choice of your favorite device. In this case, select the InterviewQuestionsFull scheme so that you can try our new target.

23- Select Scheme

Build and run. You'll see that the new target is running, and as you might expect it looks identical to the free target you duplicated:

 Welcome Screen

Now press the home button on your sim (or select Hardware->Home from the sim menu). You’ll notice you now have two installs - one with the Bundle display name you chose for Full (Interview), and the other with the default set for Free (Interview Free).This is because each target has its own Bundle Identifier, making it a unique app.
Two Apps

This means your second target is working properly, and you're now building two distinct apps out of this project.

Adding the Paid Version Resources

Right now the apps are exactly the same, so let's start adding some differentiation. You'll start by giving the paid version its own icon and splash screen that don't say "Free" on them.

Go to your project's folder in the Finder and navigate to Interview Questions Starter/InterviewQuestions/InterviewQuestions/Images/Full. You'll find assets for the paid version that haven't yet been added to the project.

Drag them to your XCode project and place them in the paid folder that you'll find in Supporting Files/Images, but don't click finish yet!

Drag to Project

Pay attention to this tricky part:

27- Select Target

The bottom half of the popup has a section labeled Add to targets that you may have ignored when adding resources to a single target project. Now it's time to start paying attention. These added images will only be used in the paid target, so check the InterviewQuestionsFull target and uncheck the InterviewQuestionsFree target before hitting Finish.

If you go back to your navigator, you'll notice you now have 3 files in your paid folder that appear with the same names in the Free folder. Go ahead and select the InterviewQuestionsFull scheme, then build and run. While the project still runs, you'll see XCode throwing some warnings if you look at the Issue Navigator:


XCode is pointing out that you're trying to copy the same file into your product bundle twice - something it cannot do because the bundle is a flat structure. This happened because you copied the paid images to the InterviewQuestionsFull target while the Free versions of the files were already there from when you duplicated the Free target.

Time to fix it! In the Project Explorer, select the 3 images under the Free folder, and then direct your attention to the File Inspector tab of the Utility Pane on the right:

Target Selection

Looking closer at the File Inspector, you'll see a list of checkboxes under Target Membership:
29- 2 Targets

Uncheck the InterviewQuestionsFull target for these three resources, as you only want them associated with the Free target:

30- Remove Target

Build with the InterviewQuestionsFull scheme again and you'll get a successful build without any warnings about duplicate files.

Even more exciting is that you can now see unique splash and icon images for the two targets. Take a look at your icons:

unique icons

Now only your free product has the word "free" in it's icon, and you've successfully learned how to tie unique assets to different targets within a project.

That was easy, but what about changes that require code modification?

Adding a Macro to Differentiate the Versions

Now that you have your two targets on the same codebase, you need to make some changes to enable paid features on the paid version. For example, you need the maximum number of allowed questions to be set to 10 in the paid version, rather than the 5 used for Free.

Running on a single codebase, you're going to need some programatic way to determine which product (target) is currently running so you can make decisions like this appropriately.

This is where a Preprocessor Macro comes into play. Macros are the small fragments of code that you're used to seeing in #define statements. You've probably used them before for something like defining a custom RGB color:

#define GREEN [UIColor colorWithRed:126.0/255.0 green:176.0/255.0 blue:122.0/255.0 alpha:1.0]

The key here is that you can define a preprocessor macro in the configuration of a target. This way, you can define a flag that identifies the product version and make decisions in your code based on that. To set this up, start off by selecting your project form the navigator:

8- Navigator

Select your InterviewQuestionsFree target and then go to the Build Settings tab.

31- Build Settings

Filter on the word “macro” and you'll find a place to enter preprocessor macros:

You’ll see the Debug build already has a macro called DEBUG=1 here. You want to add something similar to both the Debug and Release profiles to signify this target is tied to the Free product. Double click the values for these, then hit the '+' button and enter a macro called FREE=1:

33- Rename

Once complete, your macros for the InterviewQuestionsFree target should look like this:

34- After Renaming

Now you can use this macro in your code to differentiate between your two targets!

Test this out by changing the navigation item title according to different targets. Open IQViewController.m and replace the following line in setupQuiz:

self.navigationItem.title = @"Free Version";

with this:

#ifdef FREE
    self.navigationItem.title = @"Free Version";
    self.navigationItem.title = @"Paid Version";

The #ifdef simply confirms if there is a macro called FREE defined. Because you defined this on the free version only, this allows you to handle the two versions differently.

Run your app using the IntervewQuestionsFull scheme, and click Test my skills. You'll see that the navigation item title has changed to "Full Version".

35- Full Version

If you go back and run with the InterviewQuestionsFree scheme, you'll see it's still displaying "Free Version" as you'd expect.

You can use this same technique to make other changes between the two versions. Let's make the maximum allowed questions to 5 for the Free target and 10 for Full, and remove ads for the full version.

To do this, still in setupQuiz, find the following lines:

#ifdef FREE
    self.navigationItem.title = @"Free Version";
    self.navigationItem.title = @"Paid Version";

  self.maxAllowedQuestions = 5;
  [self setupAds];

Modify this code to look like the following:

#ifdef FREE
  self.navigationItem.title = @"Free Version";
  self.maxAllowedQuestions = 5;
  [self setupAds];
  self.navigationItem.title = @"Full Version";
  self.maxAllowedQuestions = 10;

Run your application with each schema to see the differences. You'll now be able to see all 10 questions for the full version, and rejoice - no more ads!

no ads

Adding the Skip Button for the Full Version

As a bonus, let's add a new feature to the full version: the ability to allow the users to skip a question.

To do this, add some code to set up this new button only in the full version by pasting the below inside the #else block in setupQuiz:

  self.skipQuestionButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
  [self.skipQuestionButton setFrame:CGRectMake(20, 80, 100, 44)];
  [self.skipQuestionButton setTitle:@"Skip Question" forState:UIControlStateNormal];
  [self.skipQuestionButton addTarget:self
  [self.view addSubview:self.skipQuestionButton];

Your else statement will now look like this:

  self.navigationItem.title = @"Full Version";
  self.maxAllowedQuestions = 10;
  self.skipQuestionButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
  [self.skipQuestionButton setFrame:CGRectMake(20, 80, 100, 44)];
  [self.skipQuestionButton setTitle:@"Skip Question" forState:UIControlStateNormal];
  [self.skipQuestionButton addTarget:self
  [self.view addSubview:self.skipQuestionButton];

Nothing fancy here. The skip button was created and set to trigger skipButtonPressed when pressed. Now move to the stubbed out skipButtonPressed and replace it with the following:

- (void)skipButtonPressed {
  self.scoreLabel.text = [NSString stringWithFormat:@"%ld",(long)self.score];
  [self showNextQuestion];
  self.skipQuestionButton.hidden = YES;

This does the following.

1. Gives the user a point and refreshes the displayed score.

2. Display the next question.

3. Hide the skip button, as it's only allowed once per quiz.

Run your app and test out the Skip Button functionality.

36- Skip

At this point, you've got two uniquely functioning versions of the app. Test them well by running different schemes to ensure that all is going great.

Where To Go From Here?

Here is the example project that you developed in this tutorial.

Congratulations - you now have a project that uses two targets with the same code-base, so now if you hit a bug that impacts both versions of your app, you only have to make that fix in one place. So once again, the day is saved. Thanks to the power rangers... oh wait... thanks to targets!

Targets are one of the features that are usually ignored by developers. With this new knowledge in hand, you’ll be able to create powerful products.

For example, you might create a template news app with different targets for each of your clients. Each target could have its own colors, resources, and minor changes in the code. This way you’ll be able to build a totally new app in just few clicks. Sell that to different clients and you’ll become rich in no time... just don't forget my commission $$$! :]

I hope you enjoyed this tutorial, and if you have any questions or comments please join the forum discussion below!

Dani Arnaout


Dani Arnaout


Over 300 content creators. Join our team.