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.

Drie macOS-vensters naast elkaar: de klok, de prijslijst en de grafiek

Meerdere vensters naast elkaar op het scherm

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 identiteit
  • Settings — het instellingenvenster (opent met Cmd+,)
@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()
            }
    }
}
NoteVerdieping: Scene-levenscyclus en @main

@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.