Project setup with SwiftUI
When creating a new project in Xcode, the Swift UI functionality must be enabled from
the start. Xcode then automatically generates not only the AppDelegate, but also a
SceneDelegate class. Furthermore, necessary properties are created directly in the
Info.plist.
The SceneDelegate provides functions to set the view at app startup and also other
events. A UIScene object is passed, for which a view is defined and set. In Unlike the
use of xib-files or storyboards, the window must be created with the help of the passed
Scene itself. As usual, the window is given a rootViewController. When using Swift UI, a
UIHostingController must be used instead of a normal ViewController. The content of this
controller corresponds to a view whose content is to be displayed. Finally, the newly
created window is displayed on the screen.
SwiftUI provides an interface view from which all classes that are to display content on
the screen must be derived from it. Each of these view classes contains an instance
variable body. This is defined by the developer by creating a new view with arbitrary
content. All elements in this view are then displayed later. Each of these views can
also contain other elements derived from the view. Thus complex surfaces can be
structured simply, while the components are structured separately and the code becomes
much clearer.
First steps
In the SceneDelegate you define which content is displayed. Xcode initially generates a
ContentView class that contains sample text. An instance of this class is created and
displayed at runtime. If the code of the ContentView is changed, the customized content
appears after a start of the app. In addition, Xcode displays a preview on the right
side next to the source code, allowing the developer can see in real time what the user
interface will look like later in the app.
In a first attempt, we add some lines to the definition of the body instance variable.
We start with a VStack, a Vertical StackView in SwiftUI. This is necessary to be able to
add multiple elements to the body, which may consist of only one view element. We also
add a text and an image in this VStack. In real time, we can see what the view will look
like.
Since the text is still too small, we want to adjust the font. Attributes of each UI
element can be easily attached to the respective initialization and are called
modifiers. In addition, we also change the color of the text in this course. This looks
like this:
After adjusting the text, the image still looks too small. We could add the same
modifiers to this element as well. However, SwiftUI also allows us to add modifiers to
all elements within a structure. Thus, we can move the modifiers and put them on the
VStack instead.
After the last modification, all elements within the stack get the defined modifiers.
Now we want to add a background to the text. For this we define a background, adjust the
cornerRadius and add padding. In the preview you can immediately see that the displayed
result is not what you would expect.
The reason for this is the order of the modifiers. First we adjust the cornerRadius,
which is not visible without a special background. The background can be seen, but it is
defined after the cornerRadius and therefore appears without rounded corners. The
padding is added last which only creates a space between the text and the image and does
not increase the size of the actual background becomes larger. After correcting the
order, the result looks like this:
To display the elements within the VStack object not centered, but for example at the
very top or at the very bottom, just add a Spacer() element at the appropriate position.
This element takes the remaining space that the other elements do not need.
Library
To simplify the whole process, Xcode provides a library that combines all views and
modifiers. This makes it very easy to add new elements and modifications via drag &
drop.
Preview
Within each View object or ViewController class, previews can be created that determine
which and how many previews are displayed. For example, a preview of different devices
or the dark mode can be created. Preview offers very many and complex application
possibilities. In this article, we will only briefly discuss Preview to show the
advantages for development with SwiftUI.
In the example above, two previews are displayed. The first is a PreviewLayout for which
a fixed size is defined. The second view is a specific device that is displayed in Dark
Mode. Multiple previews must be grouped together because, again, the Previews value
consists of only one view.
The preview function is very helpful and prevents that the app has to be started again
and again to see the result. This is especially beneficial for views that can only be
reached by many clicks in an app. Also, each preview offers a small button that
activates a live preview. With the help of this function, a view can be used
interactively without having to start the app on a device.
Example of a list with custom view elements
In the following example we want to display a list of elements. To do this, we first
create a cell that determines the content of all the list items. Then we can use this
cell in our ContentView. We also create a container class that will provide us with the
necessary data. We start with the Container Developer.
In this simple class we define a few values that we can later populate and use in the
views. For the container to be used for a list in SwiftUI, it must implement the
interface Identifiable. This way we ensure that each instance of this class can be
uniquely identified later. For this we also add a UUID value that guarantees this
uniqueness. If we are in DEBUG mode, we create test data to be able to use it directly
in the preview.
The first view we need is a DeveloperCell. This should display the values of a single developer instance.
The cell has an instance of the Developer class whose values it displays. The base of
our view is an HStack in which elements are arranged horizontally. With an icon and a
text we display an image and the name of the respective developer. If the developer is
not available, we also color the text and the icon gray.
We represent a rating by displaying the image of a star multiple times, depending on the
integer value rating. To do this, we create an image with a star using a ForEach block.
The color of the star is yellow or gray depending on the availability. The brightness we
vary depending on the height of the rating. To the complete cell we add a small padding.
For the preview of our cell we define a new instance Developer, which we fill with
sample data.
We design the ContentView so that it receives a list of Developer instances and displays
their content using the created cell.
We use List(developers) to be able to create one of our cells for each instance. Since
we may want to customize each developer's data through the app in a further development
step, we create a NavigationView as a top-level element that provides a NavigationBar
and allows us to switch from this view to an underlying view.
The list gets an additional modifier navigationBarTitle, which we use to specify the
title of the view. We also wrap each DeveloperCell instance in a NavigationLink. This
element takes a destination. This destination must be a view to navigate to after a user
interaction. For now, we simply define a text that displays the name of the respective
developer.
If we launch the app now, we will see our list with the corresponding details of the
developers. As soon as we select a cell, another view appears, on which only the name of
the selected developer can be seen. The button at the top left takes us back to the
overview.
A big advantage of SwiftUI is the handling of data. As soon as values of a developer
instance change, the view is updated. Furthermore, SwiftUI registers when elements are
added or deleted and updates the views automatically.
@State, @Binding and @Environment
When a view defines and controls a certain state, the created variable is tagged with
the @State tag. Without this tag, a view can only read the value of a variable and
cannot change it.
The toggle we define changes our showMessage variable. We provide the variable with an
additional $ symbol to signal a binding (more on this in a moment). Depending on the
state of this value we show or hide a text.
If we now want to call another view and give it access to showMessage, we can do this
with the @Binding tag. For example, if we create another view that only contains the
toggle, but should still be able to change the state of the showMessage variable without
owning it, this would be such a case. In the following example, we define ToggleView
that contains the binding to showMessage and then use this view in the previously
created MessageView.
As we did for the toggle, we pass showMessage with an additional $ symbol that we need
for a binding. The Toggle element also uses a binding internally on the variable that is
passed in. Now when we launch the app, we can use the toggle to make the displayed
message appear and also disappear.
If we want to access an environment variable within a view, we implement this with the
help of the @Environment tag. This gives us access to the status variables of a view,
such as isEnabled:
@Environment(\.isEnabled) private var isEnabled: Bool
Subsequently, this value can be used for each element within the view. The content of
this variable changes as soon as the value of the actual environment variable changes.
The view is automatically updated in such a case.
ZStack
The ZStack is a new element in SwiftUI. Just as the VStack arranges elements vertically
and the HStack horizontally, a ZStack arranges all elements on top of each other. You
use this element when you want to swap multiple views depending on certain conditions or
switch views.