Chapter 6. Anatomy of an Xcode Project

Xcode is the application used to develop an iOS app. An Xcode project is the source for an app; it’s the entire collection of files and settings used to construct the app. To create, develop, and maintain an app, it helps to know how to manipulate and navigate an Xcode project. You’ll want to be familiar with Xcode, and you’ll need to know about the nature and structure of Xcode projects and how Xcode shows them to you. That’s the subject of this chapter.

Xcode is a powerful, complex, and very large program. Our survey will chart a safe, restricted, and essential path, focusing on aspects of Xcode that you most need to understand immediately, and resolutely ignoring everything else.

Note

The term “Xcode” is used in two ways. It’s the name of the application in which you edit and build your app, and it’s the name of an entire suite of utilities that accompanies it; in the latter sense, Instruments and the Simulator are part of Xcode. This ambiguity should generally present little difficulty.

New Project

Even before you’ve written any code, an Xcode project is quite elaborate. To see this, let’s make a new, essentially “empty” project; you’ll find that it isn’t empty at all.

  1. Start up Xcode and choose File → New → Project.

  2. The “Choose a template” dialog appears. The template is your project’s initial set of files and settings. When you pick a template, you’re really picking an existing folder full of files; this folder is hidden deep inside the Xcode bundle, and will essentially be copied, with a few values filled in, to create your project.

    In this case, select iOS; under Application, select the Single View App template. Click Next.

  3. You are now asked to provide a name for your project (Product Name). Let’s call our new project Empty Window.

    As Xcode copies the template folder, it’s going to insert the project’s name in several places, including using it as the name of the app. Whatever you type at this moment is something you’ll be seeing throughout your project. You are not locked into the name of your project forever, though, and there’s a separate setting allowing you to change the name of the app that your project produces. (I’ll talk later about name changes; see “Renaming Parts of a Project”.)

    Spaces are legal in the project name, the app name, and the various names of files and folders that Xcode will generate automatically; and in the few places where spaces are problematic (such as the bundle identifier, which I’ll discuss in a moment), the name you type as the Product Name will have its spaces converted to hyphens. But do not use any other punctuation in your project name! Such punctuation can cause Xcode features to break in subtle ways.

  4. Ignore the Team pop-up menu for now; I’ll discuss its significance in Chapter 9. Ignore the Organization Name as well; it is used only in some automatically generated code comments.

  5. Note the Organization Identifier field. The first time you create a project, this field will be blank, and you should fill it in. The goal here is to create a unique string identifying you or your organization. The convention is to start the organization identifier with com. and to follow it with a string (possibly with multiple dot-components) that no one else is likely to use. Every app on a device or submitted to the App Store needs a unique bundle identifier. Your app’s bundle identifier, which is shown in gray below the organization identifier, will consist by default of the organization identifier plus a version of the project’s name; if you choose a unique organization identifier and give every project a unique name within your personal world, the bundle identifier will uniquely identify this project and the app that it produces. (You will be able to change the bundle identifier manually later if necessary.)

  6. The Language pop-up menu lets you choose between Swift and Objective-C. This choice is not positively binding; it dictates the initial structure and code of the project template, but you are free to add Swift files to an Objective-C project, or Objective-C files to a Swift project. You can even start with an Objective-C project and decide later to convert it completely to Swift. (See “Bilingual Targets”.) For now, choose Swift.

  7. The User Interface pop-up menu should say Storyboard, not SwiftUI. For this example project, make sure Use Core Data, Include Unit Tests, and Include UI Tests are not checked. Click Next.

  8. You’ve now told Xcode how to construct your project. Basically, it’s going to copy a template folder from somewhere deep within the Xcode application bundle. But you need to tell it where to copy this template folder to. That’s why Xcode is now presenting a Save dialog with a Create button. You are to specify the location of a folder that is about to be created — the project folder for this project. The project folder can go just about anywhere, and you can move it after creating it. I usually create new projects on the Desktop.

  9. Xcode also offers, through a checkbox, to create a git repository for your project. (You might need to click Options to see the checkbox.) In real life, this can be a great convenience (see Chapter 9), but for now, uncheck that checkbox. If you see an Add To pop-up menu, leave it at the default, “Don’t add to any project or workspace.” Click Create.

    The Empty Window project folder is created on disk (on the Desktop, if that’s the location you just specified), and the project window for the Empty Window project opens in Xcode.

The project we’ve just created is a working project; it really does build an iOS app called Empty Window. To see this, you can actually build the app — and run it! The scheme and destination in the project window’s toolbar might be listed as Empty Window → iPhone X or Empty Window → iPhone 8 Plus; that’s fine. (The scheme and destination are actually pop-up menus, so you can click them to change their values if needed.) Choose Product → Run. After some delay, the Simulator application eventually opens and displays your app running — an empty white screen.

Note

To build a project is to compile its code and assemble the compiled code, together with various resources, into the actual app. Typically, if you want to know whether your code compiles and your project is consistently and correctly constructed, you’ll build the project (Product → Build). To run a project is to launch the built app, in the Simulator or on a connected device; if you want to know whether your code works as expected, you’ll run the project (Product → Run), which automatically builds first if necessary.

The Project Window

An Xcode project embodies a lot of information about what files constitute the project and how they are to be used when building the app, such as:

  • The source files (your code) that are to be compiled

  • Any .storyboard or .xib files, graphically expressing interface objects to be instantiated as your app runs

  • Any resources, such as icons, images, or sound files, that are to be part of the app

  • All settings (instructions to the compiler, to the linker, and so on) that are to be obeyed as the app is built

  • Any frameworks that the code will need when it runs

A single Xcode project window presents all of this information, lets you access, edit, and navigate your code, and reports the progress and results of such procedures as building or debugging an app and more. This window displays a lot of information and embodies a lot of functionality! A project window is powerful and elaborate; learning to navigate and understand it takes time. Let’s pause to explore this window and see how it is constructed.

A project window has four main parts (Figure 6-1):

ios13 0601numbered
Figure 6-1. The project window
  1. On the left is the Navigator pane. Show and hide it with View → Navigators → Show/Hide Navigator (Command-0) or with the first View button at the right end of the toolbar.

  2. In the middle is the Editor pane (or simply “editor”). This is the main area of a project window. A project window nearly always displays an Editor pane.

  3. On the right is the Utilities pane. Show and hide it with View → Utilities → Show/Hide Utilities (Command-Option-0) or with the third View button at the right end of the toolbar.

  4. At the bottom is the Debug pane. Show and hide it with View → Debug Area → Show/Hide Debug Area (Command-Shift-Y) or with the second View button at the right end of the toolbar.

Note

All Xcode keyboard shortcuts can be customized; see the Key Bindings pane of the Preferences window. Keyboard shortcuts that I cite are the defaults.

The Navigator Pane

The Navigator pane is the column of information at the left of the project window. Among other things, it’s your primary mechanism for controlling what you see in the main area of the project window (the editor). An important use pattern for Xcode is: you select something in the Navigator pane, and that thing is displayed in the editor.

It is possible to toggle the visibility of the Navigator pane (View → Navigators → Hide/Show Navigator, or Command-0); you might hide the Navigator pane temporarily to maximize your screen real estate (especially on a smaller monitor). You can change the Navigator pane’s width by dragging the vertical line at its right edge.

The Navigator pane can display nine different sets of information; there are actually nine navigators. These are represented by the nine icons across its top; to switch among them, use these icons or their keyboard shortcuts (Command-1, Command-2, and so on). If the Navigator pane is hidden, pressing a navigator’s keyboard shortcut both shows the Navigator pane and switches to that navigator.

