Tutorial: Apps mit UIContextMenuInteraction um Kontextmenüs erweitern

von @ralfebert · aktualisiert am 27. Oktober 2019

In iOS 13 gibt es ein neues Standard-UI für Kontextmenüs, welches das alte Peek and Pop-Feature ersetzt. Diese werden auf 3D-Touch-fähigen Geräten durch einen kräftigen Force-Touch ausgelöst, auf allen anderen Geräten durch einen etwas länger gehaltenen Long Press:

Beispiel-App Kontextmenü

Wird eine App mit Catalyst für macOS kompiliert, wird das Kontextmenü entsprechend ähnlich zu einer AppKit-Anwendung dargestellt:

Kontextmenü unter macOS

Das Tutorial zeigt, wie ein UI mit einer UIContextMenuInteraction um ein solches Kontextmenü erweitert wird.

  1. Verwende die aktuellste Xcode 11-Version (dieses Tutorial wurde zuletzt getestet am 27. Oktober 2019 mit Xcode 11.1).

  2. Lade das Start-Beispielprojekt Countries.

    Dieses Projekt basiert auf dem UICollectionView-Tutorial und enthält ein UI, welches im Folgenden um Kontextmenüs erweitert wird.

    Mache Dich mit dem Beispielprojekt vertraut: GalleryCollectionViewController ist der Collection-View-Controller den die App initial anzeigt, CountryViewController ist die Folge-Sicht wenn eine Fotokachel getappt wird.

  3. Erstelle für die Implementierung des Kontextmenüs eine neue Swift-Datei CountryContextMenus.swift.

  4. Implementiere hier eine Funktion createContextMenu, die eine UIContextMenuConfiguration erstellt. Erzeuge ein UIMenu mit einer UIAction, die die Sehenswürdigkeit zu dem Land (repräsentiert durch ein Country) in der Apple Maps-App öffnet (dazu enthält das Projekt bereits eine Extension Country+Maps.swift, die das Öffnen in Maps erledigt):

    private func contextMenu(for country: Country) -> UIContextMenuConfiguration {
    
        let actionProvider: UIContextMenuActionProvider = { (suggestedActions) in
    
            let openInMaps = UIAction(title: "Auf Karte zeigen", image: UIImage(systemName: "mappin.and.ellipse")) { (action) in
                country.openInMaps()
            }
    
            return UIMenu(title: country.name, children: [openInMaps])
        }
    
        return UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: actionProvider)
    }
    

    Tipp: Die Symbole stammen aus der neuen SF Symbols Schrift, die mit der SF Symbols-App nachgeschlagen werden können:

    SF Symbols App
  5. Dies sollte zu einem Compilerfehler 'UIContextMenuConfiguration' is only available in iOS 13.0 or newer führen. Du kannst dieses Problem beheben, indem Du die Funktion nur für iOS 13 deklarierst:

    @available(iOS 13.0, *)
    private func contextMenu(for country: Country) -> UIContextMenuConfiguration {
        // ...
    }
    

    Alternativ kannst Du auch das Mindest-Deployment-Target der App auf iOS 13 setzen:

    Deployment Target auf iOS 13 konfigurieren
  6. Erstelle eine Extension für den CountryViewController, die diesen konform zu dem Protokoll UIContextMenuInteractionDelegate macht und implementiere die Methode contextMenuInteraction(_:, configurationForMenuAtLocation:):

    @available(iOS 13.0, *)
    extension CountryViewController: UIContextMenuInteractionDelegate {
    
        func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
            guard let country = country else { return nil }
            return contextMenu(for: country)
        }
    
    }
    
  7. Erstelle eine weitere Extension für den Controller und ergänze eine Methode enableContextMenu, die dem imageView des Controllers eine UIContextMenuInteraction setzt:

    @available(iOS 13.0, *)
    extension CountryViewController {
    
        func enableContextMenu() {
            imageView.isUserInteractionEnabled = true
            imageView.addInteraction(UIContextMenuInteraction(delegate: self))
        }
    
    }
    
  8. So ist die Logik für das Kontext-Menü separat abgelegt, lediglich der Aufruf von enableContextMenu muss in der CountryViewController-Klasse selbst erfolgen:

    class CountryViewController: UIViewController {
    
        // ...
    
        override func viewDidLoad() {
            super.viewDidLoad()
            if #available(iOS 13.0, *) {
        self.enableContextMenu()
    }
        }
    
        // ...
    
    }
    
  9. Erstelle eine weitere Extension, um die Klasse GalleryCollectionViewController um die collectionView(_:, contextMenuConfigurationForItemAt:)-Methode zu erweitern (UITableViewController und UICollectionViewController unterstützen das Kontextmenü über eine solche Delegate-Methode):

    @available(iOS 13.0, *)
    extension GalleryCollectionViewController {
    
        override func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
            let country = self.countries[indexPath.row]
            return contextMenu(for: country)
        }
    
    }
    
  10. Starte die App und teste, dass das Kontextmenü in beiden View-Controllern angezeigt wird (Long Press auf eines der Fotos):

    Beispiel-App Kontextmenü
  11. Sofern Du macOS Catalina verwendest: Konfiguriere die App im Target kompatibel zu Mac:

    Target Device Mac

    Wähle als Schema My Mac aus und starte die App. Hier wird das Kontextmenü als natives macOS-Kontextmenü angezeigt:

    Kontextmenü unter macOS
  12. Aufgabe: Ergänze das Kontext-Menü um eine weitere UIAction, um das Foto zu teilen. Das Share-Sheet kann mit dem UIActivityViewController angezeigt werden:

    let shareSheet = UIActivityViewController(activityItems: [country.image], applicationActivities: nil)
    self.present(shareSheet, animated: true, completion: nil)
    

Weitere Informationen