1. The View protocol
If you aren’t already aware, SwiftUI uses the
View protocol to create reusable interface elements. Views are value types, which means they use a
Struct instead of a
What does this actually mean in practice?
Structs do not allow inheritance. Although your structs conform to the
View protocol, they do not inherit from a base class called
View that Apple has provided.
This makes it different from
UIView, from which almost everything in UIKit inherits. A
UIView basically cannot be seen without being assigned a frame and being added as a subview of a
If you create a new Xcode project that uses SwiftUI instead of Storyboard as the basis of its user interface, you’ll automatically be given an example of a SwiftUI
You’ll notice that inside the
ContentView struct, there is a variable called body. This is the sole requirement of the
View protocol, and it makes use of the
some keyword which is brand new to Swift 5.1.
You can rely on a Stack Overflow thread to explain what this keyword means, better than I ever could:
“You can think of this as being a “reverse” generic placeholder. Unlike a regular generic placeholder which is satisfied by the caller… An opaque result type is an implicit generic placeholder satisfied by the implementation… The main thing to take away from this is that a function returning
some Pis one that returns a value of a specific single concrete type that conforms to
Let’s get started looking at the example views that Apple provides that conform to the
That example project that you get when creating a SwiftUI Xcode project includes probably the most simple building block for a view, and it’s called
In most cases, you’ll be passing a
String to the constructor of this, and that will be the content it displays.
Here are some examples of all the initializers for
Note that the last initializer, the one that takes a
comment, requires a separate file that uses the
.strings file extension.
As is mentioned in Apple’s documentation for this initializer, the only required parameter is the string for the key. I gave a verbose example mostly so that you can see what these other parameters require.
The default of
Localizable, the standard name for a strings file. I deliberately named mine
Local to show why I would need this parameter.
The bundle is the main bundle by default, so passing
Bundle.main is redundant in this case.
The comment should give contextual information, but in this example, I’ve just given it the string
Texts here are embedded in a
VStack because the body variable’s opaque result type has to be set to one type. In other words, it can’t be set to several items because this could involve several types.
VStack can contain up to ten views inside it, each of these can be a
Group and each can have ten types inside it.
Scroll down to HStack, VStack, and ZStack for more details on these.
3. Text ViewModifiers
Text, like all Views, can be modified by structs that conform to the ViewModifier protocol.
Let’s see an example of a custom modifier so you can see what’s happening under the surface with these:
As you can see, it would be possible to add
.modifier(YourModifier()) to call a ViewModifier, but it makes a lot more sense to use a
View extension and give a clean call site.
This is the way the standard modifiers look, so making a
View extension will make your modifiers look much more like the default ones.
It will be difficult to create a
ViewModifier that is easier to write than the defaults without this, as starting your modifier with the word “modifier” and calling its constructor adds unnecessary complexity.
4. Standard text modifiers
In this example, I put a red border around the
VStack that holds the font alignments. This was to show that the boundaries of this
VStack are limited because the
Texts inside have a fixed maximum size.
Without this, alignment for the
Texts has no effect, as the
VStack container will expand to accommodate the
To align to a leading (left)or trailing (right) edge, we need to define where that leading or trailing edge is going to be. This can also be achieved by fixing the width of the
Notice how this example has created extensions on
Text instead of
View. This is because you can only guarantee that the received type will be a
Text in this case.
It is impossible to do the equivalent to
redUnderline by creating a
View extension because these take a generic
View that may not be a
Now that you know this, you can make your own custom functions that customize
Text, without the intermediary step of creating
ViewModifiers I mentioned above.
If you want a user to enter text, you’ll need a binding to store that data. This first example uses a
State variable, which stores the string locally in the SwiftUI struct and doesn’t actually save it anywhere you can use it or store it permanently.
If you want to hold onto your data and be able to give it a computed value, you’ll need an
ObservableObject. This essentially gives you a regular Swift file to store your data in, which will get pretty useful once you start adding controls that can modify data values.
You don’t need to provide a
didSet closure for the string value that you saved, I’m just providing an example to show when
didSet runs. My
DataModel class is a normal Swift class, so its
didSet closure runs and prints the new value of the string.
However, since SwiftUI views are value types that are created dynamically, the
didSet callback does not print anything to the console when you modify the local
Did you notice how some of the later initializers use a
Float instead of a
These initializers will take any type, but be careful to pass in one of the provided
Formatter classes, or make one yourself.
In my example, I use
NumberFormatter, which will not let you input any character that isn’t a number. This makes it easy for me to save my
Float without worrying that the app will crash because I can’t convert a string of letters into the
Float that stores the
The other initializers also have two closures, the first of which is
Have you ever called
resignFirstResponder on a
UITextField in UIKit?
This essentially dismisses the keyboard because the
UITextField no longer needs focus. Even if you could bring the keyboard back at some point, text would not be inserted into that
UITextField unless you made it the first responder again.
That all relates to
UIResponder, an abstract interface from which
UIViewController, and basically everything else in UIKit inherits.
We don’t know how SwiftUI events are handled to the same extent, but I’m using the phrase first responder as it should be familiar to anyone who has used
The bool in
onEditingChanged can be called
fieldActive or anything else you want that makes it clear to you.
The important thing is that when you start to edit a
onEditingChanged is called with a bool that is set to true. When you press the keyboard’s return key, the
onCommit block is called, after which
onEditingChanged is called with a bool that is set to
6. TextField ViewModifiers
For a more detailed explanation of what ViewModifiers are, see Text ViewModifiers.
You cannot currently change the foreground color of the placeholder text in a
TextField. At the time that I’m writing this, you cannot use any keyboard type for a
TextField other than the default when displaying
TextFields in a
I found that trying to display
TextFields in a
List caused them to overlap each other too. Here are all of the keyboard types which seem to work pretty well when presented in a
Essentially the same as
TextField above with the added benefit of hiding the characters you enter, which is useful for passwords. As with
TextField above, you can choose a variety of keyboard types, of which only
numberPad is shown here.
8. SecureTextField ViewModifiers
For a more detailed explanation of ViewModifiers, see Text ViewModifiers.
TextField, you can change the foreground or background colors, add a border, and use different
TextFieldStyles, but you cannot change the foreground color of the placeholder text at this time.
I can’t expand much on what Apple’s documentation says about
Font, so I’ve provided a simple way to use custom fonts in the same way as Apple’s standard fonts:
Notice how I’ve made extensions for both
View. You don’t have to use extensions, as you can see when I use
Font.custom directly. All of these methods result in the same
Text, so it’s just a matter of which code you find to be the cleanest.
The absolute easiest to write is the
View extension, which doesn’t require you to pass anything into the function.
Font extension is more consistent with the way standard Apple fonts are assigned, for example,
Images in SwiftUI are much easier than in UIKit. Instead of needing to create a
UIImage(named: “Your file name”) and assigning it to
Image is about as easy to create as
Just pass it a
String and it’ll set it to a file with that name. If you launch your app and it doesn’t have a file with that name, you’ll get a useful console message saying:
No image named ‘Your file name’ found in asset catalog for main bundle.
If you find images not turning up in your app, you may want to search for this in the console.
Image is not resizable by default
You must call the
.resizable() modifier on your
Image before making changes to its size in subsequent modifiers.
scaledToFit modifier will lock the aspect ratio of your image and scale it to the maximum size it can be without being too large for the screen.
scaledToFill modifier also scales your image, but it does not lock the aspect ratio and, subsequently, is likely to stretch or shrink your image to fit the available space.
11. SF Symbols
If you aren’t familiar with them, SF Symbols is a library of over 1500 symbols that Apple provides in nine weights from ultralight to black.
To use these in your images, simply label the
String you pass into your Image as
systemName. It’s probably worth downloading the SF Symbols Mac app so that you can find out what the system name is for the symbols you want to use.
Using SF Symbols gives your app a consistent look that will probably be taking over the iOS ecosystem in the coming years due to the flexibility and accessibility of these free symbols.
Button has no appearance of its own. In other words, you will need to give your
Label, which itself is any concrete type that conforms to
The most obvious example of this is a
Text that will give information on what your button will do. At the time that I’m writing this, the only thing Apple specifies in the documentation (other than how to create and style them) is that buttons are triggered differently depending on your operating system.
On iOS, you tap on it, on tvOS, you press enter when the button is selected, and in a macOS app with or without Catalyst, which Apple doesn’t mention, you click with a mouse or trackpad.
The constructor requires that you give an action. This can be an empty set of curly braces, but it has to be there in this form at the very least.
As well as specifying your functionality in the curly braces, which can get verbose pretty fast, you can also specify the name of a function without curly braces and without the
() call operator. This is not binding the action to a variable, which means that you do not need the
$ operator that you’ll find on controls that take a binding such as a
Some controls allow you to choose existing styles, such as those that conform to
ButtonStyle in this case. That also means that you can create your own custom styles for a
Button, details of how to do that can be found on SwiftUI Lab’s Custom Styling tutorial.
As you might see in the comment I made on that post,
SliderStyle does not currently exist (although it is documented on Apple’s website). Let’s go through the existing styles for buttons and see them in action.
Note that some are only available on MacOS.
14. NavigationView and NavigationLink
Embedding your Views in a
NavigationView allows you to set a navigation title and link to other Views. Similarly to
NavigationLink requires a
Label which is basically any struct that conforms to the
In most cases, this will probably be a
Image, but it can also be any custom view that you create.
View that is the destination of your link slides in from the right on an iPhone, and each successive
NavigationLink slides in the same way. When returning to the initial
View, you can swipe from the left edge or use the back button in the top-left of the navigation bar.
This example is from my watch app Dog HQ that shows a scrolling list of full-sized dog photos, each of which links to a zoomed-in version.
This is why I need to pass the index to the constructor of my zoomed-in
DogView, so that I know which dog I want to be the destination.
List, which scrolls vertically and expands to any size I want, with a
ForEach allows me to create 50 rows and pass that index into the closure with a name I specify.
The iteration for the
ForEach could be a sequence that has a maximum of the number of items in an array, or the array could be passed into the constructor for a
List and accessed inside the closure with a name you specify.
Obviously, I’m just scratching the surface with this array. The array could contain complex types, such as a custom class that has a string property called
imageName, a number value, or perhaps even an instance of another class, which you can access using the dot syntax.
The navigation bar at the top of the screen can contain a leading and trailing button. The main use for this seems to be adding an
EditButton, which is described in detail below.
An edit button is pretty useful when you have a
List of items and you want to make it possible to delete some of them. Tapping it takes you into edit mode (unsurprisingly), showing a red circle with a horizontal line through it on each row.
Tapping Edit will slide the row to the left, revealing a delete button on the right end that acts as a final confirmation.
You still need to implement a function that will handle deleting the data from the list, otherwise, your changes will only be visual and your data won’t actually be deleted in the way you expect.
For more information on using
EditButton, see List, ScrollView, ForEach, and DynamicViewContent.
MenuButton is only available on macOS apps, so I’ve provided a Mac app example that uses all of the standard
From left to right, these styles are
If the one on the far right looks a lot like the one next to it, it’s because it uses
Since the default
MenuButton has the appearance of
PullDownMenuButtonStyle, these look exactly the same.
This control allows you to paste information on MacOS, but it is not available on iOS. It can take a variety of data types, which are expressed as UTI types. I’ve included a function in my example that lets you find the UTI string for any type, which will probably help you when implementing this button. Once you have decided what type identifiers you need, you will need to handle the data that you get from the NSItemProvider. I’ve shown an example where I only paste the first item in the array, but hopefully it makes it clear how you could handle other data types and multiple items.
Here’s a list of the types that conform to NSItemProviderWriting, and can therefore be used for pasting with the PasteButton:
You can also conform to this protocol with your own custom types, allowing you to paste custom types of data.
Toggle is the SwiftUI equivalent of
UISwitch in UIKit. Instead of having an
IBAction function that links your Swift code to a
UISwitch on a Storyboard and runs when its value changes, SwiftUI uses bindings.
Without marking a variable as
State (within the struct) or
Published (in an outside class conforming to
ObservableObject), SwiftUI will not redraw the contents of the
View when the value changes.
This is an essential part of the binding process, especially marking outside code as
Published, as this is the only way that SwiftUI will even be aware of that variable’s existence.
19. Creating a custom ToggleStyle
I noticed that the initializer for
Toggle can take a struct called
ToggleStyleConfiguration, and I spent a while trying to figure out how to construct this myself.
What I found, with a lot of help from SwiftUI Lab’s excellent tutorial on custom styling, was that the protocol
ToggleStyle provides the ability to make your own custom styles.
Part of the way it allows you to do this is the following line:
typealias ToggleStyle.Configuration = ToggleStyleConfiguration
This syntax perplexed me at first.
typealias here is just a way of referring to the struct with a more succinct local name. This is probably so that the
makeBody function, seen below, can have the same declaration signature as the similar protocols
func makeBody(configuration: Self.Configuration) -> some View
I didn’t want to create a custom visual appearance for the
Toggle here, as this is already covered pretty well by SwiftUI Lab’s tutorial.
Instead, I decided I would change how the label is treated by completely ignoring the label that is passed in and giving two dynamic labels that change based on the toggle’s
There are two examples here, but they look exactly the same. One uses the rather verbose form using the
.toggleStyle modifier, just as the standard
The other uses an extension on
Toggle that returns this verbose form, providing a clean call site but becoming inconsistent with the way the standard
It’s up to you which of these you prefer. It goes without saying that you do not need to have local variables in the
MyToggleStyle struct, a lack of which would remove the need to pass values into the constructor.
I only did this to show how you can pass custom values in, but you cannot change the signature of the
In other words,
makeBody can only take a
Self.Configuration parameter. By constructing a struct with uninitialized variables, we have another way to pass values alongside the
isOn binding and
Label from the
MyToggleStyle does not make use of
configuration.label, which is the value of
Text(“This label will never be seen”) we added. It isn’t necessary to add this label, as a
Toggle can be constructed without it, but it was worth pointing out how a custom
ToggleStyle can hide whatever it wants.
some View, you can return whatever you want. You could return a
Image, or even a
VStack, although I have no idea why you’d want to do that.
As was mentioned in the Hacking With Swift tutorial on Pickers, the default behavior of a
Picker inside a
Form is to take you to another where you can choose an option.
On iOS you must put the
Form inside a
NavigationView, otherwise, this navigation will not occur. Outside of a
DefaultPickerStyle will be
I have also included
SegmentedPickerStyle which has a similar appearance to
DatePicker is similar to
Picker, but doesn’t have all the same styles. When used inside a
DatePicker only takes up a single line.
As you can see in the screenshot above, the default
DatePicker in a
Form has a label and the current date. Tapping it will cause a
DatePicker to slide out underneath.
DatePicker that slides out is exactly the same as the
WheelDatePickerStyle, which is why it looks like it is displayed now when actually I just have a
WheelDatePickerStyle underneath it.
I added a
Picker that you can use to try out different date formats, just to show how you could change the format of a
DatePicker at runtime.
A slider allows you to swipe the thumb, a white circle, between a minimum and maximum value. This is similar to
UISlider in UIKit. When you create it you have to set a closed range so that SwiftUI knows what the minimum and maximum values will be.
The step can be set to any amount, potentially saving you from needing to convert a long
Float to an
Int if you don’t need your value to be a decimal.
This also helps you to increase or decrease the amount of accuracy that the slider position is recorded in, potentially making it easier to make calculations by excluding decimal places past the step amount you specify.
Stepper in SwiftUI is basically identical to a
UIKit. It consists of a connected minus and plus button.
Not all of the initializers require you to set a binding variable to store the value. Many of them take closures that are called when you decrement, increment, or edit the value of the