Ein Kontextmenu als ViewModifier


Ein Kontextmenü in SwiftUI zu entwickeln, ist an sich keine große Herausforderung. Doch je mehr Funktionen das Menü unterstützt, desto länger und unübersichtlicher wird der Programmcode. Das folgende Beispiel demonstriert ein Kontextmenü mit zwei Einträgen: einer Funktion zum Bearbeiten und einer Funktion zum Löschen. Jeder Eintrag im Menü besteht aus einem Button, der aus einem Label (für den Text und ein Symbol) und einer zugehörigen Aktion besteht.

.contextMenu {
                Button(action: {
                    print("Edit selected")
                 }) {
                     Label("Edit", systemImage: "highlighter")
                 }
                 Button(action: {
                     print("Delete selected")
                 }) {
                     Label("Delete", systemImage: "xmark.circle")
                 }
}

Allerdings funktioniert der gezeigte Code nicht immer wie erwartet, insbesondere unter macOS. Dort weigert sich das System, das Symbol im Label korrekt anzuzeigen. Wenn ein Menüeintrag von einem Icon begleitet werden soll, muss das Symbol separat als Image-Objekt definiert werden. Bild und Text müssen anschließend in einem HStack zusammengefasst werden, was den Code noch weiter aufbläht.

Es liegt daher nahe, den gesamten Menüaufbau in einen separaten View auszulagern. Schließlich sind die Bestandteile eines Menüeintrags immer gleich: eine Aktion, die beim Anklicken des Menüeintrags ausgeführt wird, ein anzuzeigender Text und ein Symbol. Die Struktur DynamicMenuItem fasst diese Eigenschaften zusammen.

struct DynamicMenuItem {
    var title : String = ""
    var image : String = ""
    var action : () -> Void
    
    init(title : String,  image : String,  action: @escaping () -> Void) {
        self.title = title
        self.image = image
        self.action = action
    }
}

Im nächsten Schritt soll ein ViewModifier entwickelt werden. Dieser hat die Aufgabe, aus einer Liste von Menüeinträgen (DynamicMenuItem) automatisch ein Kontextmenü zu generieren. Die Umsetzung erfolgt dabei über eine einfache ForEach-Schleife, die alle Einträge durchläuft und entsprechende Views erstellt.

import SwiftUI

struct DynamicMenuItem {
    var title : String = ""
    var image : String = ""
    var action : () -> Void
    
    init(title : String,  image : String,  action: @escaping () -> Void) {
        self.title = title
        self.image = image
        self.action = action
    }
}

struct DynamicContextMenuModifier: ViewModifier {
    
    let menuItems : [DynamicMenuItem]
    
    func body(content: Content) -> some View {
        content
            .contextMenu {
                ForEach(menuItems, id: \.title) { menuItem in
                    Button(action: menuItem.action) {
                        HStack {
                            Image(systemName: menuItem.image)
                            Text(menuItem.title)
                        }
                    }
                }
            }
    }
}

extension View {
    func dynamicContextMenu(menuItems: [DynamicMenuItem]) -> some View {
        self.modifier(DynamicContextMenuModifier(menuItems: menuItems))
    }
}

Ist der ViewModifier fertiggestellt, kann das neue Kontextmenü sofort verwendet werden. Für jeden Menüeintrag wird ein DynamicMenuItem hinzugefügt, das mit den Parametern title, image und action initialisiert wird. Das Ergebnis ist nicht nur deutlich übersichtlicherer Programmcode, sondern auch ein Kontextmenü, das zuverlässig Symbole und Text miteinander kombiniert – unabhängig davon, ob es unter iOS oder macOS verwendet wird.

.dynamicContextMenu(menuItems:  [
    DynamicMenuItem(title: "Edit", image: "highlighter", action: { print("Edit selected") }),
    DynamicMenuItem(title: "Delete", image: "xmark.circle", action: { print("Delete selected") }),
])

Der in diesem Beispiel gezeigte Code ist Teil des Projektes FileNameFixer. Es steht auf GitHub kostenlos zur Verfügung.

Veröffentlicht am: 01.01.2025


© 2025 Holger Hinzberg Contact