Depending on your settings in the Behaviors pane of Xcode’s preferences, a navigator might show itself automatically when you perform a certain action. For example, by default, when you build your project, if warning messages or error messages are generated, the Issue navigator may appear. This automatic behavior will not prove troublesome, because it is usually the behavior you want, and if it isn’t, you can change it; plus you can easily switch to a different navigator at any time.

Let’s begin experimenting immediately with the various navigators:

Project navigator (Command-1)

Click here for basic navigation through the files that constitute your project (Figure 6-2). For example, in the Empty Window folder (the folder-like things in the Project navigator are actually called groups), click AppDelegate.swift to view its code in the editor.

At the top level of the Project navigator, with a blue Xcode icon, is the Empty Window project itself; click it to view the settings associated with your project and its targets. Don’t change anything here without knowing what you’re doing!

The filter bar at the bottom of the Project navigator lets you limit what files are shown; when there are many files, this is great for quickly reaching a file with a known name. For example, try typing “delegate” in the filter bar search field. Don’t forget to remove your filter when you’re done experimenting.

Warning

Once you’ve filtered a navigator, it stays filtered until you remove the filter — even if you close the project! A common mistake is to filter a navigator, forget that you’ve done so, fail to notice the filter (because you’re looking at the navigator itself, not down at the bottom where the filter bar is), and wonder, “Hey, where did all my files go?”

ios13 0602
Figure 6-2. The Project navigator
Source Control navigator (Command-2)

The Source Control navigator helps you manipulate how your project’s files are handled through version control. I’ll discuss version control in Chapter 9.

Symbol navigator (Command-3)

A symbol is a name, typically the name of a class or method. The Symbol navigator lists symbols available to your code. Among other things, this can be useful for navigating. For example, highlight the first two icons in the filter bar (the first two are blue, the third is dark), twist open the class listings, and see how quickly you can reach your code’s implementation of SceneDelegate’s sceneDidBecomeActive(_:) method.

Try highlighting the filter bar icons in various ways to see how the contents of the Symbol navigator change. Type in the search field in the filter bar to limit what appears in the Symbol navigator; for example, try typing “active” in the search field, and see what happens.

Find navigator (Command-4)

This is a powerful search facility for finding text globally in your project. You can also summon the Find navigator with Find → Find in Project (Command-Shift-F). The words above the search field show what options are currently in force; they are pop-up menus, so click one to change the options. Try searching for “delegate” (Figure 6-3). Click a search result to jump to it in your code.

Below the search field, at the left, is the current search scope. This limits what files will be searched. Click it to reveal the search scopes. You can create or edit a “smart” scope (for example, search only .swift files), and you can limit the search to one or more groups (folders).

You can type in the other search field, the one in the filter bar at the bottom, to limit further which search results are displayed. (I’m going to stop calling your attention to the filter bar now; every navigator has it in some form.)

ios13 0603
Figure 6-3. The Find navigator
Issue navigator (Command-5)

You’ll need this navigator primarily when your code has issues. This doesn’t refer to emotional instability; it’s Xcode’s term for warning and error messages emitted when you build your project. The Issue navigator can also display certain runtime issues (such as leaks, as I’ll explain in Chapter 9).

To see the Issue navigator in action, let’s give your code a buildtime issue. Navigate to the file AppDelegate.swift, and in the blank line after the last comment at the top of the file’s contents, above the import line, type howdy. Build the project (Command-B). Switch to the Issue navigator if it doesn’t appear automatically; in its Buildtime pane, it displays some error messages, showing that the compiler is unable to cope with this illegal word appearing in an illegal place. Click an issue to see it within its file. In your code, issue “balloons” may appear to the right of lines containing issues.

Now that you’ve made Xcode miserable, select “howdy” and delete it; save and build again, and your issues will be gone. If only real life were this easy!

(Starting in Swift 4.2, you can create a custom buildtime issue, either a compile error or a warning, by starting a line with #error or #warning respectively followed by a string literal in parentheses, like this: #warning("Fix this!"). This can be a dramatic way to leave a note to whoever subsequently tries to compile this code — possibly your future self.)

Test navigator (Command-6)

This navigator lists test files and individual test methods and permits you to run your tests and see whether they succeeded. A test is code that isn’t part of your app; rather, it calls a bit of your app’s code, or exercises your app’s interface, to see whether things behave as expected. I’ll talk more about tests in Chapter 9.

Debug navigator (Command-7)

By default, this navigator will appear when your code is paused while you’re debugging it. There is not a strong distinction in Xcode between running and debugging; the milieu is the same. The difference is mostly a matter of whether breakpoints are obeyed (more about that, and about debugging in general, in Chapter 9).

To see the Debug navigator in action, you’ll need to give your code a breakpoint. Navigate once more to the file AppDelegate.swift, select in the line that says return true, and choose Debug → Breakpoints → Add Breakpoint at Current Line to make a blue breakpoint arrow appear on that line. Run the project. By default, as the breakpoint is encountered, the Navigator pane switches to the Debug navigator, and the Debug pane appears at the bottom of the window. This overall layout (Figure 6-4) will rapidly become familiar as you debug your projects.

ios13 0604
Figure 6-4. The Debug layout

The Debug navigator starts with several numeric and graphical displays of profiling information (at a minimum, you’ll see CPU, Memory, Disk, and Network); click one to see extensive graphical information in the editor. This information allows you to track possible misbehavior of your app as you run it, without the added complexity of running the Instruments utility (discussed in Chapter 9). To toggle the visibility of the profiling information at the top of the Debug navigator, click the “gauge” icon (to the right of the process’s name).

The Debug navigator also displays the call stack, with the names of the nested methods in which a pause occurs; as you would expect, you can click a method name to navigate to it. You can shorten or lengthen the list with the first button in the filter bar at the bottom of the navigator.

The Debug pane, which can be shown or hidden at will (View → Debug Area → Hide/Show Debug Area, or Command-Shift-Y), has at its top the debug bar containing various buttons, and consists of two subpanes:

The variables list (on the left)

The variables in scope for the selected method in the call stack at the point where we are paused, along with their values.

The console (on the right)

Here the debugger displays text messages; that’s how you learn of exceptions thrown by your running app, plus you can have your code deliberately send you log messages describing your app’s progress and behavior. Such messages are important, so keep an eye on the console as your app runs. You can also use the console to enter commands to the debugger. This can often be a better way to explore values during a pause than the variables list.

Either the variables list or the console can be hidden using the two buttons at the bottom right of the pane. The console can also be summoned by choosing View → Debug Area → Activate Console.

Breakpoint navigator (Command-8)

This navigator lists all your breakpoints. At the moment you have only one, but when you’re actively debugging a large project with many breakpoints, you’ll be glad of this navigator. Also, this is where you create special breakpoints (such as symbolic breakpoints), and in general it’s your center for managing existing breakpoints. We’ll return to this topic in Chapter 9.

Report navigator (Command-9)

This navigator lists your recent major actions, such as building or running (debugging) your project. Click a listing to see (in the editor) the report generated when you performed that action. The report might contain information that isn’t displayed in any other way, and also it lets you dredge up console messages from the recent past (“What was that exception I got while debugging a moment ago?”).

By clicking on the listing for a successful build, we can see the steps by which a build takes place (Figure 6-5). To reveal the full text of a step, click that step and then click the Expand Transcript button that appears at the far right (and see also the menu items in the Editor menu).

ios13 0605
Figure 6-5. Viewing a report

The Utilities Pane

The Utilities pane is the column at the right of the project window. It contains inspectors that provide information about the current selection or its settings; if those settings can be changed, this is where you change them. The Utilities pane’s importance emerges mostly when you’re editing a .storyboard or .xib file (Chapter 7). But it can be useful also while editing code, mostly because Quick Help, a form of documentation (Chapter 8), is displayed here as well. To toggle the visibility of the Utilities pane, choose View → Utilities → Hide/Show Utilities (Command-Option-0). You can change the Utilities pane’s width by dragging the vertical line at its left edge.

What appears in the Utilities pane depends on what’s selected in the current editor:

A code file is being edited

The Utilities pane shows the File inspector, the History inspector, or Quick Help. Toggle between them with the icons at the top of the Utilities pane, or with their keyboard shortcuts (Command-Option-1, Command-Option-2, Command-Option-3). The File inspector consists of multiple sections, each of which can be expanded or collapsed by clicking its header; I’ll give an example of using it in Chapter 9 when I talk about localization. History is about version control (Chapter 9 as well). Quick Help can be useful because it displays documentation (Chapter 8).

A .storyboard or .xib file is being edited

The Utilities pane adds the Identity inspector (Command-Option-4), the Attributes inspector (Command-Option-5), the Size inspector (Command-Option-6), and the Connections inspector (Command-Option-7). These inspectors can consist of multiple sections, each of which can be expanded or collapsed by clicking its header. I’ll talk more about them in Chapter 7.

Other forms of editing may cause other inspector combinations to appear here.

The Editor

In the middle of the project window is the editor. This is where you get actual work done, reading and writing your code (Chapter 9) or designing your interface in a .storyboard or .xib file (Chapter 7). The editor is the core of the project window. You can hide the Navigator pane, the Utilities pane, and the Debug pane, but there is basically no such thing as a project window without an editor.

The jump bar across the top shows you hierarchically what file is currently being edited. It also allows you to switch to a different file. Each path component in the jump bar is a pop-up menu. These pop-up menus can be summoned by clicking on a path component, or by using keyboard shortcuts (shown in the View → Editor submenu). Control-4 summons a hierarchical pop-up menu, which can be navigated entirely with the keyboard, allowing you to choose a different file in your project to edit. Moreover, each pop-up menu in the jump bar also has a filter field; to see it, summon a pop-up menu from the jump bar and start typing. Thus you can navigate your project even if the Project navigator isn’t showing.

Tip

Command-click a jump bar component to summon a menu showing the corresponding file in the Finder and its hierarchy of enclosing folders.

The symbol at the left end of the jump bar (Control-1) summons a hierarchical menu called the Related Items menu. This helps you navigate to files conceptually related to the current one. Its contents depend on both the current file and the current selection within it. You can navigate to related files declaring related types (Superclasses, Subclasses, Siblings, and adopted Protocols) and to methods that call or are called by the currently selected method. The Generated Interface menu displays a file’s public interface as seen by Swift or Objective-C (see Appendix A).

The editor remembers the history of what it has displayed, and you can return to previously viewed content with the Back button in the jump bar, which is also a pop-up menu (Control-2). Alternatively, choose Navigate → Go Back (Command-Control-Left).

Editor panes

It is likely, as you develop a project, that you’ll want to edit more than one file simultaneously, or obtain multiple views of a single file so that you can edit different areas of it simultaneously. New in Xcode 11, this is easier than ever before. The Editor pane area of the project window can be subdivided into smaller editor panes. Each pane can display a different file, or a different area of the same file.

To summon a new editor pane, choose File → New → Editor (Command-Control-T) or click the Add Editor button at the top right of an editor. Alternatively, if there is only one editor pane, Option-click a file listing in the Project navigator to open it in a new editor pane.

The new pane appears to the right of the current editor or below it; choose View → Change Editor Orientation to reverse this default or to move a pane from the right to below (or vice versa). To summon a new editor pane in the other orientation, choose File → New → Editor Below / On Right, or Option-click the Add Editor button.

To close an editor pane, choose File → Close Editor, or click the X button at the top left of an editor pane.

To zoom an editor pane temporarily, so that it takes over the whole editor area without closing any other panes, choose View → Editor → Focus, or click the double-arrow button at the top left of the pane. To unzoom, do the same thing again: choose View → Editor → Hide Focus, or click the double-arrow button. While a pane is zoomed, Close Editor and the X button are disabled; you have to unzoom the pane before you can close it.

When there are multiple editor panes, what happens when you click a file listing in the Project navigator? By default, its destination is current editor pane. But you can Option-click (or double-click) a file listing to specify a different destination; the exact details depend on your settings in the Navigation pane of Xcode’s preferences. For maximum flexibility, Option-Shift-click a file listing to enter destination chooser mode; you can then navigate with arrow keys to specify where you want this file to open — in an existing pane or as a new additional pane — and hit Return to open it there.

Tip

If the Project navigator selection gets out of sync with the file displayed in the current editor pane, you can bring it back in sync by choosing Navigate → Reveal in Project Navigator (Command-Shift-J).

Assistant panes

An assistant pane is a special kind of editor pane tied to some primary editor pane, in the following way: when you cause the primary pane to display a different file, its assistant pane automatically displays a different file to match. To summon an assistant pane, choose Editor → Assistant (or choose Assistant from the Editor menu at the top right of the editor pane). You’ll know you’ve entered editor-and-assistant mode because both panes will display an icon showing two linked rings.

Exactly what category of file the assistant pane automatically displays depends upon what you’ve specified as its relationship to the primary pane. You do that with the first pop-up menu in the assistant pane’s jump bar, containing the linked rings icon (Control-4). Your choices here are much like the menu items in the Related Items menu.

If more than one file falls into the category in question — for example, you’ve set the assistant to show Subclasses and the primary pane displays a class with more than one subclass — then a pair of arrow buttons appears at the right end of the assistant’s jump bar, with which you can navigate between them, or use the second jump bar component (Control-5).

Tabs and windows

A tab or window doesn’t just display a file, like an editor pane; it displays the whole project window interface, and can display it differently from another tab or window. Thus one tab or window might show the Project navigator, while another might have the Project navigator hidden, or display a different navigator.

To make a new tab, choose File → New → Tab (Command-T), revealing the tab bar (just below the toolbar) if it wasn’t showing already. Use of a tabbed interface will likely be familiar from applications such as Safari. To make a new window, choose File → New → Window (Command-Shift-T), or promote a tab to be a window by dragging it right out of its current window. You can also double-click (or Option-click) a file listing in the Project navigator; whether this opens a new tab or a new window depends on your settings in the Navigation pane of Xcode’s preferences.

Whether and how you use tabs and windows will depend on your personal tastes. There’s a lot of power and flexibility here if you need it. When I have a file I frequently want to refer to, I might spawn off a secondary window displaying that file, sized fairly small and without any panes other than the editor.

Project File and Dependents

The first item in the Project navigator (Command-1) represents the project itself. (In the Empty Window project that we created earlier in this chapter, it is called Empty Window.) Hierarchically dependent upon it are items that contribute to the building of the project. Many of the listings in the Project navigator correspond to items on disk in the project folder.

To survey this correspondence, let’s view our Empty Window project in two ways simultaneously — in the Project navigator in the Xcode project window, and in the project folder in a Finder window. Select the project listing in the Project navigator and choose File → Show in Finder. The Finder displays the contents of your project folder (Figure 6-6).

ios13 0607
Figure 6-6. The Project navigator (Xcode) and the project folder (Finder)

Contents of the Project Folder

The most important file in the project folder is Empty Window.xcodeproj. This is the project file, corresponding to the project listed first in the Project navigator. All Xcode’s knowledge about your project — what files it consists of and how to build the project — is stored in this file. To open a project from the Finder, double-click the project file. (Alternatively, you can drag the project folder onto Xcode’s icon in the Finder, the Dock, or the application switcher; Xcode will locate the project file and open it for you.)

The Project navigator displays groups (folder-like things) and files hierarchically from the project. Let’s consider how these correspond to reality on disk as portrayed in the Finder (Figure 6-6):

  • The Empty Window group corresponds directly to the Empty Window folder on disk. Groups in the Project navigator don’t necessarily correspond to folders on disk in the Finder, and folders on disk in the Finder don’t necessarily correspond to groups in the Project navigator. But in this case, they do correspond (this is a folder-linked group, as I’ll explain later).

  • Files within the Empty Window group, such as AppDelegate.swift, correspond to real files on disk that are inside the Empty Window folder. If you were to create additional code files (which, in real life, you would almost certainly do in the course of developing your project), you would likely put them in the Empty Window group in the Project navigator, and they, too, would then be in the Empty Window folder on disk. (However, your files can live anywhere and your project will still work fine.)

  • Two files in the Empty Window group, Main.storyboard and LaunchScreen.storyboard, appear in the Finder inside a folder that doesn’t visibly correspond to anything in the Project navigator, called Base.lproj. This arrangement has to do with localization, which I’ll discuss in Chapter 9.

  • The item Assets.xcassets in the Project navigator corresponds to a specially structured folder Assets.xcassets on disk. This is an asset catalog; you add resources to the asset catalog in Xcode, which maintains that folder on disk for you. I’ll talk more about the asset catalog later in this chapter, and in Chapter 9.

  • The Products group and its contents don’t correspond to anything in the project folder. Xcode generates a reference to the executable bundle generated by building each target in your project, and by convention these references appear in the Products group.

Now that you have inspected the contents of a typical project folder, you should have little need to open a project folder ever again, except in order to double-click the project file to open the project. Generally speaking, you should not manipulate the contents of a project folder by way of the Finder; manipulate the project in the project window. The project expects things in the project folder to be a certain way; if you make any alterations to the project folder directly in the Finder, behind the project’s back, you can upset those expectations and break the project. When you work in the project window, it is Xcode itself that makes any necessary changes in the project folder, and all will be well.

Groups

The purpose of groups in the Project navigator is to make the Project navigator work conveniently for you. So feel free to add further groups! If some of your code files have to do with a login screen that your app sometimes presents, you might clump them together in a Login group. If your app is to contain some sound files, you might put them into a Sounds group. And so on.

A group might or might not correspond to a folder on disk in the project folder. There’s a visual distinction: a group that corresponds to a folder on disk is a folder-linked group, and has a solid folder icon, like the Empty Window group in Figure 6-6; a group plain and simple exists purely within the Project navigator, and has a marked folder icon, like the Products group in Figure 6-6. You’ll encounter this distinction at various times:

Creating a group

When you make a new group, there’s a choice of menu items: in the contextual menu, you might see New Group and New Group With Folder. (Confusingly, the choice will sometimes be New Group and New Group Without Folder.) One creates a group plain and simple; the other creates a folder-linked group.

Using a group

When you place a file into a folder-linked group, it goes into the corresponding folder on disk (like the contents of the Empty Window folder in Figure 6-6). When you place a file into a group plain and simple, the group is effectively ignored in determining where the file will go; it generally will go into the same place as files at the same level as the group.

Renaming a group

To rename a group, select it in the Project navigator and press Return to make the name editable. When you rename a folder-linked group, the folder on disk is renamed as well.

The Target

A target is a collection of parts along with rules and settings for how to build a product from those parts. Whenever you build, what you’re building is a target (possibly more than one target).

Select the Empty Window project at the top of the Project navigator, and you’ll see the project itself along with its targets listed on the left side of the editor (Figure 6-7). Our Empty Window project comes with one target — the app target, called Empty Window (like the project itself). The app target is the target that you use to build and run your app. Its settings are the settings that tell Xcode how your app is to be built; its product is the app itself.

Under certain circumstances, you might add further targets to a project:

  • You might want to add unit tests or interface tests to your project. A test bundle is a target.

  • You might write an application extension, such as a today extension (content to appear in the notification center) or a photo editing extension (custom photo editing interface to appear in the Photos app). An extension is a target.

  • You might write a library, such as a custom framework, as part of your iOS app. A custom framework is a target.

The project name and the list of targets can appear in two ways (Figure 6-7): either as a column on the left side of the editor, or, if that column is collapsed to save space, as a pop-up menu at the top left of the editor. If, in the column or pop-up menu, you select the project, you edit the project; if you select a target, you edit the target.

ios9 0607b
Figure 6-7. Two ways of showing the project and targets

Build Phases

Edit the app target and click Build Phases at the top of the editor (Figure 6-8). These are the stages by which your app is built. The build phases are both a report to you on how the target will be built and a set of instructions to Xcode on how to build the target; if you change the build phases, you change the build process. Click each build phase to see a list of the files in your target to which that build phase will apply.

ios13 0608
Figure 6-8. The app target’s build phases

Two of the build phases have contents. The meanings of these build phases are pretty straightforward:

Compile Sources

Certain files (your code) are compiled, and the resulting compiled code is copied into the app. This build phase typically applies to all of the target’s .swift files. Sure enough, it currently contains all three Swift files supplied by the app template when we created the project.

Copy Bundle Resources

Certain files are copied into the app, so that your code or the system can find them there when the app runs. This build phase currently applies to the asset catalog; any resources you add to the asset catalog will be copied into your app as part of the catalog. It also applies to your launch storyboard file, LaunchScreen.storyboard, and your app’s interface storyboard file, Main.storyboard.

Copying doesn’t necessarily mean making an identical copy. Certain types of file are automatically treated in special ways as they are copied into the app bundle. Copying the asset catalog means that icons in the catalog are written out to the top level of the app bundle, and that the asset catalog itself is transformed into a .car file; copying a .storyboard file means that it is transformed into a .storyboardc file, which is itself a bundle containing nib files.

You can alter these lists manually, and sometimes you may need to do so. For instance:

  • If something in your project, such as a sound file, is not in Copy Bundle Resources and you want it copied into the app during the build process, drag it from the Project navigator into the Copy Bundle Resources list, or (easier) click the Plus button beneath the Copy Bundle Resources list to get a helpful dialog listing everything in your project.

  • Conversely, if something in your project is in the Copy Bundle Resources list and you don’t want it copied into the app, delete it from the list; this will not delete it from your project, from the Project navigator, or from the Finder, but only from the list of things to be copied into your app.

Build Settings

Build phases are only one aspect of how a target knows how to build the app. The other aspect is build settings. To see them, edit the target and click Build Settings at the top of the editor (Figure 6-9). Here you’ll find a long list of settings, most of which you’ll never touch. Xcode examines this list in order to know what to do at various stages of the build process. Build settings are the reason your project compiles and builds the way it does.

ios13 0609
Figure 6-9. Target build settings

You can determine what build settings are displayed by clicking Basic or All. The settings are combined into categories, and you can close or open each category heading to save room. To locate a setting quickly based on something you already know about it, such as its name, use the search field at the top right to filter what settings are shown.

You can determine how build settings are displayed by clicking Combined or Levels; in Figure 6-9, I’ve clicked Levels, in order to discuss what levels are. It turns out that not only does a target contain values for the build settings, but the project also contains values for the same build settings; furthermore, Xcode has certain built-in default build setting values. The Levels display shows all of these levels at once, so you can trace the derivation of the actual values used for every build setting.

To understand the chart, read from right to left. For example, the iOS default for the Build Active Architecture Only setting’s Debug configuration (far right) is No. But then the project comes along (second column from the right) and sets it to Yes. The target (third column from the right) doesn’t change that setting, so the result (fourth column from the right) is that the setting resolves to Yes.

You will rarely have occasion to manipulate build settings directly, as the defaults are usually acceptable. Nevertheless, you can change build setting values, and this is where you would do so. You can change a value at the project level or at the target level. You can select a build setting and show Quick Help in the Utilities pane to learn more about it; for further details on what the various build settings are, choose Help → Xcode Help and consult the build settings reference (click Show Topics and look under Reference at the left).

Configurations

There are actually multiple lists of build setting values — though only one such list applies when a particular build is performed. Each such list is called a configuration. Multiple configurations are needed because you build in different ways at different times for different purposes, and you’ll want certain build settings to take on different values under different circumstances.

By default, there are two configurations:

Debug

This configuration is used throughout the development process, as you write and run your app.

Release

This configuration is used for late-stage testing, when you want to check performance on a device, and for archiving the app to be submitted to the App Store.

Configurations exist at all because the project says so. To see where the project says so, edit the project and click Info at the top of the editor (Figure 6-10). Note that these configurations are just names. You can create additional configurations, and when you do, you’re just adding to the list of names. The importance of configurations emerges only when those names are coupled with build setting values. Configurations can affect build setting values both at the project level and at the target level.

ios13 0610
Figure 6-10. Configurations

For example, return to the target build settings (Figure 6-9) and type “optim” into the search field. Now you can look at the Optimization Level build setting (Figure 6-11):

  • The Debug configuration value for Optimization Level is No Optimization: while you’re developing your app, you build with the Debug configuration, so your code is just compiled line by line in a straightforward way.

  • The Release configuration value for Optimization Level is Optimize for Speed. When your app is ready to ship, you build it with the Release configuration, so the resulting binary is optimized for speed, which is great for your users running the app on a device, but would be no good while you’re developing the app because breakpoints and stepping in the debugger wouldn’t work properly. Compilation may take longer when the compiler must optimize for speed, but you won’t mind the delay, because you won’t do a Release build very often.

ios13 0611
Figure 6-11. How configurations affect build settings

Schemes and Destinations

So far, I have not said how Xcode knows which configuration to use during a particular build. This is determined by a scheme.

A scheme unites a target (or multiple targets) with a build configuration, with respect to the purpose for which you’re building. A new project comes by default with a single scheme, named after the project. The Empty Window project’s single scheme is currently called Empty Window. To see it, choose Product → Scheme → Edit Scheme. The scheme editor dialog opens (Figure 6-12).

ios13 0612
Figure 6-12. The scheme editor

On the left side of the scheme editor are listed various actions you might perform from the Product menu. Click an action to see its corresponding settings in this scheme.

The first action, the Build action, is different from the other actions, because it is common to all of them — the other actions all implicitly involve building. The Build action merely determines what target(s) will be built when each of the other actions is performed. For our project this means that the app target is always to be built, regardless of the action you perform.

The second action, the Run action, determines the settings that will be used when you build and run. The Build Configuration pop-up menu (in the Info pane) is set to Debug. That explains where the current build configuration comes from: whenever you build and run (Product → Run, or click the Run button in the toolbar), you’re using the Debug build configuration and the build setting values that correspond to it, because you’re using this scheme, and that’s what this scheme says to do when you build and run.

You can edit an existing scheme, and this can be useful especially as a temporary measure for doing certain kinds of specialized debugging. For example, the Run action’s Diagnostics tab contains checkboxes that let you turn on the Address Sanitizer or the Thread Sanitizer, useful for tracking down certain types of obscure runtime error. You’d check the checkbox, build and run, work on the error, and then uncheck the checkbox again.

Alternatively, you can add a scheme. A typical approach is to duplicate an existing scheme and then modify the duplicate. Instead of changing your main scheme to turn on the Address Sanitizer temporarily, you might have a second scheme where the Address Sanitizer is always turned on; you would then use the Address Sanitizer by switching schemes.

Handy access to schemes and their management is through the Scheme pop-up menu in the project window toolbar (Figure 6-13).

ios13 0613
Figure 6-13. The Scheme pop-up menu

The Scheme pop-up menu is something you’re going to be using a lot. Your schemes are listed here; hierarchically appended to each scheme are the destinations. A destination is effectively a machine that can run your app. On any given occasion, you might want to run the app on a physical device or in the Simulator — and, if in the Simulator, you might want to specify that a particular type of device should be simulated. To make that choice, pick a destination in the Scheme pop-up menu.

Destinations and schemes have nothing to do with one another. The presence of destinations in the Scheme pop-up menu is just a convenience, letting you choose a scheme or a destination or both in a single move. To switch easily among destinations without changing schemes, click the destination name in the Scheme pop-up menu. To switch among schemes, possibly also determining the destination (as shown in Figure 6-13), click the scheme name in the Scheme pop-up menu. You can open the Scheme pop-up menu with Control-0 (zero), and the Destination pop-up menu with Control-Shift-0; the menu can then be navigated with the keyboard, and is also filterable in the same way as the jump bar (discussed earlier in this chapter).

Each simulated device has a system version that is installed on that device. At the moment, all our simulated devices are running iOS 13; there is no distinction to be drawn, and the system version is not shown. But you can download additional SDKs (systems) in Xcode’s Components preference pane. If you do, and if your app can run under more than one system version, you might also see a system version listed in the Scheme pop-up menu as part of a Simulator destination name.

To manage destinations, choose Window → Devices and Simulators. Switch to the Simulators pane if necessary. This is where you govern what simulated devices exist. Here you can create, delete, and rename simulated devices, and specify whether a simulated device actually appears as a destination in the Scheme pop-up menu.

From Project to Built App

Now that you know what’s in a project, I’m going to summarize how Xcode builds that project into an app. Let’s first jump ahead and examine the end product — the app itself.

What is an app anyway? It’s actually a special kind of folder called a package (and a special kind of package called a bundle). The Finder normally disguises a package as a file and does not dive into it to reveal its contents to the user, but you can bypass this protection and investigate an app bundle with the Show Package Contents command. By doing so, you can study the internal structure of your built app bundle.

We’ll use the Empty Window app that we built earlier as a sample minimal app to investigate. Open the Products group in the Project navigator, Control-click the app listing, and choose Show in Finder. In the Finder, Control-click the Empty Window app, and choose Show Package Contents. Here you can see the results of the build process (Figure 6-14).

ios13 0615
Figure 6-14. Contents of the app package

Think of the app bundle as a transformation of the project folder. Here are some of the things it contains, and how they relate to what’s in the project folder:

Empty Window

Our app’s compiled code. The build process has compiled all our Swift files into this single file, our app’s binary. This is the heart of the app, its actual executable material.

Main.storyboardc

Our app’s interface storyboard file. The project’s Main.storyboard is currently where our app’s interface comes from — in this case, an empty white view occupying the entire window. The build process has compiled Main.storyboard into a tighter format, resulting in a .storyboardc file, which is actually a bundle of nib files to be loaded as required while the app runs. One of these nib files, loaded as our app launches, will be the source of the hitherto empty view displayed in the interface. Main.storyboardc sits in the same Base.lproj subfolder as Main.storyboard does in the project folder; as I said earlier, this folder structure has to do with localization (to be discussed in Chapter 9).

LaunchScreen.storyboardc

This is the compiled version of LaunchScreen.storyboard, containing the interface that will be displayed briefly during the time it takes for our app to launch (the launch screen).

Assets.car, AppIcon60x60@2x.png

An asset catalog and an icon file. In preparation for this build, I added an icon image to the original asset catalog, Assets.xcassets. The build process has compiled this file, resulting in a compiled asset catalog file (.car) containing any resources that have been added to the catalog; at the same time, the icon file has been written out to the top level of the app bundle.

Info.plist

A configuration file in a strict text format (a property list file). It is derived from, but is not identical to, the project’s Info.plist. It contains instructions to the system about how to treat and launch the app. For example, the project’s Info.plist has a calculated bundle name derived from the product name, $(PRODUCT_NAME); in the built app’s Info.plist, this calculation has been performed, and the value reads Empty Window, which is why our app is labeled “Empty Window” on the device. Also, in conjunction with the asset catalog writing out our icon file to the app bundle’s top level, a setting has been added to the built app’s Info.plist telling the system the name of that icon file, so that the system can find it and display it as our app’s icon.

Frameworks

The built app contains no frameworks. That’s new in Swift 5, and stands in sharp contrast to what used to happen; in the past, several megabytes of framework files were added to the app, containing the entirety of the Swift language! One of the great overarching achievements of Swift 5 is ABI stability, which means, in practical terms, that the Swift frameworks can be moved off into the system, reducing the size and overhead of your built apps. However, that’s only on iOS 13. If you were to build this app for an earlier system, those framework files would return, and the app package would look more like Figure 6-15.

ios13 0615b
Figure 6-15. Contents of the app package, old style

In real life, an app bundle may contain more files, but the difference will be mostly one of degree, not kind. Our project might have additional .storyboard or .xib files, additional frameworks, or additional resources such as sound files. All of these would make their way into the app bundle. Also, an app bundle built to run on a device will contain some security-related files.

You are now in a position to appreciate, in a general sense, how the components of a project are treated and assembled into an app, and what responsibilities accrue to you, the programmer, in order to ensure that the app is built correctly. The rest of this section outlines what goes into the building of an app from a project.

Build Settings

We have already talked about how build settings are determined. Xcode itself, the project, and the target all contribute to the resolved build setting values, some of which may differ depending on the build configuration. You, the programmer, will have specified a scheme before building; the scheme determines the build configuration, meaning the specific set of build setting values that will apply as this build proceeds.

Property List Settings

Your project contains a property list file that will be used to generate the built app’s Info.plist file. The file in the project does not have to be named Info.plist! The app target knows what file it is because it is specified in the Info.plist File build setting. In our project, the value of the app target’s Info.plist File build setting is Empty Window/Info.plist.

The property list file is a collection of key–value pairs. You can edit it, and you may need to do so. There are three main ways to edit your project’s Info.plist:

  • Edit the target, and switch to the General pane. Some of the settings here are effectively ways of editing the Info.plist. For example, when you click a Device Orientation checkbox here, you are changing the value of the “Supported interface orientations” key in the Info.plist.

  • Edit the Info.plist file manually by selecting it in the Project navigator. The editor displays a special .plist editor interface. By default, most of the key names (and some of the values) are displayed descriptively, in terms of their functionality; for example, it says “Bundle name” instead of the actual key, which is CFBundleName. To view the actual keys, choose Editor → Show Raw Keys & Values, or use the contextual menu.

    If you like, you can see the file in its true XML text form: Control-click the Info.plist file in the Project navigator and choose Open As → Source Code from the contextual menu. (But editing an Info.plist as raw XML is risky, because if you make a mistake you can invalidate the XML, causing things to break with no warning.)

  • Edit the target, and switch to the Info pane. The Custom iOS Target Properties section shows effectively the same information as editing the Info.plist in the editor.

Some values in the project’s Info.plist are processed at build time to transform them into their final values in the built app’s Info.plist. For example, the “Executable file” key’s value in the project’s Info.plist is $(EXECUTABLE_NAME); for this will be substituted the value of the EXECUTABLE_NAME build environment variable, supplied by Xcode at build time. Also, some additional key–value pairs will be injected into the Info.plist during processing.

For a complete list of the possible keys and their meanings, consult Apple’s Information Property List Key Reference in the documentation archive (see Chapter 8). I’ll talk more in Chapter 9 about some Info.plist settings that you’re particularly likely to edit.

Nib Files

A nib file is a file with the extension .nib containing a description of a piece of user interface in a compiled format. You edit a .xib or .storyboard file graphically, as in a drawing program. Your .xib and .storyboard files are then transformed into nib files during the build process by compilation. This compilation takes place by virtue of the .storyboard or .xib file being listed in the app target’s Copy Bundle Resources build phase. A .xib file results in a single nib file; a .storyboard file results in a .storyboardc bundle containing multiple nib files.

Our Empty Window project generated from the Single View App template contains an interface .storyboard file called Main.storyboard. This is our app’s main storyboard — not because of its name, but because the Info.plist file says so, under the key “Main storyboard file base name” (UIMainStoryboardFile). I’ll talk more about the main storyboard later in this chapter; in Chapter 7 I’ll explain how nib files create instances when your code runs.

Resources

Resources are ancillary files embedded in your app bundle to be extracted as needed while the app runs. At some point during your app’s lifetime you might want to display some images, or play some sound files; to do so, you can include these files in your app bundle. In effect, your app bundle is being treated as a folder full of extra stuff.

There are two different places to add resources to your project in Xcode:

The Project navigator

If you add a resource to the Project navigator, it is copied by the build process to the top level of your app bundle (assuming that it is also listed in the Copy Bundle Resources build phase).

An asset catalog

If you add a resource to an asset catalog, then when the asset catalog is copied and compiled by the build process to the top level of your app bundle, the resource will be embedded inside it.

Resources in the Project navigator

To add a resource to your project through the Project navigator, choose File → Add Files to [Project], or drag the resource from the Finder into the Project navigator. A dialog appears (Figure 6-16):

ios9 0616
Figure 6-16. Options when adding a resource to a project
Copy items if needed

Check this checkbox so that the resource is copied into the project folder. (Otherwise, your project will depend on a file that’s outside the project folder, where you might delete or change it unintentionally; keeping all of your project’s contents inside the project folder is far safer.)

Added folders

If what you’re adding to the project is a folder, these choices determine how the project references the folder contents:

Create groups

The folder’s name becomes the name of a folder-linked group within the Project navigator, and its contents appear in this group; but the folder contents are listed individually in the Copy Bundle Resources build phase and are copied individually to the top level of the app bundle.

Create folder references

The folder becomes a folder reference. It is shown in blue in the Project navigator, and the folder itself is listed in the Copy Bundle Resources build phase. The build process will copy the folder itself, along with its contents, into the app bundle; the resources won’t be at the top level of the app bundle, but in a subfolder within the app bundle. Your code for accessing a resource will have to specify the subfolder.

Add to targets

This checkbox determines whether the resource is added to a target’s Copy Bundle Resources build phase. If your purpose is to make this resource available to the app when it runs, make sure the app target is checked. If your purpose is merely to use the project folder as convenient storage for something that isn’t part of your app, the checkbox should not be checked. Don’t worry; you can always change this setting later by editing the target’s Copy Bundle Resources build phase.

Warning

Don’t accidentally store a code file in a folder reference; if you do, the compiler won’t be able to see it. Organize code files using groups, not folder references.

Resources in an asset catalog

Asset catalogs were invented originally to accommodate image files; they can now contain any kind of data file. Keeping your resources in an asset catalog provides certain advantages over keeping them at the top level of the app bundle.

For example, you might need two or three versions of an image file, corresponding to the single-, double-, and triple-resolution screens of target devices; the asset catalog provides resolution slots to make that easy (Figure 6-17). The asset catalog can perform certain transformations on an image as it is loaded. And asset catalog images load more efficiently, because they are stored in a special format.

ios12 0617
Figure 6-17. Slots for an image set in the asset catalog

Asset catalogs can also hold named colors, Sprite Kit textures, and general data objects. Different versions of the same asset can load in response to device type, light or dark mode, and localization. An asset catalog can contain “folders” that subdivide the assets between namespaces, and multiple asset catalogs can be distinguished by putting them in different bundles (such as frameworks).

New in Xcode 11, the Development Assets build setting lets you specify a path for resources that won’t be included in an archive build (meaning a build to be distributed to other users, as I’ll explain in “Distribution”). In this way your app can include resources such as default data during development and testing, without those resources polluting the final built app. These can be individual resources or entire asset catalogs. A neat approach is a folder-linked group in the Project navigator: put your development-only resources into that group, and specify the path to the corresponding folder in Development Assets.

Code Files

The build process compiles a code file into the app’s binary if it is listed in the app target’s Compile Sources build phase. The Swift files provided by the app template are listed under Compile Sources already. As you develop a real app, you’ll create new Swift files by choosing File → New → File; a Save dialog will appear, offering to make this file part of the app target, and if you accept, the file will be added to the app target’s Compile Sources build phase. If you get this wrong, your code probably won’t compile, because the compiler won’t see the newly added Swift file. Don’t worry; you can fix this later by editing the Compile Sources build phase.

When you create a new file using the Cocoa Touch Class template, you get some boilerplate code for free. A file template might import the UIKit framework and write the initial class declaration for you, and in the case of some commonly subclassed superclasses, such as UIViewController and UITableViewController, it even provides stub declarations of some of that class’s methods. You can add custom file templates by placing them in ~/Library/Developer/Xcode/Templates.

Frameworks, SDKs, and Packages

A framework is a library of compiled code used by your code. Most of the frameworks you are likely to use when programming iOS will be Apple’s built-in frameworks. These frameworks are the locus of all the stuff that every app might need to do; they are Cocoa. That’s a lot of stuff, and a lot of compiled code. Your app gets to share in the goodness and power of the frameworks because it is hooked up to them. Your code works as if the framework code were incorporated into it. Yet your app is relatively tiny; it’s the frameworks that are huge.

The Cocoa frameworks are already part of the system on the device where your app will run; they live in /System/Library/Frameworks on the device, though you can’t tell that because there’s no way (normally) to inspect a device’s file hierarchy directly.

Your compiled code also needs to be connected to those frameworks when the project is being built and run on your computer. To make this possible, the iOS device’s /System/Library/Frameworks is duplicated on your computer, inside Xcode itself. This duplicated subset of the device’s system is called an SDK (for “software development kit”). Which SDK is used depends upon what destination you’re building for.

The process of hooking up your compiled code with the frameworks that it needs, whether on your computer or on an actual device, is called linking. Linking takes care of connecting your compiled code to any needed frameworks, but your code also needs to be able to compile in the first place. The frameworks are full of classes and methods that your code will call. To satisfy the compiler, the frameworks publish their API in header files, which your code can import. Your code can speak of NSString and can call range(of:) because it imports the NSString header. Actually, what your code imports is the UIKit header, which in turn imports the Foundation header, which in turn imports the NSString header, which declares the range(of:) method.

Using a framework is therefore a two-stage process. Your code must import the framework’s header in order to compile, and it must link to the framework’s binary so that your code can communicate with the framework’s code at runtime. Luckily, Swift’s use of modules simplifies the importing and linking process (as well as improving compilation times). A Swift import statement takes care of everything. The import UIKit statement at the top of our project’s code files imports the UIKit framework’s header files and allows your code to compile; then, at build time, it also causes linkage with the UIKit framework.

A custom framework can be a useful way to subdivide your code into modules. Also, a framework is a bundle, so it can include resources that are referenced by specifying that bundle. Here’s how to create a framework in your project:

  1. Edit the target and choose Editor → Add Target.

  2. Select iOS. Under Framework & Library, select iOS Framework. Click Next.

  3. Give your framework a name; let’s call it MyCoolFramework. You can pick a language, but I’m not sure this makes any difference, as no code files will be created. The Project and Embed in Application pop-up menus should be correctly set by default. Click Finish.

A new MyCoolFramework target is created in your project. If you now add a Swift file to the MyCoolFramework target, and inside it define an object type and declare it public, then, back in one of your main app target’s files, such as AppDelegate.swift, your code can import MyCoolFramework and will then be able to see that object type and its public members.

New in Xcode 11, you can create a package. A package is simpler and more efficient than a framework; it’s just a collection of source code, and doesn’t need linking:

  1. In your project (such as our Empty Window project), choose File → New → Swift Package.

  2. Give the package a name, such as MyCoolPackage. At the bottom of the Save dialog, specify that you want to add this package to the existing project (Empty Window), and make sure you’re adding it at the top level of the project, not inside any group. Click Create.

  3. The initial package files appear in the Project navigator, but this module is not yet available to the app target. Edit the target; in the General pane, under Frameworks, Libraries, and Embedded Content, click Plus and choose the package in the dialog (Figure 6-18). Click Add.

ios13 0618
Figure 6-18. Adding a local package to the app target

In your app target’s code, you can now import MyCoolPackage to access public types declared in the package. You can add source files to the MyCoolPackage group inside Sources.

What we’ve created is a local package. But one of the key features of packages is that they can be made public; you can upload your package to GitHub, and other programmers can incorporate it into their projects. Sharing a package with others is easy, because a package is just source files. Distributing a framework to others is more complicated, both for you and for the recipients of your framework. On the other hand, a framework is a bundle, so it can include resources, such as a storyboard, an asset catalog, or image files; a package can’t.

The App Launch Process

When the user launches your app, or when you launch it by building and running it in Xcode, a lot needs to happen. Your app needs some initial instances and an initial interface, and at least some of your code needs an opportunity to run.

The Entry Point

When the app launches, the system knows where to find the compiled binary inside the app’s bundle, because the app bundle’s Info.plist file has an “Executable file” key (CFBundleExecutable) whose value is the name of the binary; by default, the binary’s name comes from the EXECUTABLE_NAME environment variable (such as “Empty Window”).

The system locates and loads the binary and links any needed frameworks. Now it must call into the binary’s code to start it running. But where?

If this app were an Objective-C program, the answer would be clear. Objective-C is C, so the entry point is the main function. Our project would typically have a main.m file containing the main function, like this:

int main(int argc, char *argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil,
            NSStringFromClass([AppDelegate class]));
    }
}

