These are the lesson notes for this day. Its build-along tutorial and flash cards have not been written yet.
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.
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.
.padding() and .background() matter?ViewModifier?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.
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.
Grid instead of LazyVGrid?GridItem(.adaptive(minimum: 100)) do?Layout implement?“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”
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.