Welcome to the Ultimate SwiftUI Series. Before you can build anything, you need a workshop -- and for iOS development, that workshop is Xcode. This first chapter is deliberately unglamorous: you're going to install your tools, make sure they actually run, and learn your way around the room before you start swinging a hammer.
It's tempting to skip the setup chapter and jump straight to building screens. Resist that urge. A few minutes spent learning where things live and which keyboard shortcuts matter will save you hours of fumbling later. Developers who feel fast in Xcode aren't smarter -- they've just internalized the layout and the shortcuts. By the end of this chapter you'll have a working project, you'll know what every pane does, and you'll have run your own app on a simulated iPhone.
What You Need
SwiftUI development happens on a Mac. There's no way around it -- the toolchain, the Simulator, and the SwiftUI preview canvas are all Mac-only.
- A Mac. Any reasonably recent Mac works. Apple Silicon (M1 and later) compiles noticeably faster than Intel, but either is fine for learning.
- macOS recent enough for the canvas. The live SwiftUI preview canvas needs macOS Catalina (10.15) or later. In practice you'll want to be on a current macOS release so you can install a current Xcode.
- Administrator access. Installing Xcode requires an account with administrator privileges.
- Free disk space. Budget at least 12 GB free before you start. Xcode itself is a multi-gigabyte download, and the iOS components, simulators, and your own build artifacts add up quickly.
Xcode bundles the compiler, the debugger, documentation, and platform SDKs for every Apple platform. On top of that, each simulated device you download is its own disk image, and every time you build a project Xcode caches intermediate files. It's normal for a working setup to consume tens of gigabytes over time.
Installing Xcode
Xcode is free. Open the App Store app, search for Xcode, and click Get. It's a large download, so this is a good moment to get a coffee -- on a slower connection it can take a while.
When the download finishes, open Xcode the same way you'd open any Mac app: double-click it in Applications, find it with Spotlight, or click Open from its App Store page. (Later, double-clicking a project's .xcodeproj file will launch Xcode straight into that project.)
The very first launch does some extra work. Xcode ships with macOS support built in, but it needs to download the iOS platform component separately. You'll see a component picker -- select iOS, click Download & Install, and enter your Mac password when prompted. When it finishes, Xcode asks you to relaunch.
Between the component download and the first time it prepares an iOS Simulator, Xcode does a lot of one-time setup. If it seems to hang on a fresh install, give it a few minutes before assuming something's wrong. This only happens once.
After relaunching, you land on the Welcome to Xcode window. It offers to create a new project, clone an existing one, or open something recent. You can always summon this window again from Window ▸ Welcome to Xcode, or with Shift-Command-1. Every action in that window also has an equivalent in the File menu, so use whichever you prefer.
Creating Your First Project
Let's make a throwaway project to explore. Click Create New Project… in the Welcome window. (The keyboard-only path is Shift-Command-N, or File ▸ New ▸ Project….)
Xcode shows a grid of project templates grouped by platform. You want iOS ▸ App. Select it and click Next.
Now you choose your project's options:
- Product Name: type
MyFirst. This becomes the app's name. - Team: leave it as None for now. You only need a team once you're signing apps to run on a physical device or submitting to the App Store.
- Organization Identifier: the reverse-DNS form of a domain you control, like
com.yourname. If you don't own a domain, invent something consistent such asorg.audrey. Xcode combines this with the product name to form the Bundle Identifier (com.yourname.MyFirst), which uniquely identifies your app across the entire App Store. - Interface: choose SwiftUI. (The alternative, Storyboard, is the older UIKit approach -- not what this series is about.)
- Language: choose Swift.
- Testing System and Storage: choose None for this throwaway project.
Click Next. Xcode asks where to save the project.
If you ever lose track of a project's location, open it and choose File ▸ Show in Finder, or look under File ▸ Open Recent. Xcode remembers everything you've opened lately.
On this save screen there's a Source Control checkbox. If you're saving somewhere that isn't already a Git repository, tick it -- Xcode will initialize a local Git repo for you. Version control from line one is a good habit, and we'll connect this to a remote repository later in the chapter.
Click Create. Your project opens with ContentView.swift showing in the editor, and the iOS Simulator quietly downloading in the background.
That first look at Xcode can be overwhelming -- there are buttons and panels everywhere. Here's the reassuring truth: almost no professional iOS developer knows what every button does. You learn the 20% you use daily, and you look up the rest when you need it. Apple also changes Xcode every year, so "knowing all of it" isn't even a stable target. Let's learn that essential 20% now.
A Tour of the Xcode Window
The Xcode window is organized into three main panes, plus two that appear when you need them:
- Navigator (left): lists of files, search results, issues, and more.
- Editor (center): where you read and write code -- and, for SwiftUI files, where the live preview canvas sits beside your code.
- Inspectors (right): details and attributes about whatever you've selected.
- Debug Area (bottom of the editor): appears while your app runs, showing console output and runtime data.
You can hide or show each pane to reclaim screen space. The toolbar has buttons for the Navigator and Inspectors, the Debug Area has its own toggle, and you can always drag a pane's border to the window edge to collapse it. The shortcuts are worth memorizing on day one:
- Command-0 -- toggle the Navigator
- Option-Command-0 -- toggle the Inspectors
- Shift-Command-Y -- toggle the Debug Area
The Navigator and its nine tabs
The Navigator isn't one list -- it's nine, selectable by the icons across its top bar. When the Navigator is hidden, pressing Command-1 through Command-9 opens it directly to a specific tab. From left to right:
- Project -- your files and folders. Add, delete, group, and open files here. This is the tab you'll live in.
- Source Control -- Git working copies, branches, commits, tags, remotes, and stashes.
- Bookmark -- code landmarks and tasks you've flagged.
- Find -- project-wide search and replace.
- Issue -- compiler errors and warnings, plus runtime issues.
- Test -- create, organize, and run your unit and UI tests.
- Debug -- live CPU, memory, disk, and network usage while the app runs.
- Breakpoint -- manage all your breakpoints in one place.
- Report -- build and run logs you can inspect or export.
At the bottom of each tab is a Filter field, and what it filters depends on the tab. In the Project navigator, for example, it can narrow the list to files you touched recently -- a lifesaver in a project with hundreds of deeply nested files.
The Editor
The Editor is where you'll spend most of your time. For a plain code file it shows your code and, optionally, a Minimap on the right edge -- a zoomed-out overview of the whole file. Hover over the minimap to find a property or method by name, then click to jump there. For the small files in this series you won't need it; hide it from the Adjust Editor Options button at the editor's top-right corner if it's in your way.
For a SwiftUI file, the Editor pairs your code with the preview canvas. Toggle the canvas with Option-Command-Return when you want the full width for code.
The Editor also behaves like a web browser. It has tabs and back/forward history:
- Command-T -- open a new tab
- Shift-Command-[ / Shift-Command-] -- previous / next tab
- Command-W -- close the current tab
- Option-click a tab's close button -- close every other tab
- Control-Command-Left/Right Arrow -- go back / forward through files you've viewed
The Inspectors
The Inspectors pane shows three to five tabs depending on what you've selected. When it's hidden, Option-Command-1 through Option-Command-5 open it to a specific tab:
- File -- name, full path, and target membership of the selected file.
- History -- source control and assist logs.
- Quick Help -- a compact version of the developer documentation for whatever symbol you've selected in the editor.
- Attributes -- the editable properties of the selected symbol.
- Accessibility -- accessibility details when you select something in the preview.
Select a file in the Project navigator and all five tabs are available. Select a folder and you get only the first three. Select Assets.xcassets and the Accessibility tab drops off.
Option-Command-4 opens the Attributes inspector even though its icon sits after the Accessibility icon in the bar. The shortcut numbers follow the logical order, not the visual one.
Matching Your Navigation Settings
This series uses a few click-to-navigate gestures, and they're configurable -- so let's set yours to match. Press Command-, to open Settings, then choose the Navigation tab and set:
- Command-click on Code → Jumps to Definition
- Option-click on Code → Shows Quick Help
- Navigation Style → your preference (Open in Tabs or Open in Place)
With those set, Command-clicking a symbol jumps you to where it's defined, and Option-clicking pops up its documentation. We'll use both constantly.
Reading ContentView.swift
ContentView.swift is the heart of your fresh project and the file Xcode opened for you. If it's not in front, select it in the Project navigator. The top few lines are auto-generated comments naming the file and its creator -- the compiler ignores them. The real code starts here.
The import statement
import SwiftUIThis line pulls in Apple's entire SwiftUI module so your code can use View, Text, VStack, and everything else SwiftUI provides. It works like imports in most languages: without it, those names simply don't exist.
Want proof it matters? Click anywhere on that line and press Command-/ to comment it out. Xcode immediately lights up with errors complaining that View and Preview are unknown. Press Command-Z to undo and the errors vanish.
Code samples in this series use 2-space indentation to keep things compact. Xcode defaults to 4 spaces. If you'd like to match, change it under Settings ▸ Text Editing ▸ Indentation -- but either works fine.
The ContentView struct
Below the import is a structure -- a named data type that bundles together properties and methods.
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
}
}The struct's name matches the file's name. That's a convention, not a rule -- nothing breaks if they differ -- but everyone follows it, so you should too.
Reading struct ContentView: View, you might assume ContentView inherits from View. It doesn't. Swift structs can't inherit from anything. View is a protocol -- a contract -- and ContentView conforms to it. We'll unpack protocols thoroughly later; for now, "conforms to View" means "promises to behave like a view."
The one thing the View protocol demands is a computed property named body that returns some kind of view. Here, body returns a VStack -- a vertical stack -- that arranges a globe image above a line of text.
A computed property hands back the value it computes. When the body is a single expression -- as it is here -- you can omit the return keyword. var body: some View { VStack { … } } is shorthand for return VStack { … }.
Let's name the pieces:
Image(systemName: "globe")displays one of Apple's built-in SF Symbols -- a globe, in this case. The chained.imageScale(.large)and.foregroundStyle(.tint)are modifiers: methods that return a tweaked copy of the view. One sizes the symbol up; the other paints it with the app's tint color.Text("Hello, world!")draws the string "Hello, world!" on screen..padding(), applied to the wholeVStack, is another modifier -- it adds breathing room around the stack so it isn't jammed against the screen edges.
To read a view's documentation, Option-click it. Option-click Text in the editor and a pop-up shows its Quick Help; scroll to the bottom for an Open in Developer Documentation button that opens the full reference. You can also click the Selectable button beneath the canvas, click a view in the preview, and read the same details in the Quick Help inspector.
There's also the Library. Press Shift-Command-L (or click the + in the toolbar) to open it, switch to the Modifiers tab, and scroll to the Text modifiers to browse what's available. The Library closes the moment you click outside it -- unless you hold Option while opening it, which pins it open.
The #Preview macro
Underneath ContentView sits a small but important block:
#Preview {
ContentView()
}#Preview is a macro -- shorthand that expands into more code at compile time. Right-click it and choose to expand the macro and you'll see it generate a PreviewRegistry type; that registry is exactly what the canvas renders. Collapse it again with Command-Z.
Curious what happens without it? Select the three #Preview lines and press Command-/ to comment them out. The canvas goes empty -- no preview to register, nothing to show. Uncomment them (Command-/ again, or Command-Z) to bring it back.
When you want maximum room for code, Option-Command-Return hides the canvas; the same shortcut brings it back.
In a real app, ContentView is usually just the starting point. More often than not it does little itself -- it orchestrates several smaller subviews, each defined in its own file. Let's make one.
Creating a Second SwiftUI View
Everything you see on screen in a SwiftUI app is a View. Apple actively encourages you to split your UI into as many small subviews as it takes to stay organized and avoid repetition -- the compiler optimizes it all into efficient machine code, so there's no performance penalty for being tidy.
In the Project navigator, select any file and press Command-N (or right-click and choose New File from Template…). The template chooser appears with a long list of options. The one you want is iOS ▸ User Interface ▸ SwiftUI View.
Select SwiftUI View and click Next. Name the file ByeView -- replacing the default SwiftUIView. By convention the file name matches the view defined inside it.
Swift names types -- structs, classes, enums, protocols -- in UpperCamelCase (ByeView, ContentView). It names properties and methods in lowerCamelCase (body, imageScale). Following this makes your code instantly readable to other Swift developers.
Leave the default location (this project, this group, this target) and click Create. The template for a new SwiftUI view is simpler than ContentView:
import SwiftUI
struct ByeView: View {
var body: some View {
Text("Hello, World!")
}
}
#Preview {
ByeView()
}No image, so no VStack and no padding -- just a single Text. Notice the "Hello, World!" string is highlighted as a token (a placeholder). Click it and type to replace it. Change it to:
Text("Bye bye, World!")Wiring the new view into ContentView
Now let's use ByeView from ContentView. Open ContentView.swift, delete the Text view inside the VStack, and start typing bye. Xcode's auto-completion offers ByeView -- and notice you didn't even have to capitalize it correctly for Xcode to find it.
Auto-completion rewards good naming. The clearer your type and method names, the faster Xcode can suggest the right one and the less you have to type. (You can also enable live spell-checking under Edit ▸ Format ▸ Spelling and Grammar ▸ Check Spelling While Typing.)
Choose ByeView from the suggestions so the line reads:
ByeView()Those parentheses are you calling the initializer of ByeView -- creating an instance of the view. If the canvas is paused, hit its refresh button, and your custom ByeView now appears where the plain Text used to be. You'll repeat this pattern -- build a small view, then compose it into a larger one -- constantly as you build real apps.
What Else Is in the Project?
The Project navigator lists a handful of files and folders Xcode created for you. Three are worth knowing now.
MyFirstApp.swift -- the entry point
@main
struct MyFirstApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}This file is where your app actually starts. The @main attribute marks MyFirstApp as the launch point. If you've used other languages you might expect to write a main() function by hand -- here, the App protocol handles that for you.
App requires just one thing: a computed property named body that returns a Scene. A scene is a container for a hierarchy of views. For a typical iOS app the default is a WindowGroup holding ContentView() as its root view. On iOS the view hierarchy fills the whole screen; on macOS or iPadOS, a WindowGroup can manage several windows at once. A common real-world tweak is to swap the root view depending on whether the user is signed in.
Assets.xcassets -- images and colors
This is where your app's images and named colors live. AppIcon is a special set holding every size and resolution of your app icon. Since Xcode 14 you can drop in a single 1024×1024 image and let Xcode generate every required size -- just select Single Size in the Attributes inspector.
Preview Content -- development-only assets
Anything your views need only while you're developing -- sample data, placeholder images -- goes here. Assets in Preview Content are excluded from the final build you ship to users.
Xcode stores the paths to these files and folders in the project's build settings. Rename or delete one and Xcode flags errors because it can no longer find what it expects. Leave the generated structure alone.
Settings Worth Changing Now
Xcode has an enormous Settings window. A few adjustments make day-to-day work noticeably nicer.
Themes
You'll stare at the editor for hours, so make it comfortable. Open Settings (Command-,) and pick the Themes tab. Xcode ships several font-and-color themes; try them out, tweak one, or build your own from scratch. There's no wrong answer -- pick what's easy on your eyes.
Matching delimiters
SwiftUI code nests closures inside closures inside closures, and it's easy to misplace a brace or parenthesis. Xcode helps in a few ways.
Under Settings ▸ Text Editing ▸ Editing, you'll find the code-completion options. Most are genuinely helpful -- and even though you can copy and paste code from this series, typing it yourself is how you learn to feel these aids working. Here's a useful tell: if you expect Xcode to suggest completions and nothing useful shows up, you're probably typing outside the closure you meant to be in.
Now open Settings ▸ Text Editing ▸ Display and enable Code folding ribbon (and Line numbers, if you like them). The code-folding ribbon is the strip of faint gray bars between the line numbers and your code. Hover over one -- say, anywhere between VStack { and .padding() -- and Xcode highlights that closure's opening and closing braces. Click the bar to fold the closure shut, hiding everything inside it; click again to unfold. For navigating deeply nested views, folding is a quiet superpower.
Two more ways to check that your delimiters line up:
- Option-hover over a
{,(,[, or its closing partner -- Xcode highlights the matching pair. - Double-click a delimiter -- Xcode selects the pair and everything between them.
Adding Your Accounts
A few Xcode features unlock once you've added account credentials. Open Settings ▸ Accounts and click the + in the lower-left.
- Apple ID: add yours here. If you have a separate paid Apple Developer Program account, add that too. You need a Developer Program team to add capabilities like push notifications or Apple Pay (done in the target's Signing & Capabilities tab) or to run on a physical device.
- Source control accounts: if you use Bitbucket, GitHub, or GitLab, add the account here so you can push your local repo to a remote. These require a personal access token rather than your password -- create one in your provider's settings (for GitHub, the Settings ▸ Developer settings ▸ Personal access tokens page).
Connecting a remote repository
If you ticked Source Control when you created the project, you already have a local Git repo. If not, choose Integrate ▸ New Git Repository… and click Create.
To add a remote: open the Source Control navigator (Command-2), select the Repositories tab, and expand your repository. Right-click Remotes, choose New "MyFirst" Remote…, pick your options, and click Create. Your project now has somewhere to push to.
Running Your App
So far you've only seen the live preview. The canvas is fast and convenient, but some behaviors only work in a full build, and a few only work on a real device. To see your app the way users will, you build and run it on the Simulator.
The toolbar, briefly
A quick orientation to the toolbar, left to right after the Navigator toggle:
- Run (Command-R) -- build and launch the app.
- Stop (Command-.) -- appears while the app runs; stops it.
- Source control -- shows branches and can start a pull request.
- Scheme menu -- labeled with the app's name; each product has a scheme, and
MyFirsthas just one. - Run destination menu -- which device (real or simulated) to run on.
- Activity view -- the wide status field showing progress, warnings, and errors.
- Library (the +) -- views, modifiers, snippets, media, colors, and SF Symbols. Option-click to keep it open.
You can hide or show the whole toolbar with Option-Command-T.
Choosing a device
Apple sells iPhones and iPads in many sizes, some with a notch or Dynamic Island. How do you know your layout works on all of them without owning every model? You don't need to -- the Simulator runs simulated devices on your Mac.
Click the run destination menu and pick, say, iPhone 16 Pro. The preview canvas uses your run destination by default, so refresh the preview if it's paused and it'll render at that device's size. (You can also force a specific preview device with the previewDevice modifier, and even keep several previews side by side.)
A fresh Xcode install might list only a handful of simulators. To add one -- an older iPhone XR, for example -- click Manage Run Destinations…, choose the Simulators tab, click +, select the model, and click Create. It then shows up in the run destination menu and in the canvas's device picker.
Build and run
Click Run (or press Command-R). The first time you run on a particular simulated device, it boots from cold, so you'll see a loading indicator -- and once it's awake it stays awake until you quit the Simulator app, so later runs skip the startup delay.
After boot, the app's launch screen appears (blank, for MyFirst), and then your app is running. If you open the Debug Area, you can watch live CPU, memory, and other stats in the Debug navigator. There's not much happening in this app yet -- but you built it, and it's running on a simulated iPhone.
The "don't ask again" trick
Here's a small quality-of-life win. Leave the app running -- don't press Stop. Go into ByeView.swift and change the text again:
Text("Hello again, World!")Press Run (Command-R). Because the app is still running, Xcode pops up a dialog asking whether to stop the current process and run the new one. Before you click Replace, tick Don't ask again, then click Replace.
From now on, hitting Run while the app is already running just relaunches it with your latest code -- no dialog, no interruption. It's a tiny thing you'll appreciate dozens of times a day.
Key Points
- SwiftUI development needs a Mac with Xcode; install Xcode from the App Store and let it download the iOS component on first launch.
- A new iOS ▸ App project with the SwiftUI interface gives you
ContentView.swift, anAppentry point,Assets.xcassets, andPreview Content. - The Xcode window has three core panes -- Navigator, Editor, Inspectors -- plus a Debug Area and a Toolbar, each with memorable toggle shortcuts.
- A SwiftUI view is a struct that conforms to the
Viewprotocol and provides abodycomputed property returningsome View. - Modifiers like
.padding()and.foregroundStyle()return tweaked copies of a view; chain them to style your UI. - The
#Previewmacro drives the canvas; without it, the canvas is empty. - Build your UI from small subviews in their own files, then compose them -- the compiler keeps it efficient.
- The
@main Apptype, with itsSceneandWindowGroup, is what actually launches your app and sets its root view. - Xcode's themes, code folding, delimiter matching, and auto-completion are there to help you write correct code faster -- lean on them.
- Run your app on a Simulator when the preview isn't enough, and check Don't ask again to silence the relaunch dialog for good.
In the next chapter, we'll leave setup behind and start prototyping a real screen -- working with images, text, and layout to build something worth looking at.
Ship your apps faster
When you're ready to publish your Swift app to the App Store, Simple App Shipper handles metadata, screenshots, TestFlight, and submissions — all in one place.
Try Simple App Shipper