The main function does two things:

  • It sets up a memory management environment — the @autoreleasepool and the curly braces that follow it.

  • It calls the UIApplicationMain function, which helps your app pull itself up by its bootstraps and get running.

Our app, however, is a Swift program. It has no main function! Instead, Swift has a special attribute: @UIApplicationMain. You can see it in the AppDelegate.swift file, attached to the declaration of the AppDelegate class:

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

This attribute essentially does everything that the Objective-C main.m file was doing: it creates an entry point that calls UIApplicationMain to get the app started.

It would be very unusual for you to give your Swift app project a main file. But you are free to do so. Delete the @UIApplicationMain attribute and instead create a main.swift file, making sure it is added to the app target. The name is crucial, because a file called main.swift gets a special dispensation: it is allowed to put executable code at the top level of the file (Chapter 1)! The file should contain essentially the Swift equivalent of the Objective-C call to UIApplicationMain, like this:

import UIKit
UIApplicationMain(
    CommandLine.argc, CommandLine.unsafeArgv, nil,
        NSStringFromClass(AppDelegate.self)
)

Regardless of whether you write your own main.swift file or you rely on the Swift @UIApplicationMain attribute, you are calling UIApplicationMain. This one function call is the primary thing your app does. Your entire app is really nothing but a single gigantic call to UIApplicationMain! Moreover, UIApplicationMain is responsible for solving some tricky problems as your app gets going. Where will your app get its initial instances? What instance methods will initially be called on those instances? Where will your app’s initial interface come from? UIApplicationMain to the rescue!

