SwiftUI is een declaratief framework van Apple voor het bouwen van gebruikersinterfaces op alle platformen van het ecosysteem. In plaats van het imperatief beschrijven van stappen, declareert de ontwikkelaar hoe de interface eruit moet zien en SwiftUI beheert het renderen en updaten ervan. Volgens Apple Developer Documentation (2025) ondersteunt SwiftUI iOS 15+, iPadOS 15+, macOS 12+, watchOS 8+ en tvOS 15+ en gebruikt het View Protocol als basisbouwsteen voor alle interfacecomponenten.
Belangrijkste punten
body — de basis van elke UI-component in SwiftUI die een beschrijving van het scherm retourneert door compositie van views.SwiftUI is een declaratief framework, geïntroduceerd door Apple in 2019 om UIKit te vervangen in nieuwe projecten. In plaats van handmatig UIView-instanties te maken en toe te voegen aan de hiërarchie, beschrijft de ontwikkelaar de interface via structuren die het View-protocol implementeren. SwiftUI berekent automatisch het verschil tussen de huidige en nieuwe staat en hertekent alleen de gewijzigde delen met behulp van zijn eigen rendering-engine.
Het framework is geschreven in Swift met value semantics (structuren, geen klassen), wat UI-componenten licht en thread-veilig maakt. In tegenstelling tot UIKit, waar UIViewController 200+ bytes kan wegen vanwege de Objective-C runtime, is een SwiftUI View slechts een structuur van enkele bytes groot. Dit is vooral belangrijk voor watchOS met zijn beperkte geheugen.
Dezelfde View-beschrijving werkt op iPhone, iPad, Mac, Apple Watch, Apple TV en Apple Vision Pro. SwiftUI past de interface aan het platform aan: op iOS — aanraakgebaren, op macOS — toetsenbordcombinaties, op watchOS — scrollen met Digital Crown. Dit verkort de ontwikkelingstijd voor bedrijven die apps voor meerdere Apple-platformen uitbrengen, maar vereist extra configuratie voor specifieke elementen van elk platform.
In SwiftUI is elk scherm een structuur die het View-protocol implementeert met één enkele vereiste: computed property body van het type some View. Het sleutelwoord some (opaque type) verbergt het concrete type van de view, waardoor SwiftUI de rendering kan optimaliseren. Binnen body combineert de ontwikkelaar kant-en-klare componenten — Text, Image, Button, List — met behulp van ViewBuilder, die meerdere views verzamelt in één.
struct GreetingView: View {
let name: String
var var body: some View {
VStack {
Text("Hallo, \(name)!")
.font(.title)
.foregroundColor(.blue)
Image(systemName: "hand.wave")
.imageScale(.large)
}
.padding()
}
}
In het voorbeeld bevat VStack (verticale stack) Text en Image. De waarde name wordt doorgegeven via de initializer van de structuur — zo werkt DI (Dependency Injection) in SwiftUI zonder externe DI-containers. Elke modifier retourneert een nieuwe view met de toegepaste wijziging, zonder het origineel te muteren. Dit is mogelijk dankzij de onveranderlijkheid (immutability) van value-types.
ViewBuilder is een result builder, geannoteerd met @resultBuilder, die tot 10 views verzamelt in één. Binnen body kunnen if/else, switch en ForEach worden gebruikt zonder extra omhulsels. ForEach werkt met Identifiable-elementen — elke view krijgt een unieke id voor correcte animatie bij invoegen/verwijderen.
In SwiftUI bepaalt de staat welke inhoud op het scherm wordt weergegeven. Wanneer de staat verandert, hercreëert SwiftUI de body van de afhankelijke view en vergelijkt het resultaat met de vorige, waarbij het diff-algoritme wordt toegepast. Voor het opslaan van de staat worden property wrappers gebruikt — elk lost zijn eigen taak op: lokale staat, verbinding met een kind-view of extern gegevensmodel.
struct CounterView: View {
@State private var count = 0
var var body: some View {
VStack {
Text("Teller: \(count)")
Button("Verhogen") {
count += 1
}
}
}
}
class UserViewModel: ObservableObject {
@Published var name = ""
@Published var age = 0
}
@State slaat een lokale eenvoudige waarde (Int, String, Bool) op in de structuur View. SwiftUI verplaatst het geheugen van de structuur naar een aparte opslag — daarom kan de eigenschap met @State worden gewijzigd (gemuteerd), zelfs als View een value-type is. @ObservableObject — voor klassen met @Published-eigenschappen, waarvan wijzigingen SwiftUI automatisch op de hoogte stellen van de noodzaak tot hertekenen.
@Binding creëert een bidirectionele verbinding met de gegevensbron in de bovenliggende view. De ouder geeft $variable (projected value) door, het kind leest en schrijft de waarde via binding. Dit maakt het mogelijk om tekstinvoer of een schakelaar in een aparte component te plaatsen, terwijl de staat in de ouder blijft. Zonder @Binding zou elke wijziging een callback-sluiting vereisen om de nieuwe waarde omhoog door te geven.
Vóór iOS 16 was navigatie in SwiftUI gebaseerd op NavigationView — een verouderde API met complex gedrag op iPad (split view, double column). Vanaf iOS 16 beveelt Apple NavigationStack aan — een vereenvoudigd alternatief met type-veilige routes. De ontwikkelaar definieert een enum van mogelijke routes en NavigationStack beheert automatisch de stapel schermen met ondersteuning voor diepe links en terugkeer naar de root.
enum Route: Hashable {
case detail(id: Int)
case settings
}
struct ContentView: View {
var var body: some View {
NavigationStack {
List {
NavigationLink("Detailscherm",
value: Route.detail(id: 42))
NavigationLink("Instellingen",
value: Route.settings)
}
.navigationDestination(for: Route.self) { route in
switch route {
case .detail(let id): DetailView(id: id)
case .settings: SettingsView()
}
}
}
}
}
Routes van het type Route: Hashable maken het mogelijk om elk gegevenstype te gebruiken voor het doorgeven van parameters. navigationDestination(for:destination:) verbindt het routetype met de doelview. Voordeel ten opzichte van UIKit-navigatie — hertekenen is niet nodig bij het toevoegen van een nieuwe route: het volstaat om een case toe te voegen in de enum en een handler in de switch. Diepe links worden verwerkt via processDeepLink op NavigationStack.
Voor programmatische overgang (na inloggen, timer of serverantwoord) wordt @State gebruikt met NavigationLink-initializer: NavigationLink(isActive: $isActive). Bij het instellen van isActive = true vindt de overgang plaats zonder aanraking van de gebruiker. Alternatief — binding van de array $path in NavigationStack: $path.append(Route.detail(id: 1)).
Modifier is een methode die een gewijzigde kopie van de view retourneert. In tegenstelling tot UIKit, waar configuratie van eigenschappen gebeurt door mutatie van de bestaande view, creëert SwiftUI een nieuwe waarde met de toegepaste wijziging. De keten van modifiers (chaining) bouwt de uiteindelijke interface uit sequentiële transformaties: lettertype → spatie → kleur → schaduw → gebaar.
Apple biedt meer dan 200 ingebouwde modifiers. De meest voorkomende zijn: .font(), .foregroundColor(), .padding(), .background(), .cornerRadius(), .shadow(), .opacity(), .offset(). De volgorde van modifiers is belangrijk: .padding() vóór .background() kleurt het gebied met spatie, erna — alleen het binnenste gebied. Aangepaste modifiers worden gemaakt via het ViewModifier-protocol.
Modifiers kunnen conditioneel worden toegepast via de ternaire operator: .foregroundColor(isError ? .red : .primary). Voor animatie wordt .animation(.easeInOut, value: state) gebruikt — de animatiemodifier wordt gekoppeld aan een specifieke eigenschap van de staat. Bij wijziging van deze eigenschap animeert SwiftUI de overgang tussen de oude en nieuwe waarde. Animatie werkt met opacity, offset, scale, rotation, grootte en kleur — voor elke eigenschap is een overeenkomstige AnimatableParameter gedefinieerd.
Voor aangepaste animaties zijn .transition (verschijnen/verdwijnen) en .matchedGeometryEffect (vloeiende overgang van een element tussen twee containers) beschikbaar. De laatste wordt gebruikt voor hero-animatie in lijsten: een pictogram in een lijstcel verandert vloeiend in een grote afbeelding op het detailscherm.
De keuze tussen SwiftUI en UIKit is een van de eerste dilemma's van een iOS-ontwikkelaar. Beide frameworks worden ondersteund door Apple, maar lossen de taak van het bouwen van een interface op fundamenteel verschillende manieren op: SwiftUI declaratief, UIKit imperatief. Het verschil komt tot uiting in staatbeheer, navigatie, prestaties en compatibiliteit.
| Aspect | SwiftUI | UIKit |
|---|---|---|
| Benadering | Declaratief: wat tonen | Imperatief: hoe bouwen |
| Staat | Property Wrappers, automatisch hertekenen | Handmatig: reloadData, setNeedsLayout |
| UI-code | Compact, modifier-ketens | Omvangrijk, NSCoder/Storyboard/constraints |
| Prestaties | Hoog op iOS 17+, diff-algoritme | Piek op iOS 12–16, directe controle |
| Minimale versie | iOS 15+ (volledige ondersteuning) | iOS 2+ (alle versies) |
Voor nieuwe projecten met minimale versie iOS 17 beveelt Apple SwiftUI aan als primair framework. UIKit blijft noodzakelijk voor interfaces die fijne controle over rendering vereisen (aangepaste UICollectionViewLayout, complexe CAAnimation-scènes) of ondersteuning voor iOS 12–14. Veel projecten gebruiken een hybride benadering: SwiftUI wordt via UIHostingController ingebed in een UIKit-app, en UIViewRepresentable maakt het mogelijk UIKit-componenten te gebruiken binnen de SwiftUI-hiërarchie.
Veelgestelde vragen
Ja, via UIHostingController (SwiftUI in UIKit) en UIViewRepresentable (UIKit in SwiftUI). Dit is een hybride aanpak, populair bij migratie.
iOS 17 — volledige functionaliteit: NavigationStack, Observation framework, Swift Charts. iOS 15 — minimale drempel voor productie.
De meest voorkomende oorzaak is het wijzigen van een @Published-eigenschap op een achtergrondthread. ObservableObject moet wijzigingen naar de main actor sturen: @MainActor class ViewModel.
Gebruik .debounce via Combine: Button.publisher(for: .tap) .debounce(for: .seconds(0.3), scheduler: RunLoop.main).
Ja, via Gesture-modifiers: DragGesture, LongPressGesture, MagnificationGesture, RotationGesture. Combineer ze via .simultaneousGesture() en .sequenced().
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook