Laying out a single-view

Let’s dive into some of the other views available in SwiftUI and walk through the layout of a design in Xcode. I’ve created a simple screen that includes many of the basic elements in SwiftUI which also make up a common design.

Backgrounds

Many tutorials work from the middle out, maybe picking an important element such as the title to begin. My designer brain wants me to start with the background, which is probably for the best. 

We will start by wrapping the entire view in a ZStack. It’s worth noting that the order of elements inside the ZStack is the opposite of your Figma/Sketch layers list, so we’ll start with the background first.

We’re going to declare a Color here. This is not a modifier—we’re really saying, give me a Rectangle of this color and fill the space. However we also need to turn on the .edgesIgnoringSafeArea modifier, otherwise the color won’t bleed to the edges.

With the background set I’m going to add a VStack after the color, which is the next layer. This vertical stack will group together all of the screen content. Should we want our button to be sticky (or any other element), we would not include that in the VStack.

From here we’re going to add from top to bottom, an image, two text views, a collection of shapes, our paragraph, and finally the button. We’re not going to achieve pixel perfect perfection, however we’ll come close and our design will be better for it.

Images are as common as text in most apps—even a basic list app is likely to have icons for the tab bar. Apple also introduced SF Symbols alongside SwiftUI, giving us access to a large collection of type weight scaled images. You can download the SF Symbols app from Apple and start exploring their icon collection, and export the template for use in your design mockup. As these images are included in iOS13 and above as a font, the symbols are already vector images. 

There just so happens to be a school desk symbol that seems perfect for this design. Similar to declaring a text view, the contents of the image will appear between the parentheses, after which we’ll add out modifiers.

Image(systemName: "studentdesk")
  .resizable()
  .scaledToFit()
  .frame(width: 123, height: 123)
  .padding(32)
  .overlay(RoundedRectangle(cornerRadius: 38)
      .stroke(Color.black, lineWidth: 8))

Initially the image will appear small so we use the resizable()scaledToFit() and frame() modifiers to tell SwiftUI, we want to resize the image, maintain proportions when scaling it, and declare the size we’ll make the image. I have then wrapped the image in a RoundedRectangle shape with 32 points of padding.

I’ve done this in an overlay which has the rectangle and stroke modifiers as its contents. Had I wanted a square box, I could have used a simple border() modifier which takes a color (Color.black) and width of 8 points. When we’re using more generic modifiers, we need to specifically write that we’re using a color which is the predefined system red.

Next we’ll place the two Text views for the label and headline. With the label, I’ve chosen the .subheadline font to get a 15 point font size. I’ve selected the .heavy font weight and given the text a custom color by using the foregroundColor modifier. The color is declared as an RGB value on the 0–255 scale.

Text("Prototyping with SwiftUI")
   .font(.subheadline)
   .fontWeight(.heavy)
   .foregroundColor(Color.init(red: 16/255, green: 88/255, blue: 138/255))
   .padding(.top, 48)

Text("Introduction \nto SwiftUI")
   .font(.system(.largeTitle, design: .serif))
   .fontWeight(.heavy)
   .multilineTextAlignment(.center)
   .padding(.top, 8)
 

With most of the views on this screen I have added padding just to the top to give varied spacing. We can actually define a constant spacing value for our stacks, which I will demonstrate below.

On the headline I’ve gone for a serif typeface in the design, which means I need to modify the text view to use this ‘design’ while also setting the text to largeTitle, the biggest type style in SwiftUI. While views are placed on the screen aligned down the middle, the text inside a text view is left aligned by default, so I added the multilineTextAlignment modifier. That was an easy one to add from the Inspector panel if you ever find yourself forgetting its name. 

Finally because I’m a designer, I put in a little hack to insert a soft line break after “Introduction” to create two even lines of type by using “\n”. [if you wanted to known \t = tab]

The next section really illustrates many different layout constraints in SwiftUI to create the three icons in code. We’ve already used the Z and V stack, so now welcome HStack to create the horizontal row. This essentially groups three more stacks in a row evenly spaced with 32 points between. These icons are all made from the Rectangle shape arranged in two VStacks and an HStack. The repeating Rectangles are actually quite repetitive so it’s possible to use code to build one item three times which you can see with the HStack.