How an App Gets Going

Here’s what happens when your app launches and UIApplicationMain is called. The sequence I’m describing is new in iOS 13, for a project created with an Xcode 11 app template (with a UIWindowSceneDelegate). I will assume that your app supports scenes; the runtime knows this because the Info.plist contains an “Application Scene Manifest” dictionary. I will also assume (for now) that your app has a main storyboard:

  1. UIApplicationMain creates the shared application instance, subsequently accessible to your code as UIApplication.shared. The default class is UIApplication; it is possible to specify a different class, but it is unlikely that you’d need to do so.

  2. UIApplicationMain creates the application instance’s delegate. With an explicit call to UIApplicationMain, the fourth argument specifies, as a string, what the class of the app delegate instance should be; in the main.swift file I described earlier, that specification is NSStringFromClass(AppDelegate.self). When we use the @UIApplicationMain attribute, that attribute is part of the AppDelegate class declaration in AppDelegate.swift, and means: “This is the app delegate class!”

  3. If this app supports scenes, UIApplicationMain turns to the app delegate and, for the first time, runs some of your code: it calls application(_:didFinishLaunchingWithOptions:). This is a place for you to perform certain initializations.

  4. UIApplicationMain creates a UISceneSession, a UIWindowScene, and your app’s window scene delegate. The Info.plist typically specifies, as a string, what the class of the window scene delegate instance should be. In the app template, it is the SceneDelegate class, which is declared in SceneDelegate.swift; in the Info.plist, this is written as $(PRODUCT_MODULE_NAME).SceneDelegate to take account of Swift “name mangling” (see Appendix A).

  5. If there is a storyboard associated with this scene, as specified by the Info.plist, UIApplicationMain loads it and looks inside it to find the view controller designated as this storyboard’s initial view controller (or storyboard entry point); it instantiates this view controller, a UIViewController subclass. In our app template, the app’s main storyboard, Main.storyboard, is the initial scene’s storyboard; in that storyboard, the initial view controller is an instance of the ViewController class, which is declared in ViewController.swift.

  6. UIApplicationMain creates your app’s window. This window is assigned to the scene delegate’s window property. UIApplicationMain then assigns the initial view controller instance to the window instance’s rootViewController property. This view controller is now the app’s root view controller.

  7. UIApplicationMain causes your app’s interface to appear, by calling the UIWindow instance method makeKeyAndVisible.

  8. The window is about to appear. This causes the window to turn to the root view controller and tell it to obtain its main view. If this view controller gets its view from a nib file, that nib is loaded and its objects are instantiated and initialized (as I’ll describe in Chapter 7). The view controller’s viewDidLoad is then called — another early opportunity for your code to run. Finally, the root view controller’s main view is placed into the window, where it and its subviews are visible to the user.

