User Interfaces and App Architecture
Declarative UI
SwiftUI views are lightweight structs describing UI as a function of state. body must be pure and fast; never mutate state while it is being evaluated. State drives rendering: @State owns local value state, @Binding shares a value owned elsewhere, @StateObject owns a reference-type model for the view's lifetime, @ObservedObject observes a model it does not own, and @EnvironmentObject reads an injected observable object. Model classes conform to ObservableObject and publish changes with @Published. Use ForEach with stable Identifiable IDs or explicit ids to preserve identity; unstable ids reset state.
UIKit counterparts
View controllers manage a view hierarchy and follow lifecycle callbacks: viewDidLoad once, viewWillAppear and viewDidAppear, and viewDidDisappear. Outlets are weak implicitly unwrapped references to nib-owned views. Delegates are usually weak to avoid retain cycles; closures stored long-term should capture self weakly when appropriate. Update UI only on the main thread.
Architecture
MVC separates model, view, and controller; MVVM adds an observable view model that holds presentation logic and is testable without UI. Keep a single source of truth, pass dependencies explicitly, and keep networking and persistence behind protocols so they can be replaced in tests. Navigation belongs in the view layer; business rules belong in models or view models.
Traps
- Using
@StateObjectfor a model passed in from a parent, creating duplicate state. - Mutating
@Stateinsidebody, causing update loops. - Strong delegate or closure references that leak view controllers.
- Doing UI work off the main thread.
Rule of thumb
Ask "who owns this state?" before choosing a property wrapper. Ownership determines @StateObject versus @ObservedObject, and clarity there prevents most data-flow bugs.