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

Building our basic UI

Paul Hudson    @twostraws   

The first step in our project is to build the basic user interface, which for this app will be:

  1. A NavigationView so we can show our app’s name at the top.
  2. A large gray box saying “Tap to select a picture”, over which we’ll place their imported picture.
  3. An “Intensity” slider that will affect how strongly we apply our Core Image filters, stored as a value from 0.0 to 1.0.
  4. A “Save” button to write out the modified image to the user’s photo library.

Initially the user won’t have selected an image, so we’ll represent that using an @State optional image property.

First add these two properties to ContentView:

@State private var image: Image?
@State private var filterIntensity = 0.5

Now modify the contents of its body property to this:

NavigationView {
    VStack {
        ZStack {
            Rectangle()
                .fill(Color.secondary)

            // display the image
        }
        .onTapGesture {
            // select an image
        }

        HStack {
            Text("Intensity")
            Slider(value: self.$filterIntensity)
        }.padding(.vertical)

        HStack {
            Button("Change Filter") {
                // change filter
            }

            Spacer()

            Button("Save") {
                // save the picture
            }
        }
    }
    .padding([.horizontal, .bottom])
    .navigationBarTitle("Instafilter")
}

There are lots of placeholders in there, and we’ll be filling them piece by piece as we work through this project.

For now, I want to focus on this comment: // display the image. This is where we need to show the selected image if we have one, but otherwise we should show a prompt telling the user to tap that area to trigger image selection.

Now, you might think this is a great place to use if let and try replacing that comment with something like this:

if let image = image {
    image
        .resizable()
        .scaledToFit()
} else {
    Text("Tap to select a picture")
        .foregroundColor(.white)
        .font(.headline)
}

However, if you try building that you’ll see it doesn’t work – you’ll get a fairly obscure error message along the lines of “Closure containing control flow statement cannot be used with function builder ViewBuilder”.

What Swift is trying to say is it has support for only a small amount of logic inside SwiftUI layouts – we can use if someCondition, but we can’t use if let, for, while, switch, and so on.

What’s actually happening here is that Swift is able to convert if someCondition into a special internal view type called ConditionalContent: it stores the condition and the true and false views, and can check it at runtime. However, if let creates a constant, and switch can have any number of cases, so neither can be used.

So, the fix here is to replace if let with a simple condition, then rely on SwiftUI’s support for optional views:

if image != nil {
    image?
        .resizable()
        .scaledToFit()
} else {
    Text("Tap to select a picture")
        .foregroundColor(.white)
        .font(.headline)
}

That code will now compile, and because image is nil you should see the “Tap to select a picture” prompt displayed over our gray rectangle.

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!

BUY OUR BOOKS
Buy Pro Swift Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift (Vapor Edition) Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Server-Side Swift (Kitura Edition) Buy Beyond Code

Was this page useful? Let us know!

Average rating: 5.0/5