More of your code can run at this time (some further app delegate and scene delegate methods are called if they are implemented), but basically the app is now up and running, with an initial set of instances and a visible interface. UIApplicationMain is still running (like Charlie on the M.T.A., UIApplicationMain never returns), and is just sitting there, watching for the user to do something, maintaining the event loop, which will respond to user actions as they occur. Henceforth, your app’s code will be called only in response to Cocoa events (as I’ll explain in Chapter 11).

App Without a Storyboard

In the preceding description of the app launch process, I assume that the app has a main storyboard. It is possible, however, not to have a main storyboard. Without a main storyboard, things like creating a window instance, assigning it to the window property, creating an initial view controller, assigning that view controller to the window’s rootViewController property, and calling makeKeyAndVisible on the window to show the interface, must be done by your code.

Let’s try it. Make a new project starting with the Single View App template; call it Truly Empty. Now follow these steps:

  1. Edit the target. In the General pane, select “Main” in the Main Interface field and delete it (and press Tab to set this change).

  2. In the Info.plist, select the “Storyboard Name” entry in the “Application Scene Configuration” dictionary (Figure 6-19) and press Delete (and save).

  3. Optionally, in the Project navigator, delete Main.storyboard from the project. You don’t have to do this, because even if Main.storyboard remains, it will now be ignored.

  4. In SceneDelegate.swift, edit scene(_:willConnectTo:options:) to look like Example 6-1.

