Chapters

Hide chapters

SwiftUI by Tutorials

Fifth Edition · iOS 16, macOS 13 · Swift 5.8 · Xcode 14.2

Before You Begin

Section 0: 4 chapters
Show chapters Hide chapters

7. Introducing Stacks & Containers
Written by Antonio Bello

Heads up... You’re accessing parts of this content for free, with some sections shown as gfsaxvfum text.

Heads up... You’re accessing parts of this content for free, with some sections shown as wljocbxad text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

In the previous chapter, you learned about common SwiftUI controls, including TextField, Button, Slider and Toggle. In this chapter, you’ll be introduced to container views, which are used to group related views together, as well as to lay them out in respect to each other.

Before starting, though, it’s essential to learn and understand how views are sized.

Preparing the Project

Before jumping into views and their sizes, be aware that the starter project for this chapter has some additions compared to the final project of the previous chapter.

If you want to keep working on your own copy, worry not! Just copy these files and add to your project, or drag and drop then directly into Xcode.

  • Practice/ChallengeView.swift
  • Practice/ChallengesViewModel.swift
  • Practice/ChoicesRow.swift
  • Practice/ChoicesView.swift
  • Practice/CongratulationsView.swift
  • Practice/PracticeView.swift
  • Practice/QuestionView.swift
  • StarterView.swift
  • HistoryView.swift

Layout and Priorities

In UIKit and AppKit, you were used to using Auto Layout to constrain views. The general rule was to let a parent decide the size of its children, usually obtained by adding constraints, unless their size was statically set using, for example, width and height constraints.

Heads up... You’re accessing parts of this content for free, with some sections shown as vlragmfuc text.

Heads up... You’re accessing parts of this content for free, with some sections shown as dwgughsuv text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
Size expectation
Womo obkalyomiih

Layout for Views With a Single Child

Open the starter project and go to Practice/ChallengeView.swift, which is a new view created out of the SwiftUI View template. You can see that it contains a single Text:

struct ChallengeView: View {
  var body: some View {
    Text("Hello World!")
  }
}
Blank Hello World
Hqoth Kiqni Vuxpf

Text("Hello World!")
  .background(Color.red)
Hello World 1
Qejhe Linvc 8

Text("A great and warm welcome to Kuchi")
  .background(Color.red)

Heads up... You’re accessing parts of this content for free, with some sections shown as wnmetxnem text.

Heads up... You’re accessing parts of this content for free, with some sections shown as clmymctod text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
Hello World 2
Hodte Tipvs 2

Text("A great and warm welcome to Kuchi")
  .background(Color.red)
  // fixed frame size
  .frame(width: 150, height: 50, alignment: .center)
  .background(Color.yellow)
Hello World 3
Gebpa Namqf 4

Heads up... You’re accessing parts of this content for free, with some sections shown as tpzantrek text.

Heads up... You’re accessing parts of this content for free, with some sections shown as gkticmmon text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
.frame(width: 300, height: 100, alignment: .center)
Hello World 4
Jijda Kawct 2

.frame(width: 100, height: 50, alignment: .center)
Hello World 5
Kokwo Sejgp 8

Text("A great and warm welcome to Kuchi")
  .background(Color.red)
  .frame(width: 100, height: 50, alignment: .center)
  // Add this scale factor
  .minimumScaleFactor(0.5)
  .background(Color.yellow)
Hello World 6
Zovya Hudrf 9

Heads up... You’re accessing parts of this content for free, with some sections shown as cjbultnyt text.

Heads up... You’re accessing parts of this content for free, with some sections shown as jvbafvjuz text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
Image("welcome-background")
  .background(Color.red)
  .frame(width: 100, height: 50, alignment: .center)
  .background(Color.yellow)
Hello World 7
Kimqi Mivrj 5

Image("welcome-background")
  .resizable()
Hello World 8
Collu Guchz 9

Stack Views

You’ve used stack views in earlier chapters, but you haven’t yet explored container views in any depth. The following section will go into more detail and teach you the logic behind the views.

Layout for Container Views

In the case of a container view, i.e., a view that contains two or more child views, the rules that determine children’s sizes are:

Heads up... You’re accessing parts of this content for free, with some sections shown as qfbavrfuz text.

Heads up... You’re accessing parts of this content for free, with some sections shown as xfnervsiz text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
HStack {
  Text("A great and warm welcome to Kuchi")
    .background(Color.red)
  Text("A great and warm welcome to Kuchi")
    .background(Color.red)
}
.background(Color.yellow)
Hello World 9
Gohyi Yitdv 1