HStack (spacing: 32) {
   VStack (spacing: 3) {

       Rectangle()
           .frame(width: 30, height: 8)
           .foregroundColor(Color.init(red: 254/255, green: 74/255, blue: 73/255))
       Rectangle()
           .frame(width: 30, height: 8)
           .foregroundColor(Color.init(red: 254/255, green: 74/255, blue: 73/255))
       Rectangle()
           .frame(width: 30, height: 8)
           .foregroundColor(Color.init(red: 254/255, green: 74/255, blue: 73/255))
   }

   HStack (spacing: 3) {
       ForEach(1...3, id: \.self) { rect in
           Rectangle()
               .frame(width: 8, height: 30)
               .foregroundColor(Color.init(red: 16/255, green: 119/255, blue: 128/255))
       }
   }

   VStack (spacing: 0) {

       Rectangle()
           .frame(width: 30, height: 11)
           .foregroundColor(Color.init(red: 252/255, green: 191/255, blue: 50/255))
           .rotation3DEffect(Angle(degrees: 45), axis: (x: 90.0, y: 0.0, z: 0.0))
       Rectangle()
           .frame(width: 30, height: 10)
           .foregroundColor(Color.init(red: 252/255, green: 191/255, blue: 50/255))
           .rotation3DEffect(Angle(degrees: 45), axis: (x: 90.0, y: 0.0, z: 0.0))
       Rectangle()
           .frame(width: 30, height: 10)
           .foregroundColor(Color.init(red: 252/255, green: 191/255, blue: 50/255))
           .rotation3DEffect(Angle(degrees: 45), axis: (x: 90.0, y: 0.0, z: 0.0))
   }
}
                      

The final VStack icon to simulate a ZStack has slightly different dimensions making a loop more work. This VStack also uses the rotation3DEffect modifier to tilt the Rectangle back 45-degrees on the x-axis. This took some trial and error while I first attempted to update the x- and then y-axes.

Following this is a paragraph describing the Introduction of SwiftUI course. The Text is set to .body type style, and aligned to match the rest of the design.

Text("SwiftUI is Apple’s awesome new UI toolkit for Swift. It’s perfect for designers looking to built interactive prototypes for showing off a design and validating with usability testing.")
  .font(.body)
  .multilineTextAlignment(.center)
  .padding(.top, 32)

Before adding the final view, including a Spacer() view that takes up the remaining vertical space and forces the button to the bottom and the elements above towards the top. You can add multiple ones, such as one above the Image to have 2 even Spacers filling the available space evenly.

Spacer()

The Button has two sections—the action (what will happen when someone taps the button) and the content & modifiers that make up the Button. You could put a number of different views inside the Button, such as a shape or image (or in fact an image that’s just an icon from SF Symbols).

Button(action: {
    // the action
}) {
   VStack {
      Text("Start course".uppercased())
        .fontWeight(.bold).font(.body)
        .frame(minWidth: 300, maxWidth: .infinity, minHeight: 44, maxHeight: 44)
            .background(Color.init(red: 254/255, green: 74/255, blue: 73/255))
        .foregroundColor(.white)
        .cornerRadius(8)
        .padding(.bottom, 16)
    }
 }.padding(.top, 16)

We’ve seen many of these modifiers before, however for the Button there are some differences. A background is set with a custom color. The foregroundColor is then set to white—the default tint on Buttons is the system blue. Since a background is used, the cornerRadius modifier works correctly—adding a border would then not work perfectly. A shadow can even be added to give the Button some depth.

One final piece, two padding modifiers on the whole VStack collection. Multiple padding modifiers are allowed in order to have greater control, first with 24 points of horizontal (left and right) padding, and 48 points added to the top to push the whole stack down slightly. Horizontal padding wasn’t included on any of the previous elements and instead gets applied to the entire VStack, and without it, elements go edge to edge.

.padding(.horizontal, 24)
.padding(.top, 48)

There we have one complete View or screen built from an existing design which can now be put on any number of iPhone models (or iPads) and tested. You can hit play on the Preview panel and test out the limited interactive features (tap the button), or plug an iPhone into your Mac and send the entire app to your device (if you have a dev account).

Seeing your design as a real SwiftUI layout on your device is valuable. For example, an iPhone 7, 8 or SE might present issues you don’t see in the Preview panel. Here there is no padding under the button so it touches the edge of the screen. Therefore a little .bottom padding can make a big difference.

© 2024 SwitftUI Prototyping