ios13 0618b
Figure 6-19. The line to be deleted from the Info.plist
Example 6-1. A scene delegate with no storyboard
func scene(_ scene: UIScene,
           willConnectTo session: UISceneSession,
           options connectionOptions: UIScene.ConnectionOptions) {
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.backgroundColor = .white
        window.rootViewController = ViewController()
        self.window = window
        window.makeKeyAndVisible()
    }
}

The result is a minimal working app with an empty white window. You can prove to yourself that the app is working normally by editing ViewController.swift so that its viewDidLoad method changes the main view’s background color:

override func viewDidLoad() {
    super.viewDidLoad()
    self.view.backgroundColor = .red
}

Run the app again; sure enough, the background is now red.

This architecture is useful when your intention is to create the entire interface in code. That’s what a SwiftUI project does. If you create a project from the Single View App template with the User Interface pop-up menu set to SwiftUI, you’ll find that it is structured just like the project we just made, except that it has a SwiftUI ContentView struct; its scene(_:willConnectTo:options:) contains this code:

let contentView = ContentView()
if let windowScene = scene as? UIWindowScene {
    let window = UIWindow(windowScene: windowScene)
    window.rootViewController = UIHostingController(rootView: contentView)
    self.window = window
    window.makeKeyAndVisible()
}

