Irbisa · Cheatsheet

SwiftUI Fundamentals

View protocol, modifiers, layout containers, navigation, lists

Junior Developer · 25 questions

  1. 01

    What is SwiftUI, and what are its advantages and drawbacks compared with UIKit?

    SwiftUI is Apple's declarative UI framework: you describe what the screen looks like for the current state, and the framework works out which changes to apply.

  2. 02

    What does declarative UI mean, and why are SwiftUI views structs rather than classes?

    Declarative means you describe the result (what the screen looks like for a given state) and the framework performs the updates.

  3. 03

    What is the View protocol, what is body, and what does some View mean?

    View is the protocol every SwiftUI view conforms to, and its one requirement is a computed body that returns other views.

    struct ProfileHeader: View {  let name: String  let isPro: Bool   // The real type is VStack<TupleView<(Text, Text?)>>; `some View` hides it  var body: some View {…
  4. 04

    What do @main, App and Scene do in a SwiftUI app?

    @main marks the app's entry point, the App protocol describes the app as a whole, and its body returns one or more scenes: containers such as WindowGroup that the system turns into windows.

    @mainstruct NotesApp: App {  @State private var store = NotesStore()   // @Observable model owned by the app  @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate  @Environment(\.scenePhase) private var scenePhase…
  5. 05

    How do VStack, HStack and ZStack lay out their children, and when do you need LazyVStack instead?

    VStack places children top to bottom, HStack from leading to trailing, and ZStack layers them back to front.

    VStack(alignment: .leading, spacing: 4) {  Text("Title").font(.headline)  Text("Subtitle").foregroundStyle(.secondary)} HStack {…
  6. 06

    What does each of these three badges look like, and why does swapping two modifiers change the result?

    // Badge AText("Sale")  .padding()  .background(.red) // Badge BText("Sale")  .background(.red)  .padding() // Badge CText("Sale")  .background(.red)  .frame(width: 200)

    Badge A is a red box with the text inside default padding, B is a tight red strip with invisible padding around it, and C is red only behind the text inside a wider, empty frame.

  7. 07

    How do you write a reusable style in SwiftUI: a custom ViewModifier or a ButtonStyle?

    Use a ViewModifier to bundle modifiers you apply to any view, such as a card look, and a ButtonStyle to style buttons, because it also receives the pressed state and the button's role.

    struct CardStyle: ViewModifier {  func body(content: Content) -> some View {    content      .padding()      .background(.regularMaterial, in: .rect(cornerRadius: 16))  }…
  8. 08

    You stretched a Text with .frame(maxWidth: .infinity) and it stayed centred. How do the different alignments in SwiftUI differ?

    .frame centres its child by default, so you need .frame(maxWidth: .infinity, alignment: .leading).

    // wrong: the frame stretches, the text stays in its centreText("Title").frame(maxWidth: .infinity) // right: tell the frame where to put its childText("Title").frame(maxWidth: .infinity, alignment: .leading)…
  9. 09

    A bundled Image ignores its .frame and .scaledToFill() spills over neighbours; in what order do the modifiers go, and what does AsyncImage not do for you?

    Image keeps its natural size until you call .resizable(), and .scaledToFill() draws outside its frame unless you clip it.

    // wrong: a bundled image ignores the proposed size until it is resizableImage("cover").frame(width: 120, height: 120)   // full size, overflowing // right: resizable, content mode, frame, then clip what .fill spillsImage("cover")  .resizable()…
  10. 10

    How do you build a List with ForEach, and why does every row need a stable id?

    List is a scrolling, platform-styled container, and ForEach creates one row per element of a collection.

    struct Todo: Identifiable {  let id: UUID          // stable: stored on the model, not generated in body  var title: String  var isDone = false}…
  11. 11

    How do you build a form in SwiftUI with TextField, SecureField, Toggle and Picker?

    Wrap the controls in a Form, which gives them the grouped settings look, and bind each control to state with $, as in TextField("Email", text: $email).

    struct SignUpForm: View {  @State private var email = ""  @State private var password = ""  @State private var plan: Plan = .free  @State private var wantsNews = false…
  12. 12

    How do you present a sheet or an alert in SwiftUI, and when do you use the item: form instead of isPresented:?

    Presentation is a modifier bound to state: set a Bool or an optional value and SwiftUI shows the sheet or alert; reset it and the screen goes away.

    struct InboxView: View {  @State private var editing: Message?          // nil means no sheet  @State private var pendingDelete: Message?   var body: some View {    List(messages) { message in…
  13. 13

    When do you use LazyVGrid and when Grid, and how do you make the column count follow the screen width?

    LazyVGrid flows a collection into columns and builds cells lazily, which suits a gallery; Grid is an eager table whose rows share column widths, which suits a form or comparison table.

    // Gallery: as many 120-pt columns as fit, cells built lazilyScrollView {  LazyVGrid(columns: [GridItem(.adaptive(minimum: 120), spacing: 8)], spacing: 8) {    ForEach(photos) { photo in      Thumbnail(photo).frame(height: 120)    }…
  14. 14

    How do you write SwiftUI previews for a view with several states or a service dependency?

    Use the #Preview macro (one named preview per state that can break the layout) and feed it sample data and fake services, so the preview never touches the network or a real database.

    #Preview("Default") {  ProfileCard(user: .preview)} #Preview("Long bio, largest text") {  ProfileCard(user: User(id: UUID(), name: "Alice",…
  15. 15

    When do you use a List, and when a ScrollView with a LazyVStack?

    Use List for long, uniform data: it reuses rows and brings separators, swipe actions, selection and edit mode for free.

  16. 16

    How does SwiftUI decide a view's size: who has the final say, the parent or the child?

    Layout is a negotiation: the parent proposes a size, the child picks its own size, and the parent then places the child.

    // .frame proposes a size and positions whatever the child returnsText(longString).frame(width: 100)            // Text wraps into 100 pt // fixedSize asks for the ideal size: stop the vertical truncationText(paragraph)  .fixedSize(horizontal: false, vertical: true)…
  17. 17

    How does NavigationStack work, and how do you navigate programmatically or from a deep link?

    NavigationStack keeps its screens as a path of values: NavigationLink(value:) appends a value, .navigationDestination(for:) turns a value of that type into a screen, and Back removes the last one.

    enum Route: Hashable {  case profile(userID: String)  case settings} struct RootView: View {…
  18. 18

    Tapping a row in this list does nothing, and Xcode logs a navigation warning: what is wrong?

    struct ShopView: View {  let products: [Product]   var body: some View {    NavigationStack {      List(products) { product in        NavigationLink(product.name, value: product)          .navigationDestination(for: Product.self) { product in            ProductDetail(product: product)          }      }      .navigationTitle("Shop")    }  }}

    The .navigationDestination sits inside the List rows.

  19. 19

    What does this code print when the screen appears and on each tap, and what does that tell you about init, body and onAppear?

    struct Child: View {  init() { print("init") }  var body: some View {    let _ = print("body")    Text("Child").onAppear { print("appear") }  }} struct Parent: View {  @State private var count = 0  var body: some View {    VStack { Text("Taps: \(count)"); Child(); Button("Tap") { count += 1 } }  }}

    In a plain run, on first appearance it prints init, body, appear; after every tap, only init.

  20. 20

    Find the bug: each time the user comes back to this screen the stopwatch counts faster, and it keeps ticking while another screen is on top.

    struct StopwatchView: View {  @State private var seconds = 0   var body: some View {    Text("\(seconds) s")      .font(.largeTitle.monospacedDigit())      .onAppear {        Task {          do {            while true {              try await Task.sleep(for: .seconds(1))              seconds += 1            }          } catch {}        }      }  }}

    The Task started in .onAppear is not tied to the view, so nothing ever cancels it: each return to the screen starts one more endless loop, and all of them keep adding to seconds, even while another screen covers this one.

  21. 21

    What is @ViewBuilder doing behind the scenes, and why is returning AnyView a bad habit?

    @ViewBuilder is a result builder that folds a list of statements into one nested generic view type, which is how body can hold several views and an if without you writing that type out.

    // Your own container that takes a builderstruct Card<Content: View>: View {  @ViewBuilder var content: Content   var body: some View {    VStack(alignment: .leading, spacing: 8) { content }…
  22. 22

    What is the difference between withAnimation and the .animation modifier, and why was the single-argument .animation(_:) deprecated?

    withAnimation animates every change caused by the state mutation inside its closure, wherever the affected views sit; the .animation(_:value:) modifier animates one view's reaction to one named value.

    struct Panel: View {  @State private var isExpanded = false  @State private var count = 0   var body: some View {    VStack {…
  23. 23

    A list row has a star button and a delete button, and tapping either one runs both plus the row's navigation: what is happening?

    Inside a List row, buttons with the default style share the whole row as their tap target, so one tap triggers every button in the row.

    // wrong: .automatic buttons inherit the row's tap regionList(messages) { message in  NavigationLink(value: message) {    HStack {      Text(message.title)      Spacer()…
  24. 24

    After saving on the fourth pushed screen you must land back on the root, and the same flow also runs inside a sheet: how do you build that?

    Pop-to-root is one data change (clearing the array that drives the NavigationStack), so the deep screen needs access to that path, because @Environment(\.dismiss) only ever undoes one level.

    @Observable @MainActorfinal class Router { var path: [Route] = [] } struct RootView: View {  @State private var router = Router()  @State private var sheetRouter = Router()…
  25. 25

    Find the bug: picking another article updates the title, but this web view keeps showing the first page.

    struct WebView: UIViewRepresentable {  let url: URL   func makeUIView(context: Context) -> WKWebView {    let webView = WKWebView()    webView.load(URLRequest(url: url))    return webView  }   func updateUIView(_ webView: WKWebView, context: Context) {}} // In the parent: WebView(url: selectedArticle.url)

    updateUIView is empty, so the new url never reaches the WKWebView.