← All daysDay 11 of 49 · Part 3: Views and layout
Day 11

View modifiers, and why their order matters · Grids and custom layouts

These are the lesson notes for this day. Its build-along tutorial and flash cards have not been written yet.

Lesson 12

View modifiers, and why their order matters

A modifier is a method that returns a new view wrapping the original one. Each modifier applies to everything that comes before it in the chain, so changing the order changes the result. .padding().background(.red) produces a red area that includes the padding. .background(.red).padding() produces a red area that hugs the content, with clear padding around it. The same reasoning applies to tap areas, borders, and frames.

When the same group of modifiers appears in more than one place, give it a name: write a type that conforms to ViewModifier, or an extension on View that applies them.

Example
Text("Hello")
    .padding()
    .background(.red)      // red covers the text AND its padding

Text("Hello")
    .background(.red)      // red hugs the text only...
    .padding()             // ...and the padding around it stays clear
Exercise

Produce both versions shown above. Then build a tappable "chip" and make sure the whole padded area responds to taps, not only the text. Finally, turn your chip styling into a reusable ViewModifier.

After this lesson you should be able to answer
  1. Why does the order of .padding() and .background() matter?
  2. What does a modifier return?
  3. When should you create a custom ViewModifier?
Resources
Lesson 13

Grids and custom layouts

Grid lays out a fixed set of rows and columns whose cells line up with each other, like a table. LazyVGrid and LazyHGrid lay out a scrolling collection in columns that you describe with GridItem — fixed, flexible, or adaptive — and create cells only as they are needed. When neither does what you want, the Layout protocol lets you write your own container by implementing two methods: sizeThatFits, which reports the container's size, and placeSubviews, which positions each child.

Coming from UIKit: LazyVGrid covers most of what you used UICollectionView grid layouts for, and a custom Layout covers what you would have written as a custom UICollectionViewLayout.

Exercise

Build a photo grid with LazyVGrid and adaptive columns. Then build a small settings table with Grid whose labels line up in one column. Say why each container suits its job.

After this lesson you should be able to answer
  1. When would you choose Grid instead of LazyVGrid?
  2. What does GridItem(.adaptive(minimum: 100)) do?
  3. Which two methods must a custom Layout implement?
Resources
Write the code
Interview problem · Part 3

An infinite photo carousel

“Build a horizontally paging photo carousel that feels infinite in both directions, from five photos. Thirty minutes. Talk while you type.” Given: a Photo type that is Identifiable, and a finished PhotoCard view.

Where this comes from: DoorDash iOS interview, as reported by candidates (Prepfully) — recently reported by DoorDash candidates: “design a photo browser app with an infinite scroll carousel”

Show the answer
struct Carousel: View {
    let photos: [Photo]
    // "infinite" = far more copies than a thumb can travel
    private let laps = 1_000
    @State private var position: Int?

    var body: some View {
        ScrollView(.horizontal) {
            LazyHStack(spacing: 0) {
                ForEach(0 ..< photos.count * laps, id: \.self) { index in
                    PhotoCard(photo: photos[index % photos.count])
                        .containerRelativeFrame(.horizontal)
                }
            }
            .scrollTargetLayout()
        }
        .scrollTargetBehavior(.paging)
        .scrollPosition(id: $position)
        .scrollIndicators(.hidden)
        // begin in the middle, so both directions exist
        .onAppear { position = photos.count * laps / 2 }
    }
}

Three decisions worth saying out loud. A lazy stack, so that a thousand copies cost nothing until they are scrolled to. Modulo arithmetic to map a huge index onto five real photos. And starting in the middle, so that both directions exist from the first frame. scrollTargetLayout with .paging gives the snapping; containerRelativeFrame sizes each card to the scroll view without a GeometryReader.

Then name the trade-off before the interviewer does: this only appears infinite. It is honest for any human thumb, but a stricter solution re-centres the position when it nears either end. Say what you would add with ten more minutes: an accessibility value ("photo 3 of 5") and image prefetching.

What interviewers listen for: Laziness, a defensible choice of identity (here the index really is the identity), and trade-offs that you offer rather than have dragged out of you.