In between an app with a main storyboard and an app without a main storyboard, there is a hybrid architecture where there’s a main storyboard (you omit steps 1, 2, and 3 in the earlier example) but you sometimes ignore it at launch time (step 4). A common use case would be an app with a sign-in screen that should appear when the user first launches the app (you create the sign-in view controller manually), but once the user has signed in, that screen shouldn’t appear on any future launch (you let the main storyboard construct the interface).

Renaming Parts of a Project

The name you give your project at creation time is used in many places throughout the project. Beginners may worry that they can never rename a project without breaking something. But in fact it’s not a problem.

In the first place, you probably don’t need to rename the project. The project name isn’t something the user will ever see, so what does it matter? Typically, what you want to change is the name of the app — the name that the user sees on the device, associated with this app’s icon. To do so, change (or create) the “Bundle Display Name” in the Info.plist; you can do this most easily by editing the Display Name text field at the top of the General pane when you edit the target (see “Property List Settings”).

If you really do want to rename the project, select the project listing at the top of the Project navigator, press Return to make its name editable, type the new name, and press Return again. Xcode presents a dialog proposing to change some other names to match, including the app target and the built app and, by implication, various relevant build settings.

Everything that needs to change changes coherently when you rename the project in this way. The only thing that isn’t changed is the scheme name; there is no particular need to change it, but you can do so: choose Product → Manage Schemes and click the scheme name to make it editable.

You can change the name of the project folder in the Finder at any time, and you can move the project folder in the Finder at will, because all build setting references to file and folder items in the project folder are relative.

Warning

Be careful about changing the name of a folder-linked group. When you do that, Xcode automatically changes the name of the corresponding folder on disk, but does not change build settings that depend upon the name of that folder, such as the Info.plist File build setting. I regard this as a bug, because it means that changing a group’s name can prevent your project from building. However, it usually isn’t hard to fix the problem by changing manually any build settings that have broken.