NEW: Join my free 100 Days of SwiftUI challenge today! >>

SwiftUI tips and tricks

Paul Hudson    @twostraws   

Fully updated for Xcode 11 GM

SwiftUI is packed with powerful headline features, but there are also dozens of smaller tips and tricks that will help you write better apps.

I’ve tried to summarize all the tips I’ve come across so far below, and where applicable I’ve also provided links to my more in-depth SwiftUI tutorials to help give you extra context.

Resume the live preview

Having a live preview of your layouts while you code is a great feature of Xcode, but often you’ll see it pause because you changed a lot and Xcode couldn’t keep up.

Rather than constantly reaching for your trackpad to press Resume, here’s the most important keyboard shortcut for SwiftUI developers: press Option-Cmd-P to make the preview window reload immediately, and resume its live updates.

Make @State private

Apple provides us with three ways to use state in our apps: @State is for simple local properties, @ObservedObject is for complex properties or properties that are shared between views, and @EnvironmentObject is for properties that are indirectly shared potentially by many views.

Because @State is specifically designed for use by the local view, Apple recommends marking @State properties as private to really re-enforce that they aren’t designed to be accessed elsewhere:

@State private var score = 0

Prototype with constant bindings

If you’re just trying out a design and don’t want to have to create bindings to use things like text fields and sliders, you can use a constant binding instead. This will allow you to use the object with a realistic value.

For example, this creates a text field with the constant string “Hello”:

TextField("Example placeholder", text: .constant("Hello"))
    .textFieldStyle(RoundedBorderTextFieldStyle())

And this creates a slider with a constant value of 0.5:

Slider(value: .constant(0.5))

Note: Constant bindings like this one are just for testing and illustration purposes – you can’t change them at runtime.

Presenting test views

Another useful tip while you’re prototyping is that you can present any kind of view rather than a full detail view – even when working with a navigation view.

For example, if you had a list of users and wanted to make sure that tapping one of them worked, you could use a navigation link that points to a text view rather than a fully fledged custom custom view, like this:

struct ContentView: View {
    let users = (1...100).map { number in "User \(number)" }

    var body: some View {
        NavigationView {
            List(users, id: \.self) { user in
                NavigationLink(destination: Text("Detail for \(user)")) {
                    Text(user)
                }
            }.navigationBarTitle("Select a user")
        }
    }
}

This allows you to make one screen complete before going on to design the real detail view.

Go past the 10 view limit

All containers in SwiftUI must return no more than ten children, which is usually fine for most purposes. However, if you need to have more than 10 views, if you need to return more than one view from your body property, or if you need to return several different kinds of view, you should use a group like this:

struct ContentView: View {
    var body: some View {
        List {
            Group {
                Text("Row 1")
                Text("Row 2")
                Text("Row 3")
                Text("Row 4")
                Text("Row 5")
                Text("Row 6")
            }

            Group {
                Text("Row 7")
                Text("Row 8")
                Text("Row 9")
                Text("Row 10")
                Text("Row 11")
            }
        }
    }
}

Groups are purely logical containers – they don’t affect your layouts.

Use semantic colors

SwiftUI is designed to work with themed user interfaces out of the box, which means it provides both semantic and adaptive colors by default. Although it might be tempting to use your own custom colors, you should at least check first whether you have something in the default SwiftUI set.

For example, Color.red isn’t the pure red of RGB(255, 0, 0), but instead slightly lighter or slightly darker based on the environment – it adapts automatically without us needing to think about it.

Similarly, Color.primary, Color.secondary, and Color.accentColor all refer to fixed values that are provided by the environment, allowing us to structure and highlight content in a standardized way.

Rely on adaptive padding

SwiftUI lets us control precisely how much padding to apply around views, like this:

Text("Row 1")
    .padding(10)

While it’s tempting to always control padding like this to “get things just right”, if you use the padding() modifier without any parameters you get adaptive padding – padding that automatically adjusts itself to its content and its environment.

So, if your app is running on an iPad with a regular size class you’ll get more padding than if the user moves it down to a split view – all without having to write any code.

Combine text views

You can create new text views out of several small ones using +, which is an easy way of creating more advanced formatting. For example, this creates three text views in different colors and combines them together:

struct ContentView: View {
    var body: some View {
        Text("Colored ")
            .foregroundColor(.red)
        +
        Text("SwifUI ")
            .foregroundColor(.green)
        +
        Text("Text")
            .foregroundColor(.blue)
    }
}

How to make print() work

If you press play in the SwiftUI preview to try out your designs, you’ll find that any calls to print() are ignored. If you’re using print() for testing purposes – e.g. as simple button tap actions – then this can be a real headache.

Fortunately, there’s a simple fix: right-click on the play button in the preview canvas and choose “Debug Preview”. With that small change made you’ll find your print() calls work as normal.

Relying on the implicit HStack

When you create a list of items, it’s common to want to get the iOS-standard look of having an image on the left then some text on the right.

Well, if you’re using a dynamic list of items – i.e., a list that’s attached to an array of data – then you actually get a HStack for free inside your list, so there’s no need to make one by hand.

So, this code will create a list based on picture names from an array, and relies on the implicit HStack to arrange the image and text side by side:

struct ContentView: View {
    let imageNames = ["paul-hudson", "swiftui"]

    var body: some View {
        List(imageNames, id: \.self) { image in
            Image(image).resizable().frame(width: 40)
            Text(image)
        }
    }
}

Splitting up large views

