Irbisa · Cheatsheet
SwiftUI Layout, Identity & Rendering
Layout protocol, GeometryReader, PreferenceKey, view identity, Canvas, lazy containers, insets
In this horizontal
ScrollViewtheSpacercollapses and the cards sit side by side: what is theScrollViewproposing, and how do you fix it?ScrollView(.horizontal) { HStack { CardView(card: first) Spacer() CardView(card: second) }}When would you use
GeometryReader, and what does it do to the layout around it?// Wrong: the reader eats the leftover height and pushes Footer downVStack { Title() GeometryReader { proxy in Chart(width: proxy.size.width) // drawn at the top-leading corner }…What is a
PreferenceKey, and how does a child pass a value up to its ancestors?struct WidthKey: PreferenceKey { static let defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = max(value, nextValue()) }}…How does
ViewThatFitspick which child to show, and why is it often better than checkinghorizontalSizeClass?ViewThatFits(in: .horizontal) { // 1. labels with icons HStack { ForEach(actions) { Label($0.title, systemImage: $0.icon) } } // 2. icons only…How do you wrap a UIKit view with
UIViewRepresentable, host SwiftUI in UIKit withUIHostingController, and how does each side learn the other's size?// UIKit inside SwiftUI: a multi-line label that wraps to the width SwiftUI offersstruct WrappingLabel: UIViewRepresentable { let text: String func makeUIView(context: Context) -> UILabel { let label = UILabel()…How do alignment guides work, and how do you line up views from different rows with a custom alignment?
// Badge level with the first line of a wrapping titleHStack(alignment: .firstTextBaseline) { Text(article.title) Badge("NEW")}…What is view identity in SwiftUI (structural versus explicit), and what happens when a view's identity changes?
The user picks another item in the sidebar, but this editor still shows the previous item's title: why, and how do you fix it?
struct DetailEditor: View { let item: Item @State private var draft: String init(item: Item) { self.item = item _draft = State(initialValue: item.title) } var body: some View { TextField("Title", text: $draft) }}SwiftUI warns that an ID occurs multiple times in a
ForEach, and rows start swapping state: what broke, and where do you fix it?Every time
isHighlightedflips, the counter in this row resets to zero: what is wrong with this conditional-modifier helper?extension View { @ViewBuilder func `if`<T: View>(_ condition: Bool, transform: (Self) -> T) -> some View { if condition { transform(self) } else { self } }} struct Row: View { let isHighlighted: Bool var body: some View { Counter() // holds @State private var count = 0 .if(isHighlighted) { $0.foregroundStyle(.red) } }}Find the bug: every label in this form is clipped to the width of "Phone", so "Email address" is truncated instead of the widest label setting the width.
struct LabelWidthKey: PreferenceKey { static let defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = nextValue() }}struct SettingsForm: View { @State private var labelWidth: CGFloat = 0 let titles = ["Name", "Email address", "Phone"] var body: some View { VStack(alignment: .leading) { ForEach(titles, id: \.self) { title in Text(title) .background(GeometryReader { Color.clear.preference(key: LabelWidthKey.self, value: $0.size.width) }) .frame(width: labelWidth == 0 ? nil : labelWidth, alignment: .leading) } } .onPreferenceChange(LabelWidthKey.self) { labelWidth = $0 } }}Why does
onAppearin aLazyVStackfire before the row is on screen, and how do you build pagination that survives it?@MainActor @Observablefinal class Feed { private(set) var items: [Item] = [] private var nextPage = 0 private var isLoading = false…Why does
proxy.scrollTo(id)called right after appending a message often miss, and what can you use instead ofScrollViewReaderon iOS 17–18?struct ChatView: View { @State private var messages: [Message] = [] @State private var position = ScrollPosition(idType: Message.ID.self) // iOS 18 var body: some View { ScrollView {…What breaks when you put
.ignoresSafeArea()on a screen's root, and how do you let only the background reach the edges?struct ProfileScreen: View { @State private var name = "" var body: some View { Form { TextField("Name", text: $name)…How would you build a tag layout that wraps onto new lines with the
Layoutprotocol, and what mustsizeThatFitsandplaceSubviewsdo?struct FlowLayout: Layout { var spacing: CGFloat = 8 func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { let frames = arrange(subviews, maxWidth: proposal.width ?? .infinity) // nil: one row return CGSize(width: frames.map(\.maxX).max() ?? 0, height: frames.map(\.maxY).max() ?? 0)…Find the bug: this tag layout draws its tags in the top-left corner of the screen instead of under the header in the
VStack.struct TagFlow: Layout { var spacing: CGFloat = 8 func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { measure(subviews, maxWidth: proposal.width ?? .infinity) // correct: sums the rows, no origin involved } func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) { var x: CGFloat = 0, y: CGFloat = 0, rowHeight: CGFloat = 0 for subview in subviews { let size = subview.sizeThatFits(.unspecified) if x + size.width > bounds.width, x > 0 { x = 0; y += rowHeight + spacing; rowHeight = 0 } subview.place(at: CGPoint(x: x, y: y), proposal: ProposedViewSize(size)) x += size.width + spacing rowHeight = max(rowHeight, size.height) } }}What do
compositingGroup()anddrawingGroup()do, and when does each one help or hurt?// wrong: the shadow is drawn under every childVStack { Text("Title"); Image(systemName: "star") } .shadow(radius: 6) // right: children compose first, one shadow for the resultVStack { Text("Title"); Image(systemName: "star") }…A waveform built from 2,000
Rectangleviews drops frames: when do you switch toCanvas, and what do you give up?struct Waveform: View { let samples: [Float] var body: some View { TimelineView(.animation) { timeline in Canvas { context, size in…Your
Cardcontainer must put a divider between its children, but aForEachpassed in counts as one view: how do you reach the real children?extension ContainerValues { @Entry var isPinned: Bool = false} struct Card<Content: View>: View { @ViewBuilder var content: Content…Bodies are cheap, but the SwiftUI instrument shows time spent in layout and the screen still hitches: what makes layout expensive?
What is the AttributeGraph, and how does SwiftUI decide which
bodyto run again after a state change?struct PriceChart: View, Equatable { let points: [PricePoint] let onSelect: (PricePoint) -> Void // a closure cannot be compared // nonisolated: Equatable is not main-actor isolated nonisolated static func == (lhs: Self, rhs: Self) -> Bool {…