SwiftUI Fundamentals

Feb 28 2023 · Swift 5.7, macOS Venture 13.1, Xcode 14.2

Part 1: SwiftUI Views

09. Challenge: Stacks & ForEach

Episode complete

Play next episode

Next
About this episode

Leave a rating/review

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 08. Repeat Views with ForEach & Lists Next episode: 10. Introduction

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Transcript: 09. Challenge: Stacks & ForEach

This is your last challenge in this part!

Your goal is to recreate this interface from a Weather app widget.

I did set up a starter project for you with many of the elements styled and some data to use, so you can focus on the layout.

But if you want an extra challenge and more practice, there’s nothing in that starter file that you couldn’t make yourself, including the data model. All you need is your own Swift skills, regular SwiftUI components, and some SFSymbols. So, you can start from scratch!

Remember that SwiftUI is great for extracting and reusing little views. And, I recommend employing ForEach for this challenge instead of a list.

Ok! In the starter project, you’ve got this Weather struct, with a nested Forecast type, and this array of 5 hours worth of forecasts.

And up in the body of ContentView, I’ve got this first VStack all set up for you with some styling.

And then all of these little views are just hanging out in that VStack.

Now I’ve got to make this view in the first tab look like the image on the second tab!

I’ll start at the top and work my way down.

First, these two texts can be stacked together

VStack(alignment: .leading) {
  Text("Cupertino")
    .font(.title3)
  Text("63°")
    .font(.largeTitle)
}

And then the image and the other two texts should be stacked together, this time with a trailing alignment.

VStack(alignment: .trailing) {
  Image(systemName: "sun.max.fill")
    .renderingMode(.original)
  
  Text("Sunny")
  Text("H:68° L:42°")
}
  • Now I can Stack both of those little VStacks into an HStack…
  • and stick a Spacer in between to push each to it’s own side of the view.
  • You could align this HStack on the bottom, and if you did, that’s fine, but there is another kind of alignment for HStacks that takes the baseline of text into account. So, I’m going to use “lastTextBaseline” which will make these VStacks align based on the baselines of this big 63 degrees and the Hi and Lo temp text.
HStack(alignment: .lastTextBaseline) {
  VStack(alignment: .leading) { }

  Spacer()

  VStack(alignment: .trailing) { }
}

With that all set up, this is a great time to extract the whole HStack into a subview!

struct CurrentConditions: View {
...
}

Next is the part that requires ForEach.

First, I’ll stack these three little views together

VStack {
  Text(weather.hourlyForecast[0].hour)
  
  weather.hourlyForecast[0].conditions
    .renderingMode(.original)
    .frame(height: 50)
  
  Text(weather.hourlyForecast[0].temperature)
}
  • And then, I’m actually going to extract just that little stack into a subview so I can repeat it with ForEach.
  • Add a forecast property to pass in the data it needs
  • and adjust the views inside the stack to use that data
struct HourlyForecast: View {
  let forecast: Weather.Forecast
  
  var body: some View {
    VStack {
      Text(forecast.hour)
      
      forecast.conditions
        .renderingMode(.original)
        .frame(height: 50)
      
      Text(forecast.temperature)
    }
  }
}
  • Now back up in ContentView, I can start an HStack
  • and ForEach…
  • Pass in the hourlyForecast, and I’ll use the hour as the ID
  • then use that HourlyForecast view inside the content closure.
 HStack {
  ForEach(weather.hourlyForecast, id: \.hour) { forecast in
    HourlyForecast(forecast: forecast)
  }
}

This is pretty close to the example, but the little forecast views are all squished together.

I’ll add a spacer on each side of each little view to space them out equally.

 HStack {
  ForEach(weather.hourlyForecast, id: \.hour) { forecast in
    🟢Spacer()
    HourlyForecast(forecast: forecast)
    🟢Spacer()
  }
}

And that’s it! A nice little weather widget view.