14 H14: Meerdere vensters
Een macOS-app kan meerdere vensters open hebben tegelijk. De klok in één venster, de prijslijst in een ander, de grafiek in een derde. In dit hoofdstuk leer je hoe je meerdere vensters definieert in de app-struct, hoe je ze opent vanuit de code, en hoe je menu-items toevoegt met sneltoetsen.
14.1 Wat gaan we bouwen?
De complete EnergyClockApp-struct met zes vensters (klok, prijslijst, grafiek, boxplot, widget-preview, instellingen) en een menu met sneltoetsen voor elk venster.

14.2 Playground-voorbeeld
Meerdere vensters werken alleen in een echte macOS-app, niet in Playgrounds. Maar je kunt de app-structuur oefenen:
import SwiftUI
@main
struct MijnApp: App {
var body: some Scene {
// Het hoofdvenster
WindowGroup {
Text("Hoofdvenster")
}
}
}Dit is de minimale app-struct. Alles staat in body, en alles is een Scene.
14.3 Concept uitgelegd
14.3.1 App en Scene
Een SwiftUI-app is opgebouwd uit Scenes. Een Scene is een stuk van je app dat zijn eigen venster(s) beheert.
WindowGroup— een venster dat de gebruiker kan dupliceren (standaard voor documenten)Window— een enkel venster met een vaste identiteitSettings— het instellingenvenster (opent metCmd+,)
@main
struct MijnApp: App {
var body: some Scene {
WindowGroup { HoofdView() } // hoofdvenster
Window("Info", id: "info") { InfoView() } // apart venster
Settings { InstellingenView() } // instellingen
}
}14.3.2 Vensters openen
Vensters met een id kun je openen vanuit de code met @Environment(\.openWindow):
@Environment(\.openWindow) private var openWindow
Button("Open Info") {
openWindow(id: "info")
}14.4 Code schrijven
De volledige app-struct
Maak EnergyClockApp.swift. Dit is de centrale plek waar alle vensters gedefinieerd worden:
import SwiftUI
// Bron: EnergyClock/EnergyClockApp.swift
@main
struct EnergyClockApp: App {
@State private var dataManager = EnergyDataManager()
@Environment(\.openWindow) private var openWindow
var body: some Scene {
// Hoofdvenster: de klok
WindowGroup(id: "main") {
ContentView()
.environment(dataManager)
}
.commands {
CommandGroup(replacing: .appInfo) {
Button("About EnergyClock") {
openWindow(id: "about")
}
}
// Sneltoetsen voor vensters
CommandGroup(after: .windowArrangement) {
Button("EnergyClock") { openWindow(id: "main") }
.keyboardShortcut("1", modifiers: .command)
Button("Prijslijst") { openWindow(id: "pricesList") }
.keyboardShortcut("2", modifiers: .command)
Button("Grafiek") { openWindow(id: "chart") }
.keyboardShortcut("3", modifiers: .command)
Button("Boxplot") { openWindow(id: "boxplot") }
.keyboardShortcut("4", modifiers: .command)
}
}
// About-venster
Window("About EnergyClock", id: "about") {
AboutView()
}
.defaultSize(width: 380, height: 440)
.windowResizability(.contentSize)
// Prijslijst-venster
Window("Price List", id: "pricesList") {
PricesListView()
.environment(dataManager)
}
.keyboardShortcut("2", modifiers: .command)
.defaultSize(width: 640, height: 800)
// Grafiek-venster
Window("Price Chart", id: "chart") {
PriceChartView()
.environment(dataManager)
}
.keyboardShortcut("3", modifiers: .command)
.defaultSize(width: 1200, height: 600)
// Boxplot-venster
Window("Boxplot", id: "boxplot") {
BoxplotView()
.environment(dataManager)
}
.keyboardShortcut("4", modifiers: .command)
.defaultSize(width: 1000, height: 540)
// Instellingen (macOS beheert Cmd+, automatisch)
Settings {
SettingsView(manager: dataManager)
}
}
}Data doorgeven aan alle vensters
Elke view die EnergyDataManager nodig heeft, krijgt hem via .environment(dataManager). Zo hoef je de manager niet door te geven via initializers — alle views kunnen hem ophalen met @Environment(EnergyDataManager.self).
De standaard opstart
Voeg aan de ContentView een .task toe zodat data geladen wordt bij de eerste start:
struct ContentView: View {
@Environment(EnergyDataManager.self) var manager
var body: some View {
KlokView(colorMapper: manager.colorMapper)
.task {
await manager.laadBijOpstarten()
}
}
}@main markeert het startpunt van je app. Swift genereert automatisch de main() entry point op basis van deze annotatie. Het protocol App vereist dat je body implementeert als een some Scene.
WindowGroup verschilt van Window op een belangrijk punt: WindowGroup laat meerdere instanties toe. Als de gebruiker Cmd+N drukt, opent een nieuw exemplaar van dezelfde view. Window heeft altijd precies één instantie — al is die misschien geminimaliseerd of achter andere vensters.
Settings is een speciale Scene die macOS integreert met het App-menu: Cmd+, opent hem automatisch, en hij verschijnt altijd als één venster, ook als de gebruiker hem sluit en opnieuw opent.
De openWindow-omgevingswaarde werkt via SwiftUI’s environment actions: Apple biedt een OpenWindowAction aan die macOS vraagt het venster met de opgegeven id op de voorgrond te brengen (of te openen als het nog niet bestaat).
14.5 Apple documentatie
Meer over meerdere vensters:
developer.apple.com/documentation/swiftui/windowgroup
developer.apple.com/documentation/swiftui/window
Meer over Commands en menu-items:
developer.apple.com/documentation/swiftui/commands
Zoek in de Window-documentatie naar windowResizability — dat bepaalt of de gebruiker het venster kan vergroten of dat het zich aanpast aan de inhoud.
14.6 Samenvatting
| Begrip | Betekenis |
|---|---|
App |
Het protocol voor het startpunt van je app |
@main |
Markeert het startpunt van de app |
Scene |
Een zelfstandig onderdeel van de app met eigen venster(s) |
WindowGroup |
Venster dat meerdere instanties toestaat |
Window |
Eén venster met een vaste identiteit |
Settings |
Het instellingenvenster (geopend met Cmd+,) |
openWindow |
Omgevingswaarde om een venster te openen |
commands |
Voegt items toe aan het macOS-menu |
CommandGroup |
Groepeert menu-items op een specifieke plek |
.keyboardShortcut |
Voegt een sneltoets toe |
14.7 Opdracht
Voeg een zevende venster toe: “Logboek”. Dit venster toont de laatste 20 log-regels van de app (zie H15 voor logging). Voeg hem toe aan de app-struct met id "log", een standaardgrootte van 600×400, en een sneltoets Cmd+5.