Text("A great and warm welcome to Kuchi")
  .background(Color.red)
Text("A great and warn welcome to Kuchi") // <- Replace `m` with
                                          //    `n` in `warm`
  .background(Color.red)
Hello World 10
Hadxu Fobzr 44

Layout Priority

A container view sorts its children by restriction degree, going from the control with the most restrictive constraints to the one with the least. In case the restrictions are equivalent, the smallest will take precedence.

Heads up... You’re accessing parts of this content for free, with some sections shown as nplywztux text.

Heads up... You’re accessing parts of this content for free, with some sections shown as qsdupbzaj text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Modifier

You can use a modifier to make the view more or less adaptive. Examples include:

Priority

You also have the option of changing the layout priority using the .layoutPriority modifier. With this, you can explicitly alter the control’s weight in the sort order. It takes a Double value, which can be either positive or negative. A view with no explicit layout priority can be assumed to have a value equal to zero.

HStack(spacing: 2) {
  Text("A great welcome to Kuchi")
    .background(Color.red)

  Text("A great welcome to Kuchi")
    .background(Color.red)

  Text("A great welcome to Kuchi")
    .background(Color.red)
}
.background(Color.yellow)
Hello World 11
Pexlo Yijjy 72

Heads up... You’re accessing parts of this content for free, with some sections shown as dhwezrhob text.

Heads up... You’re accessing parts of this content for free, with some sections shown as glgyvkmul text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
HStack {
  Text("A great and warm welcome to Kuchi")
    .background(Color.red)

  Text("A great and warm welcome to Kuchi")
    .layoutPriority(1)
    .background(Color.red)

  Text("A great and warm welcome to Kuchi")
    .background(Color.red)
}
Hello World 12
Wobsu Hojws 81

HStack {
  Text("A great and warm welcome to Kuchi")
    .layoutPriority(-1)
    .background(Color.red)

  Text("A great and warm welcome to Kuchi")
    .layoutPriority(1)
    .background(Color.red)

  Text("A great and warm welcome to Kuchi")
    .background(Color.red)
}
Hello World 13
Laggi Povlr 87

Heads up... You’re accessing parts of this content for free, with some sections shown as rdxalsbyd text.

Heads up... You’re accessing parts of this content for free, with some sections shown as qtkexqluz text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
Hello World 14
Tuhme Xextt 50

Hello World 15
Gijgi Cujky 48

Hello World 16
Ciqre Goqgf 56

Heads up... You’re accessing parts of this content for free, with some sections shown as ndragxnem text.

Heads up... You’re accessing parts of this content for free, with some sections shown as hzgeppmik text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
Hello World 17
Cazda Nodyy 12

Hello World 18
Fakbo Hirpq 32

Hello World 19
Mirnu Tehfz 90

The HStack and the VStack

HStack and VStack are both container views, and they behave in the same way. The only difference is the orientation:

// HStack
init(
  alignment: VerticalAlignment = .center,
  spacing: CGFloat? = nil,
  @ViewBuilder content: () -> Content
)

// VStack
init(
  alignment: HorizontalAlignment = .center,
  spacing: CGFloat? = nil,
  @ViewBuilder content: () -> Content
)

Heads up... You’re accessing parts of this content for free, with some sections shown as hwmijhsef text.

Heads up... You’re accessing parts of this content for free, with some sections shown as kkmedtsuq text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

A Note on Alignment

While the VStack alignment can have three possible values — .center, .leading and .trailing — the HStack counterpart is a bit richer. Apart from center, bottom and top, it also has two very useful cases:

var body: some View {
  HStack() {
    Text("Welcome to Kuchi").font(.caption)
    Text("Welcome to Kuchi").font(.title)
    Button(action: {}, label: { Text("OK").font(.body) })
  }
}
HStack center
PJkotp mikgen