If you find yourself with a large view you might find it easier to break it up into several smaller views and compose those together to get the same result. One of the great features of SwiftUI is that there’s no performance difference because it flattens its view hierarchy, but it certainly makes maintenance easier!

For example, here’s a list that shows an image, a title, and a subtitle for every users:

struct ContentView: View {
    let users = ["Paul Hudson", "Taylor Swift"]

    var body: some View {
        NavigationView {
            List(users, id: \.self) { user in
                NavigationLink(destination: Text("Detail View")) {
                    Image("example-image").resizable().frame(width: 50, height: 50)

                    VStack(alignment: .leading) {
                        Text("Johnny Appleseed").font(.headline)
                        Text("Occupation: Programmer")
                    }
                }
            }.navigationBarTitle("Users")
        }
    }
}

Even though it’s not really that complicated, you still need to read it carefully to understand what it’s going on.

Fortunately, we can take parts of the view out into a separate view to make it easier to understand and easier to re-use, and Xcode makes it a cinch: just Cmd-click on the navigation link and choose Extract Subview. This will pull the code out into a new SwiftUI view, and leave a reference where it was.

Note: If your subview relies on data from the parent you’ll need to pass that along yourself.

Better previewing

One of the many benefits of SwiftUI is that we get instant previews of our layouts as we work. Even better, we can customize those previews so that we can see multiple designs side by side, see how things look with a navigation view, try out dark mode, and more.

For example, this creates a preview for ContentView that shows three different designs side by side: extra large text, dark mode, and a navigation view:

#if DEBUG
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
                .environment(\.sizeCategory, .accessibilityExtraExtraExtraLarge)
            ContentView()
                .environment(\.colorScheme, .dark)
            NavigationView {
                ContentView()
            }
        }
    }
}
#endif

Tip: Make sure you zoom out or scroll around in the preview window to see all the different previews.

Create custom modifiers

If you find yourself regularly repeating the same set of view modifiers – for example, making a text view have padding, be of a specific size, have fixed background and foreground colors, etc – then you should consider moving those to a custom modifier rather than repeating your code.

For example, this creates a new PrimaryLabel modifier that adds padding, a black background, white text, a large font, and some corner rounding:

struct PrimaryLabel: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.black)
            .foregroundColor(.white)
            .font(.largeTitle)
            .cornerRadius(10)
    }
}

You can now attach that to any view using .modifier(PrimaryLabel()), like this:

struct ContentView: View {
    var body: some View {
        Text("Hello World")
            .modifier(PrimaryLabel())
    }
}

Animate changes easily

SwiftUI has two ways for us to animate changes to its view hierarchy: animation() and withAnimation(). They are used in different places, but both have the effect of smoothing out changes to the views in our app.

The animation() method is used on bindings, and it asks SwiftUI to animate any changes that result in the binding’s value being modified. For example, here’s a view that has a Toggle to show or hide a label:

struct ContentView: View {
    @State private var showingWelcome = false

    var body: some View {
        VStack {
            Toggle(isOn: $showingWelcome) {
                Text("Toggle label")
            }

            if showingWelcome {
                Text("Hello World")
            }
        }
    }
}

When the toggle is changed, the text view below it will appear or disappear immediately, which isn’t a great experience. However, if we used animation() we could make the view slide in and out smoothly when the toggle is changed:

Toggle(isOn: $showingWelcome.animation()) {
    Text("Toggle label")
}

You can even control the kind of animation you want, like this:

Toggle(isOn: $showingWelcome.animation(.spring())) {
    Text("Toggle label")
}

When you’re working with regular state rather than bindings, you can animate changes by wrapping them in a withAnimation() call.

For example, here’s our same view except now it shows or hide the welcome label using a button press:

struct ContentView: View {
    @State private var showingWelcome = false

    var body: some View {
        VStack {
            Button(action: {
                self.showingWelcome.toggle()
            }) {
                Text("Toggle label")
            }

            if showingWelcome {
                Text("Hello World")
            }
        }
    }
}

As with before that will cause the welcome label to appear and disappear immediately, but if we wrap our changes in withAnimation() they will be animated instead:

withAnimation {
    self.showingWelcome.toggle()
}

And it’s customizable in exactly the same way as animation():

withAnimation(.spring()) {
    self.showingWelcome.toggle()
}

Publishing new values from a binding

Last but not least, to avoid problems when sending update notifications from a publisher – e.g. calling send() on a PassthroughSubject or updating any @Published property – you should make sure you’re always on the main thread.

As with UIKit and most other UI frameworks, you can do all the background work you want in your SwiftUI apps, but you should only ever manipulate the user interface on the main thread. Because state changes automatically trigger a refresh of your body, it’s important that you make sure you perform those state changes on the main thread.

What are your tips?

The SwiftUI tips and tricks above are ones I’ve come across watching WWDC sessions, asking questions in the labs, and writing lots and lots of code while moving my own projects from UIKit.

I’d love to hear what tips you have – send me a tweet @twostraws and let me know!

LEARN SWIFTUI FOR FREE I have a massive, free SwiftUI video collection on YouTube teaching you how to build complete apps with SwiftUI – check it out!

Similar solutions…

MASTER SWIFT NOW
Buy Testing Swift Buy Practical iOS 12 Buy Pro Swift Buy Swift Design Patterns Buy Swift Coding Challenges Buy Server-Side Swift (Vapor Edition) Buy Server-Side Swift (Kitura Edition) Buy Hacking with macOS Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with Swift Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.7/5