A comprehensive guide covering everything from fundamentals to advanced SwiftUI concepts — structured point by point with visual diagrams.
1. What is SwiftUI?
Q: What is SwiftUI and when was it introduced?
SwiftUI is Apple's modern declarative UI framework introduced at WWDC 2019 (iOS 13+). It allows developers to build user interfaces for all Apple platforms — iOS, macOS, watchOS, and tvOS — using a single, unified Swift codebase.
✅ Key Advantages
┌─────────────────────────────────────────────────────────────┐
│ SwiftUI Advantages │
├─────────────────────┬───────────────────────────────────────┤
│ Declarative Syntax │ Describe WHAT the UI looks like, │
│ │ not HOW to build it step by step │
├─────────────────────┼───────────────────────────────────────┤
│ Live Preview │ See real-time UI changes in Xcode │
│ │ without recompiling the entire app │
├─────────────────────┼───────────────────────────────────────┤
│ Less Code │ Achieve complex layouts with far │
│ │ fewer lines than UIKit │
├─────────────────────┼───────────────────────────────────────┤
│ Multi-Platform │ One codebase targets iOS, macOS, │
│ │ watchOS, and tvOS │
├─────────────────────┼───────────────────────────────────────┤
│ Built-in Animation │ Add smooth animations with simple │
│ │ modifiers and state changes │
├─────────────────────┼───────────────────────────────────────┤
│ Auto Accessibility │ Default accessibility support baked │
│ │ in without extra configuration │
└─────────────────────┴───────────────────────────────────────┘
💻 Basic SwiftUI Example
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, SwiftUI!")
.font(.title)
.bold()
}
.padding()
}
}
2. SwiftUI vs UIKit
Q: What are the key differences between SwiftUI and UIKit?
┌─────────────────────────────────────────────────────────────────┐
│ SwiftUI vs UIKit │
├──────────────────────┬──────────────────┬───────────────────────┤
│ Feature │ SwiftUI │ UIKit │
├──────────────────────┼──────────────────┼───────────────────────┤
│ Paradigm │ Declarative │ Imperative │
│ Language │ Swift only │ Swift / Objective-C │
│ iOS Support │ iOS 13+ │ iOS 2+ │
│ UI Tool │ Code / Preview │ Storyboard / Code │
│ State Mgmt │ Built-in (@State)│ Manual │
│ Layout │ Stacks, Grids │ Auto Layout │
│ Learning Curve │ Lower (modern) │ Higher (legacy) │
│ Maturity │ Growing │ Very Mature │
│ Animations │ Simple modifiers │ Complex API │
│ Cross-Platform │ Yes │ iOS only │
└──────────────────────┴──────────────────┴───────────────────────┘
⚠️ When to Choose UIKit Over SwiftUI
- Supporting iOS 12 or earlier
- Highly complex, performance-critical interfaces
- Using legacy Objective-C codebases
- Needing more fine-grained control over the render cycle
3. Declarative vs Imperative UI
Q: What is the difference between declarative and imperative UI programming?
IMPERATIVE (UIKit)
┌────────────────────────────────────────────┐
│ Step 1: Create a UILabel │
│ Step 2: Set text = "Hello" │
│ Step 3: Set font, color, frame │
│ Step 4: Add to view hierarchy │
│ Step 5: Manually update when data changes │
│ Step 6: Manage lifecycle yourself │
└────────────────────────────────────────────┘
↓ "Tell me HOW to do it"
DECLARATIVE (SwiftUI)
┌────────────────────────────────────────────┐
│ Describe: "I want a bold Text saying │
│ 'Hello', red foreground, large font." │
│ │
│ SwiftUI handles rendering + updates │
│ automatically when data changes │
└────────────────────────────────────────────┘
↓ "Tell me WHAT you want"
// ✅ SwiftUI — Declarative
Text("Hello, \(name)!")
.font(.title)
.foregroundColor(.red)
// ❌ UIKit — Imperative equivalent
let label = UILabel()
label.text = "Hello, \(name)!"
label.font = UIFont.systemFont(ofSize: 24)
label.textColor = .red
view.addSubview(label)
// + Auto Layout constraints...
4. The View Protocol
Q: What is the
Viewprotocol in SwiftUI and why are viewsstructtypes?
Every visible element in SwiftUI conforms to the View protocol. It requires a single computed property:
protocol View {
associatedtype Body: View
var body: Self.Body { get }
}
Why Struct (Value Type) Instead of Class?
┌──────────────────────────────────────────────────────────┐
│ Struct vs Class for Views │
├──────────────────────┬───────────────────────────────────┤
│ Struct ✅ │ Class ❌ │
├──────────────────────┼───────────────────────────────────┤
│ Value type (copied) │ Reference type (shared) │
│ No inheritance │ Inheritance adds complexity │
│ Thread-safe by nature│ Needs manual synchronization │
│ Lightweight │ Heavier (heap allocation) │
│ Immutable body │ Mutable state = harder to reason │
│ SwiftUI re-creates │ Lifecycle management required │
│ them cheaply │ │
└──────────────────────┴───────────────────────────────────┘
struct MyView: View {
var title: String
var body: some View {
Text(title)
.font(.headline)
.padding()
}
}
5. Layout System: VStack, HStack, ZStack
Q: Explain SwiftUI's layout containers.
┌──────────────────────────────────────────────────────────┐
│ SwiftUI Layout Containers │
│ │
│ VStack (Vertical) HStack (Horizontal) │
│ ┌──────────┐ ┌────────────────────┐ │
│ │ [Item1] │ │ [Item1][Item2][Item3] │ │
│ │ [Item2] │ └────────────────────┘ │
│ │ [Item3] │ │
│ └──────────┘ ZStack (Layered / Z-axis) │
│ ┌──────────────────┐ │
│ │ Background │ │
│ │ ┌──────┐ │ │
│ │ │ Text │ │ │
│ │ └──────┘ │ │
│ └──────────────────┘ │
└──────────────────────────────────────────────────────────┘
// VStack — vertical arrangement
VStack(alignment: .leading, spacing: 10) {
Text("Title").font(.title)
Text("Subtitle").font(.subheadline)
Button("Tap Me") { }
}
// HStack — horizontal arrangement
HStack(spacing: 16) {
Image(systemName: "star.fill")
Text("Favorites")
Spacer() // pushes content to edges
Badge(count: 3)
}
// ZStack — layer on top of each other
ZStack {
Color.blue.ignoresSafeArea() // background layer
Image("banner") // middle layer
Text("Overlay Text") // top layer
.foregroundColor(.white)
}
Lazy Variants (Performance)
// Use LazyVStack / LazyHStack for large data sets
// They only render views that are currently visible
ScrollView {
LazyVStack {
ForEach(0..<1000) { index in
Text("Row \(index)")
}
}
}
6. State Management: @State
Q: What is
@Stateand when should you use it?
@State is a property wrapper that lets SwiftUI manage a value locally within a single view. When the value changes, SwiftUI automatically re-renders the body.
@State Flow Diagram
┌─────────────────────────┐
│ SwiftUI View │
│ │
│ @State var count = 0 │◄──── SwiftUI owns & manages
│ │ │
│ ▼ │
│ body re-renders │
│ when count changes │
└─────────────────────────┘
│
▼
User taps button → count += 1 → View updates
struct CounterView: View {
@State private var count = 0 // 1. Declare state
var body: some View {
VStack(spacing: 20) {
Text("Count: \(count)") // 2. Read state
.font(.largeTitle)
Button("Increment") {
count += 1 // 3. Mutate state → auto re-render
}
.buttonStyle(.borderedProminent)
}
}
}
🔑 Key Rules for @State
- Always declare
@Stateasprivate - Only use inside the view that owns the data
- For sharing across views → use
@Binding - For complex objects → use
@StateObject
7. @Binding
Q: What is
@Bindingand how does it differ from@State?
@Binding creates a two-way connection between a parent view's state and a child view. The child can read and write the value without owning it.
@State / @Binding Relationship
Parent View Child View
┌──────────────────┐ ┌──────────────────┐
│ @State var │ │ @Binding var │
│ isOn = false │────────►│ isOn: Bool │
│ │◄────────│ │
│ (owns the data) │ sync │ (references data)│
└──────────────────┘ └──────────────────┘
│ │
└─────── Both update ─────────┘
the same source of truth
// Parent — owns the state
struct ParentView: View {
@State private var isToggled = false
var body: some View {
ToggleView(isOn: $isToggled) // $ creates a Binding
}
}
// Child — receives a binding
struct ToggleView: View {
@Binding var isOn: Bool // Does NOT own the data
var body: some View {
Toggle("Enable Feature", isOn: $isOn)
}
}
8. @ObservedObject & @StateObject
Q: What is the difference between
@StateObjectand@ObservedObject?
Both work with ObservableObject classes, but differ in ownership and lifecycle.
┌─────────────────────────────────────────────────────────────┐
│ @StateObject vs @ObservedObject │
├──────────────────────────┬──────────────────────────────────┤
│ @StateObject ✅ │ @ObservedObject │
├──────────────────────────┼──────────────────────────────────┤
│ VIEW creates the object │ Object injected from outside │
│ VIEW owns the object │ View does NOT own it │
│ Persists across re-draws │ May be re-created on re-draw │
│ Use in CREATING view │ Use in RECEIVING view │
│ │ │
│ @StateObject var vm = │ @ObservedObject var vm: │
│ ViewModel() │ ViewModel │
└──────────────────────────┴──────────────────────────────────┘
// Observable class
class UserViewModel: ObservableObject {
@Published var username = "John"
@Published var score = 0
func incrementScore() {
score += 1
}
}
// ✅ Use @StateObject in the view that CREATES the object
struct ProfileView: View {
@StateObject private var viewModel = UserViewModel()
var body: some View {
VStack {
Text("User: \(viewModel.username)")
Text("Score: \(viewModel.score)")
Button("Add Score") { viewModel.incrementScore() }
ScoreBoard(viewModel: viewModel) // pass down
}
}
}
// ✅ Use @ObservedObject in views that RECEIVE the object
struct ScoreBoard: View {
@ObservedObject var viewModel: UserViewModel
var body: some View {
Text("Current Score: \(viewModel.score)")
}
}
9. @EnvironmentObject
Q: What is
@EnvironmentObjectand when should you use it?
@EnvironmentObject allows you to share data across many views in the hierarchy without passing it explicitly through each child.
@EnvironmentObject — Shared Data Across Tree
App Level
┌──────────────┐
│ AppState │ ◄── injected with .environmentObject()
└──────┬───────┘
│ available to ALL descendants
┌─────────▼──────────┐
│ HomeView │
└───────┬────────────┘
┌──────▼──────┐
│ SettingsView│ ← can access AppState directly
└─────┬────────┘
│
┌─────▼────────┐
│ UserProfile │ ← can also access AppState directly
└──────────────┘
No need to pass through each level! ✅
// 1. Define the shared model
class AppState: ObservableObject {
@Published var isLoggedIn = false
@Published var currentUser = ""
}
// 2. Inject at the root
@main
struct MyApp: App {
@StateObject var appState = AppState()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(appState) // inject here
}
}
}
// 3. Use anywhere in the hierarchy
struct ProfileView: View {
@EnvironmentObject var appState: AppState // no passing needed
var body: some View {
Text("Hello, \(appState.currentUser)")
}
}
10. Data Flow Architecture
Q: How does data flow work in SwiftUI?
SwiftUI Data Flow — Unidirectional
┌──────────────────────────────────────────────┐
│ │
│ User Action (tap, swipe, input) │
│ │ │
│ ▼ │
│ State/Model Changes │
│ (@State, @Published, etc.) │
│ │ │
│ ▼ │
│ SwiftUI detects change │
│ │ │
│ ▼ │
│ View body re-evaluated │
│ │ │
│ ▼ │
│ Diff computed (minimal updates) │
│ │ │
│ ▼ │
│ UI Updated on screen │
│ │ │
│ └────────────────────────────────── │
│ ↑ cycle repeats │
└──────────────────────────────────────────────┘
11. Property Wrappers Comparison
Q: Can you summarize all SwiftUI property wrappers?
┌──────────────────────────────────────────────────────────────────────┐
│ SwiftUI Property Wrappers — Quick Reference │
├────────────────────┬─────────────────────────┬───────────────────────┤
│ Wrapper │ Purpose │ Use When │
├────────────────────┼─────────────────────────┼───────────────────────┤
│ @State │ Local value storage │ Simple local UI state │
│ @Binding │ Two-way reference │ Pass state to child │
│ @StateObject │ Owns ObservableObject │ Creating a VM in view │
│ @ObservedObject │ References Observable │ Receiving a VM │
│ @EnvironmentObject │ Shared across hierarchy │ App-wide shared data │
│ @Environment │ System values │ colorScheme, locale │
│ @Published │ Observable property │ Inside ObservableObj │
│ @AppStorage │ UserDefaults binding │ Persist user prefs │
│ @SceneStorage │ Scene-specific storage │ Per-scene state │
│ @FetchRequest │ CoreData query │ CoreData integration │
└────────────────────┴─────────────────────────┴───────────────────────┘
// @Environment — reading system values
struct ThemeAwareView: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
Text("Mode: \(colorScheme == .dark ? "Dark" : "Light")")
}
}
// @AppStorage — UserDefaults made easy
struct SettingsView: View {
@AppStorage("isDarkMode") var isDarkMode = false
var body: some View {
Toggle("Dark Mode", isOn: $isDarkMode)
}
}
12. Animations in SwiftUI
Q: How do you implement animations in SwiftUI?
SwiftUI supports two styles of animation: implicit and explicit.
Implicit Animation
─────────────────
Attach .animation() to a view.
Any state change affecting that view is animated.
Explicit Animation
──────────────────
Wrap state change in withAnimation { }.
Only that specific change is animated.
// Implicit Animation
struct ImplicitAnimationView: View {
@State private var scale = 1.0
var body: some View {
Circle()
.frame(width: 100, height: 100)
.scaleEffect(scale)
.animation(.spring(response: 0.5, dampingFraction: 0.6), value: scale)
.onTapGesture { scale = scale == 1.0 ? 1.5 : 1.0 }
}
}
// Explicit Animation
struct ExplicitAnimationView: View {
@State private var isExpanded = false
var body: some View {
VStack {
Rectangle()
.frame(width: isExpanded ? 300 : 100,
height: isExpanded ? 300 : 100)
Button("Toggle") {
withAnimation(.easeInOut(duration: 0.4)) {
isExpanded.toggle() // only this change animates
}
}
}
}
}
Animation Types
┌──────────────────────────────────────────────┐
│ SwiftUI Animation Curves │
├──────────────────┬───────────────────────────┤
│ .linear │ Constant speed │
│ .easeIn │ Starts slow, ends fast │
│ .easeOut │ Starts fast, ends slow │
│ .easeInOut │ Slow → fast → slow │
│ .spring(...) │ Bouncy, natural feel │
│ .interpolating │ Custom timing curve │
│ SpringAnim │ │
└──────────────────┴───────────────────────────┘
Transitions (for appearing/disappearing views)
if isVisible {
Text("Hello!")
.transition(.asymmetric(
insertion: .slide,
removal: .opacity
))
}
13. NavigationView & NavigationStack
Q: How does navigation work in SwiftUI? What is NavigationStack?
NavigationStack (iOS 16+) ← Preferred modern approach
┌──────────────────────────────────────────┐
│ NavigationStack(path: $navPath) { │
│ ContentView() │
│ .navigationDestination(for: ...) {} │
│ } │
└──────────────────────────────────────────┘
│
▼
Push → [Root] → [Detail] → [SubDetail]
Pop ← [Root] ← [Detail] ← [SubDetail]
// Modern Navigation (iOS 16+)
struct AppNavigationView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
List(items) { item in
NavigationLink(value: item) {
Text(item.name)
}
}
.navigationTitle("Items")
.navigationDestination(for: Item.self) { item in
ItemDetailView(item: item)
}
}
}
}
// Programmatic Navigation
Button("Go to Detail") {
path.append(selectedItem) // push
}
Button("Go Back") {
path.removeLast() // pop
}
Button("Go to Root") {
path.removeLast(path.count) // pop all
}
14. List & ForEach
Q: What is the difference between
ListandForEachin SwiftUI?
┌────────────────────────────────────────────────┐
│ List vs ForEach │
├───────────────────────┬────────────────────────┤
│ List │ ForEach │
├───────────────────────┼────────────────────────┤
│ Full scroll container │ Just iterates views │
│ Provides separators │ No built-in styling │
│ Built-in swipe-delete │ Used inside containers │
│ Selection support │ More flexible │
│ Auto cell styling │ Pure view builder │
└───────────────────────┴────────────────────────┘
struct FruitListView: View {
@State private var fruits = ["Apple", "Banana", "Cherry"]
var body: some View {
List {
ForEach(fruits, id: \.self) { fruit in
Text(fruit)
}
.onDelete { indexSet in
fruits.remove(atOffsets: indexSet)
}
.onMove { from, to in
fruits.move(fromOffsets: from, toOffset: to)
}
}
.toolbar {
EditButton()
}
}
}
Identifiable Protocol
// ✅ Always use Identifiable for proper diffing
struct Product: Identifiable {
let id = UUID()
var name: String
var price: Double
}
List(products) { product in
ProductRow(product: product)
}
15. View Modifiers
Q: What are view modifiers and how do they work?
Modifiers return a new modified view — they don't mutate the original. Order matters!
Modifier Chain — Order Matters!
Text("Hello")
│
▼
.padding() → adds padding around text
│
▼
.background(.blue) → blue covers the padded area
│
▼
.cornerRadius(10) → rounds the blue background
vs.
Text("Hello")
.background(.blue) → blue only behind the text (no padding)
.padding() → padding outside the blue box
.cornerRadius(10) → rounds nothing visible here
Text("SwiftUI")
.font(.title)
.fontWeight(.bold)
.foregroundColor(.white)
.padding(.horizontal, 20)
.padding(.vertical, 10)
.background(Color.blue)
.clipShape(RoundedRectangle(cornerRadius: 12))
.shadow(color: .black.opacity(0.3), radius: 8, x: 0, y: 4)
16. Custom ViewModifier
Q: How do you create a reusable custom ViewModifier?
// 1. Define the modifier
struct CardStyle: ViewModifier {
var backgroundColor: Color = .white
func body(content: Content) -> some View {
content
.padding()
.background(backgroundColor)
.cornerRadius(12)
.shadow(color: .black.opacity(0.1), radius: 6, x: 0, y: 3)
}
}
// 2. Add a View extension for clean syntax
extension View {
func cardStyle(background: Color = .white) -> some View {
modifier(CardStyle(backgroundColor: background))
}
}
// 3. Use it anywhere
struct SomeView: View {
var body: some View {
VStack {
Text("Card Title").font(.headline)
Text("Card subtitle goes here")
}
.cardStyle(background: .white) // ✅ Clean reusable modifier
}
}
17. @ViewBuilder
Q: What is
@ViewBuilderand when do you use it?
@ViewBuilder is a result builder that lets functions return multiple views from a single closure — the same mechanism SwiftUI itself uses for body.
// Without @ViewBuilder — can only return ONE view
func simpleHeader() -> some View {
Text("Title") // must be a single view
}
// With @ViewBuilder — can return MULTIPLE views conditionally
@ViewBuilder
func dynamicContent(isLoggedIn: Bool) -> some View {
if isLoggedIn {
Text("Welcome Back!") // ← multiple views based on condition
Button("Logout") { }
} else {
Text("Please Log In")
Button("Login") { }
}
}
// Custom container using @ViewBuilder
struct Card<Content: View>: View {
let title: String
@ViewBuilder let content: () -> Content // accepts view builder closure
var body: some View {
VStack(alignment: .leading) {
Text(title).font(.headline)
Divider()
content() // renders whatever is passed
}
.padding()
.background(.white)
.cornerRadius(10)
}
}
// Usage
Card(title: "Profile") {
Text("Name: John")
Text("Age: 30")
Image(systemName: "person.circle")
}
18. GeometryReader
Q: What is
GeometryReaderand when should you use it?
GeometryReader gives you access to the size and coordinate space of the parent container, enabling responsive and proportional layouts.
GeometryReader Concept:
┌──────────────────────────────────────────┐
│ Parent Container │
│ │
│ GeometryReader { geometry in │
│ ┌──────────────────────────────┐ │
│ │ geometry.size.width = 375 │ │
│ │ geometry.size.height = 800 │ │
│ │ geometry.frame(in: .local) │ │
│ │ geometry.frame(in: .global) │ │
│ └──────────────────────────────┘ │
│ │
└──────────────────────────────────────────┘
struct ResponsiveView: View {
var body: some View {
GeometryReader { geometry in
VStack {
// 60% width, 30% height of the available space
Rectangle()
.fill(Color.blue)
.frame(
width: geometry.size.width * 0.6,
height: geometry.size.height * 0.3
)
Text("Width: \(Int(geometry.size.width))")
Text("Height: \(Int(geometry.size.height))")
}
}
}
}
⚠️ Caution:
GeometryReaderexpands to fill available space. Use it only when you truly need size information — preferframe(),overlay(), andbackground()for most layout tasks.
19. Combine & SwiftUI
Q: How does SwiftUI integrate with the Combine framework?
Combine + SwiftUI Integration
Data Source ──► Publisher ──► Subscriber ──► View Update
(Network, (Just, (sink, (body
Timer, Future, assign, re-renders)
User input) PassthroughSubject) onReceive)
import Combine
import SwiftUI
class SearchViewModel: ObservableObject {
@Published var query = ""
@Published var results: [String] = []
private var cancellables = Set<AnyCancellable>()
init() {
// Debounce search input — wait 300ms after last keystroke
$query
.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
.removeDuplicates()
.sink { [weak self] text in
self?.performSearch(query: text)
}
.store(in: &cancellables)
}
func performSearch(query: String) {
// Simulate async search
results = query.isEmpty ? [] : ["Result for '\(query)'"]
}
}
struct SearchView: View {
@StateObject private var viewModel = SearchViewModel()
var body: some View {
VStack {
TextField("Search...", text: $viewModel.query)
.textFieldStyle(.roundedBorder)
.padding()
List(viewModel.results, id: \.self) { result in
Text(result)
}
}
}
}
20. UIKit ↔ SwiftUI Interoperability
Q: How do you use UIKit components in SwiftUI and vice versa?
SwiftUI ←──────────────────────► UIKit
UIViewRepresentable UIHostingController
┌──────────────────────┐ ┌───────────────────────┐
│ Wraps a UIKit view │ │ Wraps a SwiftUI view │
│ for use in SwiftUI │ │ for use in UIKit │
│ │ │ │
│ makeUIView() │ │ let vc = │
│ updateUIView() │ │ UIHostingController │
│ Coordinator (delegate│ │ (rootView: MyView())│
│ pattern) │ └───────────────────────┘
└──────────────────────┘
// UIKit → SwiftUI: Wrap UIKit view with UIViewRepresentable
struct UIKitMapView: UIViewRepresentable {
@Binding var region: MKCoordinateRegion
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ mapView: MKMapView, context: Context) {
mapView.setRegion(region, animated: true)
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, MKMapViewDelegate {
var parent: UIKitMapView
init(_ parent: UIKitMapView) { self.parent = parent }
}
}
// SwiftUI → UIKit: Wrap SwiftUI view with UIHostingController
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let swiftUIView = MySwiftUIView()
let hostingController = UIHostingController(rootView: swiftUIView)
addChild(hostingController)
view.addSubview(hostingController.view)
hostingController.didMove(toParent: self)
}
}
21. Async/Await in SwiftUI
Q: How do you use async/await for data fetching in SwiftUI?
// ViewModel with async data loading
class ArticleViewModel: ObservableObject {
@Published var articles: [Article] = []
@Published var isLoading = false
@Published var errorMessage: String?
func loadArticles() async {
isLoading = true
defer { isLoading = false }
do {
let url = URL(string: "https://api.example.com/articles")!
let (data, _) = try await URLSession.shared.data(from: url)
articles = try JSONDecoder().decode([Article].self, from: data)
} catch {
errorMessage = error.localizedDescription
}
}
}
// View using .task modifier (preferred over .onAppear for async)
struct ArticleListView: View {
@StateObject private var viewModel = ArticleViewModel()
var body: some View {
Group {
if viewModel.isLoading {
ProgressView("Loading...")
} else if let error = viewModel.errorMessage {
Text("Error: \(error)").foregroundColor(.red)
} else {
List(viewModel.articles) { article in
Text(article.title)
}
}
}
.task {
// .task is auto-cancelled when view disappears ✅
await viewModel.loadArticles()
}
}
}
.task vs .onAppear
┌──────────────────────────────────────────────────────┐
│ .task vs .onAppear │
├─────────────────────────┬────────────────────────────┤
│ .task │ .onAppear │
├─────────────────────────┼────────────────────────────┤
│ Native async support │ Synchronous by default │
│ Auto-cancels on disappear│ Runs every appearance │
│ Preferred for async ops │ Good for simple triggers │
│ iOS 15+ │ All iOS versions │
└─────────────────────────┴────────────────────────────┘
22. MVVM Architecture
Q: How do you implement MVVM pattern in SwiftUI?
MVVM in SwiftUI
┌─────────────────────────────────────────────────────┐
│ │
│ MODEL VIEW MODEL VIEW │
│ ┌─────────┐ ┌──────────────┐ ┌────────┐ │
│ │ Data │◄──────►│@Published │◄──►│SwiftUI │ │
│ │ Struct │ │ properties │ │ View │ │
│ │ or │ │ │ │ │ │
│ │ Entity │ │ Business │ │@State │ │
│ └─────────┘ │ Logic │ │@StateObj│ │
│ │ │ │ │ │
│ │ ObservableObj│ │ │ │
│ └──────────────┘ └────────┘ │
│ │
│ Data / Persistence Logic Layer UI Layer │
└─────────────────────────────────────────────────────┘
// MODEL
struct User: Codable, Identifiable {
let id: UUID
var name: String
var email: String
}
// VIEW MODEL
class UserListViewModel: ObservableObject {
@Published var users: [User] = []
@Published var isLoading = false
private let service: UserService
init(service: UserService = UserService()) {
self.service = service
}
func fetchUsers() async {
await MainActor.run { isLoading = true }
users = await service.getUsers()
await MainActor.run { isLoading = false }
}
}
// VIEW
struct UserListView: View {
@StateObject private var viewModel = UserListViewModel()
var body: some View {
NavigationStack {
List(viewModel.users) { user in
Text(user.name)
}
.navigationTitle("Users")
.overlay { if viewModel.isLoading { ProgressView() } }
.task { await viewModel.fetchUsers() }
}
}
}
23. Performance Optimization
Q: How do you optimize SwiftUI view performance?
Performance Optimization Techniques
1. EQUATABLE VIEWS
┌─────────────────────────────────────────┐
│ struct MyView: View, Equatable { ... } │
│ .equatable() ← skip re-render if equal │
└─────────────────────────────────────────┘
2. LAZY CONTAINERS (for large lists)
┌─────────────────────────────────────────┐
│ LazyVStack, LazyHStack, LazyVGrid │
│ Only renders visible views │
└─────────────────────────────────────────┘
3. PROPER IDENTIFIERS
┌─────────────────────────────────────────┐
│ Use stable IDs for ForEach │
│ Avoid id: \.self on mutable objects │
└─────────────────────────────────────────┘
4. @MainActor for UI updates
┌─────────────────────────────────────────┐
│ Ensure UI updates happen on main thread │
└─────────────────────────────────────────┘
// ✅ Prefer let over @State when data doesn't change
struct StaticRow: View {
let title: String // not @State — no re-render overhead
var body: some View { Text(title) }
}
// ✅ Use .id() to force complete re-creation when needed
ScrollView {
LazyVStack {
ForEach(items) { item in
ItemView(item: item)
}
}
}
// ✅ Split large views into smaller components
// — SwiftUI only re-renders the affected subtree
struct OptimizedList: View {
@StateObject var vm = ListViewModel()
var body: some View {
List(vm.items) { item in
ItemRow(item: item) // separated component
}
}
}
24. Accessibility in SwiftUI
Q: How does SwiftUI support accessibility?
SwiftUI provides built-in accessibility support that automatically maps standard views to accessibility traits. You can also customize it with dedicated modifiers.
struct AccessibleView: View {
@State private var isFavorite = false
var body: some View {
VStack {
// Image with meaningful accessibility label
Image(systemName: isFavorite ? "heart.fill" : "heart")
.accessibilityLabel(isFavorite ? "Remove from favorites" : "Add to favorites")
Button(action: { isFavorite.toggle() }) {
Text("Toggle Favorite")
}
// Group elements as single accessible unit
.accessibilityElement(children: .combine)
.accessibilityHint("Double tap to toggle the favorite state")
.accessibilityAddTraits(.isButton)
// Custom accessibility value
ProgressView(value: 0.7)
.accessibilityValue("70 percent complete")
}
}
}
25. Common Interview Traps & Tips
Q: What are some common SwiftUI gotchas interviewers test?
⚠️ Common Traps
┌─────────────────────────────────────────────────────────────────┐
│ Interview Gotchas │
├─────────────────────┬───────────────────────────────────────────┤
│ Topic │ Common Mistake │
├─────────────────────┼───────────────────────────────────────────┤
│ @State │ Using it in a ViewModel (should be in │
│ │ the View only, not ObservableObject) │
├─────────────────────┼───────────────────────────────────────────┤
│ @StateObject vs │ Using @ObservedObject when the View │
│ @ObservedObject │ creates the object (causes data loss │
│ │ on re-render) │
├─────────────────────┼───────────────────────────────────────────┤
│ Modifier Order │ Forgetting that .padding() before │
│ │ .background() behaves differently │
├─────────────────────┼───────────────────────────────────────────┤
│ GeometryReader │ Overusing it — causes unexpected │
│ │ layout behavior; prefer other tools │
├─────────────────────┼───────────────────────────────────────────┤
│ List identity │ Using id: \.self on mutable objects │
│ │ causes incorrect diff updates │
├─────────────────────┼───────────────────────────────────────────┤
│ @EnvironmentObject │ Forgetting to inject it at the root │
│ │ causes a crash at runtime │
├─────────────────────┼───────────────────────────────────────────┤
│ .task vs .onAppear │ Using .onAppear with async code instead │
│ │ of .task (task auto-cancels properly) │
└─────────────────────┴───────────────────────────────────────────┘
🎯 Top Tips for SwiftUI Interviews
- Always explain WHY, not just WHAT — interviewers want reasoning behind choices
- Know the property wrapper lifecycle deeply (
@StateObjectvs@ObservedObjectis a very common question) - Be able to draw the data flow — one-directional, reactive, state → UI
- Mention backward compatibility — SwiftUI features have different iOS version requirements
- Talk about MVVM naturally — it's the de-facto pattern for SwiftUI
- Mention
.taskfor async — shows you know modern concurrency integration - Discuss testing — ViewInspector, Preview-driven development, ViewModel unit tests
📚 Quick Revision Summary
┌──────────────────────────────────────────────────────────────┐
│ SwiftUI Mental Model │
│ │
│ UI = f(State) │
│ │
│ ┌──────────┐ changes ┌──────────┐ renders │
│ │ State │ ────────────► │ SwiftUI │ ────────────► UI │
│ │ @State │ │ Engine │ │
│ │ @Published│ │ (diffing)│ │
│ │ @Binding │ └──────────┘ │
│ └──────────┘ │
│ │
│ Views are value types (structs) — cheap to re-create │
│ State lives outside the view — owned by SwiftUI │
│ Data flows down (one-way), events flow up (actions) │
└──────────────────────────────────────────────────────────────┘
🏷️ Tags
SwiftUI iOS Swift Apple Mobile Development Interview Prep UIKit Xcode MVVM Combine Async/Await
📝 Last updated: 2025 | Covers iOS 13 → iOS 17+ SwiftUI features