HStack(alignment: .bottom) {

Heads up... You’re accessing parts of this content for free, with some sections shown as xkfingpyt text.

Heads up... You’re accessing parts of this content for free, with some sections shown as ghmoppcar text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
HStack bottom
MFbazk juppop

HStack(alignment: .firstTextBaseline) {
HStack base
DHnolm mupo

The ZStack

With no AppKit and UIKit counterpart, the third stack component is ZStack, which stacks children views one on top of the other.

Other Container Views

It may sound obvious, but any view that can have a one-child view can become a container: simply embed its children in a stack view. So a component, such as a Button, which can have a label view, is not limited to a single Text or Image; instead, you can generate virtually any multi-view content by embedding everything into a stack view.

Heads up... You’re accessing parts of this content for free, with some sections shown as xrleldtow text.

Heads up... You’re accessing parts of this content for free, with some sections shown as wstupqmed text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Back to Kuchi

So far, this chapter has consisted mostly of theory and freeform examples to demonstrate specific features or behaviors. So, now it’s time to get your hands dirty and make some progress with the Kuchi app.

The Congratulations View

The congratulations view is used to congratulate the user after she gives five correct answers. Open CongratulationsView and take a look at its content.

struct CongratulationsView: View {
  let avatarSize: CGFloat = 120
  let userName: String

  init(userName: String) {
    self.userName = userName
  }

  var body: some View {
    EmptyView()
  }
}
var body: some View {
  VStack {
  }
}
VStack {
  Text("Congratulations!")
    .font(.title)
    .foregroundColor(.gray)
}
Congratulations view
Huyltohuboqaudm sioj

Text("You’re awesome!")
  .fontWeight(.bold)
  .foregroundColor(.gray)
Congrats View 2
Xaqrharx Book 8

Button(action: {
  challengesViewModel.restart()
}, label: {
  Text("Play Again")
})
.padding(.top)
struct CongratulationsView: View {
  // Add this property
  @ObservedObject
  var challengesViewModel = ChallengesViewModel()
  ...

Heads up... You’re accessing parts of this content for free, with some sections shown as pfwestwel text.

Heads up... You’re accessing parts of this content for free, with some sections shown as tldybwruw text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
Congrats View 3
Tarqmuxf Ceev 8

User Avatar

But let’s not stop there — surely you can make this look even better! How about adding the user’s avatar and their name on a colored background, but split vertically into two halves of a different color?

Congrats View 4
Jicqcumn Yeuw 2

Vstack
Skyods

// 1
ZStack {
  // 2
  VStack(spacing: 0) {
    Rectangle()
      // 3
      .frame(height: 90)
      .foregroundColor(
        Color(red: 0.5, green: 0, blue: 0).opacity(0.2))
    Rectangle()
      // 3
      .frame(height: 90)
      .foregroundColor(
        Color(red: 0.6, green: 0.1, blue: 0.1).opacity(0.4))
  }

  // 4
  Image(systemName: "person.fill")
    .resizable()
    .padding()
    .frame(width: avatarSize, height: avatarSize)
    .background(Color.white.opacity(0.5))
    .cornerRadius(avatarSize / 2, antialiased: true)
    .shadow(radius: 4)

  // 5
  VStack() {
    Spacer()
    Text(userName)
      .font(.largeTitle)
      .foregroundColor(.white)
      .fontWeight(.bold)
      .shadow(radius: 7)
  }
  .padding()
}
// 6
.frame(height: 180)

Heads up... You’re accessing parts of this content for free, with some sections shown as hlmonxmez text.

Heads up... You’re accessing parts of this content for free, with some sections shown as gxjivgdar text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
Congrats View
Hafxvizg Luox

The Spacer View

One thing worth mentioning is how Spacer is used inside the VStack at Step 5. The VStack contains the Spacer and the Text with the username — nothing else. So you might wonder why it’s even necessary?

Congrats View 2
Jekmjesw Duuc 5

Spacer
Qwikow

Heads up... You’re accessing parts of this content for free, with some sections shown as lqjegxsat text.

Heads up... You’re accessing parts of this content for free, with some sections shown as xzcygczan text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
Text("You're awesome!")
  .fontWeight(.bold)
  .foregroundColor(.gray)

Spacer() // <== The spacer goes here

Button(action: {
  self.challengesViewModel.restart()
}, label: {
  Text("Play Again")
})
Congrats View 3
Nizcresr Poog 8

VStack {
  Spacer() // <== The spacer goes here

  Text("Congratulations!")
  ...
Congrats View 4
Quhsjagl Noiz 8

Completing the Challenge View

Earlier you’ve used ChallengeView as a playground to test code shown throughout this chapter. Now you need to fill it with more useful code. The challenge view is designed to show a question and a list of answers.

Heads up... You’re accessing parts of this content for free, with some sections shown as dclospjip text.

Heads up... You’re accessing parts of this content for free, with some sections shown as wkbihhfak text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
let challengeTest: ChallengeTest

@State var showAnswers = false
// 1
static let challengeTest = ChallengeTest(
  challenge: Challenge(
    question: "おねがい します",
    pronunciation: "Onegai shimasu",
    answer: "Please"
  ),
  answers: ["Thank you", "Hello", "Goodbye"]
)

static var previews: some View {
  // 2
  return ChallengeView(challengeTest: challengeTest)
}
ChallengeView(challengeTest: challengeTest!)
var body: some View {
  // 1
  VStack {
    // 2
    Button(action: {
      showAnswers.toggle()
    }) {
      // 3
      QuestionView(question: challengeTest.challenge.question)
        .frame(height: 300)
    }

    // 4
    if showAnswers {
      Divider()
      // 5
      ChoicesView(challengeTest: challengeTest)
        .frame(height: 300)
        .padding()
    }
  }
}

Heads up... You’re accessing parts of this content for free, with some sections shown as xhkiqghyl text.

Heads up... You’re accessing parts of this content for free, with some sections shown as plsictdih text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
Challenge View preview
Mliztahyo Paet xqejeir

Reworking the App Launch

With the challenge view now completed, you still need to work on two other parts of the app in order to run:

@main
struct KuchiApp: App {
  let userManager = UserManager()

  init() {
    userManager.load()
  }

  var body: some Scene {
    WindowGroup {
      StarterView()
        .environmentObject(userManager)
    }
  }
}
struct KuchiApp_Previews: PreviewProvider {
  static let userManager = UserManager(name: "Ray")
  static var previews: some View {
    StarterView()
      .environmentObject(userManager)
  }
}
var body: some View {
  if self.userViewModel.isRegistered {
    WelcomeView()
  } else {
    RegisterView()
  }
}
@EnvironmentObject var userManager: UserManager
@ObservedObject var challengesViewModel = ChallengesViewModel()
@State var showPractice = false

Heads up... You’re accessing parts of this content for free, with some sections shown as sxfudgtol text.

Heads up... You’re accessing parts of this content for free, with some sections shown as vxvecjhuc text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
struct WelcomeView_Previews: PreviewProvider {
  static var previews: some View {
    WelcomeView()
      .environmentObject(UserManager())
  }
}
var body: some View {
  if showPractice {
    // 1
    PracticeView(
      challengeTest: $challengesViewModel.currentChallenge,
      userName: $userManager.profile.name
    )
  } else {
    // 2
    ZStack {
      WelcomeBackgroundImage()

      VStack {
        Text(verbatim: "Hi, \(userManager.profile.name)")

        WelcomeMessageView()

        // 3
        Button(action: {
          self.showPractice = true
        }, label: {
          HStack {
            Image(systemName: "play")
            Text(verbatim: "Start")
          }
        })
      }
    }
  }
}
Kuchi
Rozna

Kuchi 2
Tibge 2

The Lazy Stacks

Stacks are very useful to lay out views in one direction or another. In most cases, they are all you need for that purpose. There’s one exception though, which is when the number of views to stack one after the other is large.

Heads up... You’re accessing parts of this content for free, with some sections shown as qwveszvyn text.

Heads up... You’re accessing parts of this content for free, with some sections shown as kcjixbdyc text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Practice History

To see lazy stacks in action, you’re going to build a history view that displays all the recent challenges. Since we don’t have any tracked history yet, you’ll randomly generate some data.

var body: some View {
  LazyVStack {      
  }
}
init(
  _ data: Data,
  id: KeyPath<Data.Element, ID>,
  content: @escaping (Data.Element) -> Content
)

Heads up... You’re accessing parts of this content for free, with some sections shown as lpwybcbaw text.

Heads up... You’re accessing parts of this content for free, with some sections shown as pgxadwceb text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
ForEach(history, id: \.self) { element in
}
ForEach(history, id: \.self) { element in
  getElement(element)
}
Lazy Stack
Nacd Ztunr

ScrollView {
  LazyVStack {
    ForEach(history, id: \.self) { element in
      getElement(element)
    }
  }
}
Section(header: header) {
  ForEach(history, id: \.self) { element in
    getElement(element)
  }
}
Lazy Stack with header
Gixd Fzasj hoyt waayum

Heads up... You’re accessing parts of this content for free, with some sections shown as kfritztap text.

Heads up... You’re accessing parts of this content for free, with some sections shown as zrfomlwuj text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
LazyVStack(spacing: 0, pinnedViews: [.sectionHeaders]) {
  Section(header: header) {
    ForEach(history, id: \.self) { element in
      getElement(element)
    }
  }
}

Key Points

Another long chapter — but you did a great job of getting through it! A lot of concepts have been covered here, the most important ones being:

Where to Go From Here?

To know more about container views, the WWDC video that covers them is a must-watch:

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2025 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as wvcemhxen text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now