As a developer, there’s only one thing you need to worry about when creating a new project in Xcode: writing the code you need to build an amazing app. Every new Xcode project comes with all the scaffolding you need to run the app on a device.
If you only need to build an app to your device, and occasionally push to the App Store, this might be good enough. With more complicated projects and apps, you’ll need to go beyond what Xcode offers as the default and learn more about build customization in Xcode.
By the end of this chapter, you’ll know how to:
- Build more than one app in a single project.
- Work with build settings.
- Change exactly what happens when you click Build.
- Understand the difference between building your device and archiving for the App Store.
Right now, Emitron has the default build setup of any iOS project. You can do a local build to your device by building and running the app as usual, or you can prepare a build destined for the App Store by using the Product ▸ Archive menu option. In this chapter, you’ll configure another way to build the app: as an Alpha build specifically destined for internal testing.
Different build types
Any release of an iOS app goes through different stages before it’s published to the App Store. How you or your team chooses to handle releases may differ, but many large-scale apps look something like this:
- Dev build: A local build installed on the developer’s device or simulator, with logging and debug features enabled to catch pesky bugs before they become a problem.
- Alpha/QA build: Alpha builds are available internally to the members of the team or company so they can test new features and revisions right away.
- Beta build: Beta builds are usually publicly available versions of your app which gives you a chance to test new features and make sure there aren’t any large bugs lying around.
- Release build: The version of your app that users can download on the App Store.
The different build types can go by many names- these are just some examples. For instance, you might refer to an alpha build as an internal or QA build. The terminology is less important than the ability to set it up, which you’ll learn in this chapter.
If your app utilizes servers, dev and alpha builds should ideally point to a different backend server to prevent interference with production data. If you were working on a banking app, you wouldn’t want to see real money change hands during testing! Using a different server when debugging or doing QA testing can prevent this issue.
Emitron’s release pipeline
Every new version of Emitron goes through four stages based on the different build types.
- Dev: When the team is working on new features, they’ll deploy dev builds to their own devices.
- Alpha: Once the dev team wraps up their work, they will create a new alpha build. The team distributes the alpha internally through either ad-hoc, enterprise or TestFlight deployment.
- Beta: When the alpha build is stable and bug-free, the dev team makes a new beta build. This could be distributed internally again, or to external beta testers.
- Release: The build is finally ready for the App Store!
Multiple builds at once
Multiple build types should be installable on the same device, at the same time. A developer should be able to have their latest dev build on their iPhone, the last QA build that went to the team and the latest release build from the App Store.
Technically, you can’t have more than one version of the same app on one device. iOS uses the bundle identifier to identify apps; which means no two apps can have the same bundle identifier. By using different bundle IDs for each build type, iOS thinks of them as different apps entirely, allowing you to have more than one build on a device. This means modifying the bundle ID is your golden ticket to having multiple types of builds running on the same device. Neat!
So, how do you swap out the bundle ID based on which build you need to make? You could just change the value for the bundle ID when you’re making an alpha build, but then you’d have to remember to change it back for a release build. The correct way to tackle this problem is to set up Xcode so that you can swap between build types at the click of a button.
Moving between build types
When moving between build types, you must remain aware that unintended changes can slip through.
This is usually not a problem when going between dev, alpha, and beta, because bugs are generally expected on these types of builds. Moving from beta to release can be problematic: once your app is on the App Store, it’s up to your users to make sure that no unintended changes made it through!
This is why it’s a good idea to not recompile and re-distribute your app when moving from beta to release. By recompiling and using a newly built and potentially untested version of your app as your release build, you open up the possibility that unexpected bugs could appear in your app! You should use the final beta build as your release build to avoid these unexpected bugs.
Because you won’t be recompiling the app between beta and release, both the beta and release build types will have the same bundle identifier. That means you won’t be able to have the beta build and the release build both installed on the same device. There won’t be any changes in the configuration between the two.
Different build types in Emitron
In this chapter, you’ll configure the build setup in Emitron to switch between three build types: dev, alpha and release. Each build type will vary in its bundle IDs, their app icons, and the method of code signing.
Remember: You’re not configuring a beta build type because there shouldn’t be any configuration or code differences between beta and release.
When you’re ready to send the beta build to the App Store, the binary is already waiting for you in App Store Connect. No further code changes are needed!
Emitron already has a working dev and release build type- this is the default configuration for new Xcode projects. To add the alpha build type, you’ll need to get acquainted with the three cornerstones of build customization: targets, build settings and schemes, and learn how they work together.
A target is a list of files and instructions that tell Xcode how to build your app or app extension. When you hit Build in Xcode, the target is what you’re building.
When you build and run the Emitron project, you’re building an iOS App target. An Xcode project is not limited to only one target. The Emitron project has two targets: one that you use for building the app and one for unit tests.
Targets in Emitron
Open the sample project for this chapter in Xcode. In the Project Navigator, click on the Emitron project to reach the project screen.
On the left, you’ll see a list of projects and targets. You have two targets: emitron and emitronTests. The emitron target is an iOS App target. All of the source code for Emitron is added to this target. Xcode knows that when you build this target, the output should be an iOS app.
The second target, emitronTests, contains all of the unit testing code. When you build this target, Xcode compiles the unit testing code, which then runs on a device or simulator to test the app, which is built by the emitron target.
In the Project Navigator, expand the Emitron project. There are two folders of interest under it: Emitron for the main app target, and emitronTests for the unit test target.
Expand the Emitron folder, and click AppDelegate.swift.
Make sure the Inspectors panel is open on the right side of the window, and click on the File inspector. Have a look at the Target Membership section.
Here, you see that AppDelegate.swift is a member of the emitron target. When you build that target, Xcode compiles AppDelegate.swift along with all the other files added to the target.
Different target types
The emitron target is an iOS App target, but targets aren’t limited to only producing an iOS app. You can make apps for other platforms in Xcode too, such as a watchOS, tvOS or macOS app.
Click on the Emitron project again in the Project Navigator to reach the project screen. Here, under the list of targets, click the plus (+) button.
Xcode presents you with a myriad of target templates for iOS, watchOS, tvOS and macOS. There are many options within the iOS section- you can build almost anything!
In a cross-platform app, you likely would want to share code between different platforms. To make an app for two platforms, such as iOS and macOS, you make two targets: an iOS App target and a macOS App target, both in the same project. From there, you can add the same code files to both targets. When you need to write code for macOS only, you can add that code to the only macOS target, and vice versa for iOS.
Different targets are great for different products or actions. For example, you could use multiple targets to add a watchOS app to your project, an iMessage extension to make an iMessage app, as well as a target for automated UI tests.
As mentioned at the beginning of this chapter, you’ll be adding an alpha build configuration to Emitron. So how do targets affect this?
Targets for the alpha build type
Click Cancel on the New Target sheet.
You don’t need to make any changes to the targets in Emitron to accomplish this. The emitron target already builds the emitron iOS app, and that’s all you need. To add the alpha build type, you’ll still use this target, but you’ll change how to build the target.
Next, you’ll learn about build settings and how to change them for the alpha build.
Any time you build a target, you tell Xcode how to run the build by specifying a large set of build settings. Navigate to the project screen in Xcode again, select the emitron target, and then click Build Settings.
These settings are the rules that Xcode follows when building your app. Here, you change build settings on a target level: what you specify here will apply when building the emitron target, and you can have another set of build settings for a different target.
There are hundreds of settings, but you shouldn’t have to worry about most of them. Feel free to scroll through the list and have a look. The settings you specify here define the Swift language version used in the app, how to package the app and its dependencies, the actual display name of the app, how to handle code signing and much more.
Build settings resolution
Usually, build settings apply to the whole project. In projects with more than one target, you may want to change a build setting depending on the chosen target.
There are three different levels of specificity to take into account when configuring build settings.
- Target Level: Build settings specified for an individual target.
- Project Level: Settings that apply to the whole project.
- Platform Default: No setting is specified, so Xcode uses a default.
When you build a target, each possible build setting gets a value determined by the build settings specified for each level. If the Swift Language Version build setting has a value on the target level and the project level, then Xcode will use the value for the target level, because it’s more specific.
If you add a value for a build setting for a specific target, Xcode will use that value for the build setting. If there isn’t a value on the target level, Xcode looks one level up: the project level. And, if you didn’t specify a value on the project level, you’ll get the iOS default value instead.
In Xcode, still on the Build Settings tab for the emitron target, you’ll find a Levels button to the left of the build settings search bar. Click on it.
Now, you see four columns for each build setting:
- Resolved: After comparing all settings levels, Xcode chooses the most specific value and shows it here. Xcode will use this value when building the chosen target.
- Target: The value that you have specified for the chosen target.
- Project: The value that you have chosen to be used across the whole project unless there is a value on the target level.
- iOS Default: If no other value is specified, then Xcode will use the default value shown here.
In the search bar, search for Swift Language Version.
Here are the values for each level:
- Target: Swift 5
- Project: Unspecified
- iOS Default: Unspecified
Under the Resolved column, you can see that the value will be Swift 5 when you build the project, which is taken from the target level.
To the left of the Swift Language Version build setting, click the ▸ icon. Here, you see another way to customize a build setting: by build configuration.
Build configurations are a template for build settings. There are two default configurations in every iOS project: Debug and Release. Using these configurations, you can specify a value for a build setting when performing a Debug build, and a different value for a Release build. This comes in handy because you need a different provisioning profile and code signing certificate when doing a release build as opposed to a dev build.
Release builds also have heavier optimization to keep them running smoothly. Can you guess how Xcode knows that it should enable optimizations for a release build, but not for a dev build? That’s right, by using build configurations!
You aren’t limited to Debug and Release configurations. You can create your own, such as an alpha or a beta configuration. You’ll do that now so that you can use the alpha configuration to do an alpha build of Emitron.
Creating your own build configuration
Build and run the sample project. Once it’s running on your device, minimize the app to reach the home screen.
Here, you see the Emitron app installed on your device. When you’re done configuring the alpha version, you will be able to have a different copy of Emitron installed on your device for each different build type: dev, alpha and release.
Note: You won’t have a beta and release build on the same device, because you shouldn’t be recompiling the code between beta and release.
You’ll accomplish this by swapping out the bundle identifier for different build configurations. Right now, Emitron has the default Debug and Release configurations. You’ll need to create the Alpha configuration yourself.
In Xcode, make sure you’re still on the project screen by clicking on the Emitron project in the Project Navigator. Then, click Emitron under the Project section of the sidebar. Click on the Info tab. Here, you’ll find a Configuration section.
Click the plus (+) button near the bottom left of the Configurations section, and choose Duplicate “Release” Configuration.
Change the name to Alpha and press Enter to save your changes.
Now, you have three configurations: the default Debug and Release configurations and a brand new Alpha configuration that is an exact copy of the Release configuration.
Having a separate build configuration for alpha builds means that you can change build settings for the Alpha configuration but leave the settings unchanged for all other build types. Next, you’ll add more distinction to the Alpha configuration by changing its build settings.
Swapping settings by build configurations
Still in the project screen in Xcode, click on the emitron target in the sidebar to the left, and click on the Build Settings tab. Change the view from Levels to Combined to simplify the settings view:
In the search bar, search for bundle identifier. Next to the Project Bundle Identifier build setting, click the ▸ icon to expand the setting.
Here, you can see that the build setting can have three possible different values for each build configuration. Change the value for the Alpha configuration to
com.raywenderlich.emitron.pias.alpha. Then, change the value for the Debug configuration to
Now, you can have three different versions of the app running on your device, thanks to the three different build configurations and their varying bundle identifiers!
When you run the app on your device for development purposes, you build it using the Debug configuration. So, the next time you run the app, it will install using the new
com.raywenderlich.emitron.pias.dev bundle identifier.
Build and run. Once the app is running on your device, minimize it.
You should now see two different copies of Emitron on your device.
Earlier, when you ran Emitron, Xcode built it with the default
com.raywenderlich.emitron.pias bundle identifier. That version is still there on your device, but now you’ve deployed the app again using the
com.raywenderlich.emitron.pias.dev bundle identifier.
Changing the app icon
You can also change the app icon based on the build configuration. Head back to Xcode. Still in the build settings, in the search bar, search for icon. Next to the App Catalog App Icon Set Name build setting, click the ▸ icon to expand the setting.
Now, change the value for the Alpha configuration to
AppIcon.alpha and the value for the Debug configuration to
AppIcon.dev Your settings should look like this when you’re done:
The sample project files contain three different app icons: AppIcon, AppIcon.alpha and AppIcon.dev. The dev and alpha icons were previously unused, but now you’re swapping them in based on the build configuration.
Build and run. Once the app is running, minimize it again. Now, your dev build has the right app icon, so there’s much less chance of mistaking the two!
So far, you’ve made changes to the Debug configuration so that they differ from Release, and you’ve seen the results on your device. You can’t see any changes that you’ve made to your Alpha configuration yet. To do that, you’ll need to add the Alpha configuration to a scheme.
A scheme is a collection of targets and a build configuration that Xcode uses when building. Schemes are the amalgamation of everything you’ve learned so far in this chapter. You can think of them as a blueprint that tells Xcode exactly what to do when you click the build button. It specifies the actual targets to build and what build configuration to use.
When you build in Xcode or run the app on a device, Xcode is building a scheme that specifies what target is to be built. You can see the active scheme in the Xcode toolbar:
Building the project will use the active scheme, Emitron, as the blueprint. Xcode will resolve build settings based on the build configuration that the scheme specifies. Then, it uses those settings to build the scheme’s target.
Schemes come with a set of different scheme actions. When you build, archive or run tests on a project, you’re telling the active scheme to execute a particular scheme action.
Click on the active scheme, Emitron, and then click Edit Scheme… in the dropdown.
On the left, you see six different scheme actions:
- Build: The most basic action, Build compiles a target.
- Run: First builds a target, then deploys it to your device.
- Test: Builds a target along with a Test target before running the tests on your device.
- Profile: Starts the same as the Run action, but also attaches the running app to Instruments.
- Analyze: Builds a target and then runs a static analyzer on the result.
- Archive: Usually done with a Release build configuration, this builds a target and then opens a window to let you submit it to the App Store.
Each build action further customizes how to configure your build using the tabs at the top of the sheet.
Depending on what action you have selected, you’ll see some of these tabs:
- Info: Lets you choose the target and build configuration to apply.
- Arguments: Allows you to pass arguments and set environment variables.
- Options: Provides various options that you can enable to help test and debug different scenarios.
- Diagnostics: Enables diagnostic tools to help you catch and prevent many of the trickier bugs.
At the bottom of the scheme editor, the Shared checkbox lets you make a scheme public and add it to Git.
Right now, the Emitron scheme is set up to build a dev build for your device, run the emitronTests target to execute unit tests, or archive a release build depending on the build action you choose.
You don’t need to make any changes to the Emitron scheme for the alpha build that you’re adding to the project. Instead, you’ll duplicate the Emitron scheme and use it to change the way it builds Emitron.
Creating and modifying a scheme
The version of Emitron in your sample project files has been localized and translated into Polish, but there isn’t an easy way to test the localization. You could change the language of your device, but it might not be so easy to get it back to English again! :]
As part of the options on the Run action, you can change the language of the app once it’s deployed to your device. Changing this for the Emitron scheme means that every time you run the app, it’ll be in a foreign language. So, rather than changing the Emitron scheme, you’ll duplicate it and use that scheme for testing localization.
Still in the scheme editor, click Duplicate Scheme near the bottom of the sheet.
You’re now editing the text box with the scheme name. Change the name to
Emitron (PL), but don’t press Enter yet, as it’ll close the scheme editor.
Next, click on the Run action, and then Options.
The Application Language is set to English. Change it to Polish.
Finally, click Close. Xcode changes the active scheme to your new scheme, Emitron (PL).
Build and run, and take a look at the text in the app.
Your new Emitron (PL) scheme is an exact duplicate of the familiar Emitron scheme, except that when installing the app, it sets the app language to Polish.
Lucky you, now you can learn both Polish and iOS programming at once! :]
Now that you’ve seen how to create additional schemes, you can use what you’ve learned to create a scheme for the alpha build.
Preparing your alpha build
Earlier, you created an Alpha build configuration and used it to change the bundle identifier and app icon, but you can’t make an alpha build until there’s a scheme using the Alpha build configuration.
In Xcode, click on the active scheme and choose Manage Schemes… to bring up the scheme manager.
From here, you can see a list of all your schemes, not just the active one.
You can also see that your new scheme, Emitron (PL), isn’t marked as Shared. New schemes aren’t shared by default. This is fine for a scheme that you’re using for some localization testing, but remember to share your schemes if you want other developers to get them, too.
Select the Emitron scheme and then click Edit… at the bottom of the scheme manager.
Now, click Duplicate Scheme.
Similar to when you created the Emitron (PL) scheme, change the name to
Emitron Alpha but don’t press Enter.
Next, click on the Run action, and switch to the Info tab.
Change the Build Configuration to Alpha:
Now, when you run the Emitron Alpha scheme, Xcode will use the Alpha configuration to resolve build settings.
When you prepare an alpha release, you also want to use the Alpha configuration. Click the Archive action in the scheme editor.
Once again, change the Build Configuration to Alpha.
Finally, click the Close button at the bottom of the scheme editor.
Emitron Alpha is now your active scheme:
Build and run to test your changes. Once the app is running, minimize it to have a look at your home screen.
Xcode installed Emitron on your device with a different bundle identifier and app icon. That brings you to a whopping total of three versions of Emitron running on your device!
So far, you’ve managed to get an alpha build running on your phone, but it’s not ready for TestFlight yet. In the next chapter, you’ll set up code signing and everything else you need to start sharing your alpha with other users.
- Not every build is destined for the App Store. Builds can be separated by purpose into different build types such as Dev, QA or Release.
- Targets represent a product to be built. Xcode offers a large variety of target types including different platforms and extensions.
- Build settings tell Xcode how to build your target.
- Build configurations swap out build settings for different build types.
- Schemes are blueprints instructing Xcode how to compile a target using a chosen build configuration. When you build and run an app in Xcode, you’re executing the Run action for the currently active scheme.
- Different schemes can build the same target with different scheme options. You could simulate a different location for Core Location, change the app language, enable a variety of debugging tools and more.