3. August 2019

Tutorial: Apps mit UIContextMenuInteraction um Kontextmenüs erweitern

Das Tutorial zeigt, wie ein Beispielprojekt rückwärtskompatibel in iOS 13 mit einer UIContextMenuInteraction um Kontextmenüs erweitert wird:

Beispiel-App Kontextmenü

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

Kontextmenü unter macOS

  1. Verwende für das Tutorial die Xcode 11 Beta-Version (dieses Tutorial wurde zuletzt getestet mit Beta 5).

  2. Lade Start-Beispielprojekt von dem Countries-Projekt. Dieses Projekt basiert auf dem UICollectionView-Tutorial und enthält ein UI, welche im Folgenden um Kontextmenüs erweitert werden soll.

  3. Mache Dich mit dem Beispielprojekt vertraut: GalleryCollectionViewController enthält ein Collection-View-Controller den die App initial anzeigt, CountryViewController ist die Folge-Sicht wenn ein Fotokachel getappt wird.

  4. Erstelle für die Implementierung eine neue Swift-Datei CountryControllers+ContextMenu.swift.

  5. 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 createContextMenu(country: Country) -> UIContextMenuConfiguration {
    
        let actionProvider: UIContextMenuActionProvider = { _ in
    
            let openInMaps = UIAction(title: "Auf Karte zeigen", image: UIImage(systemName: "mappin.and.ellipse")) { _ in
                country.openInMaps()
            }
    
            return UIMenu(title: country.landmark.name, children: [openInMaps])
        }
    
        return UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: actionProvider)
    }

    Hinweis: Vor Beta 5 musste UIAction/UIMenu mit dem Parameternamen __title: aufgerufen werden, siehe:
    iOS 13.0 UIMenu and UIAction for UIContextMenuConfiguration
    Apple Developer Forums: UIAction, UIMenu.

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

    Sf Symbols

  6. 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 createContextMenu(country: Country) -> UIContextMenuConfiguration {
        // ...
    }

    oder das Mindest-Deployment-Target der App auf iOS 13 setzt:

    Deployment Target auf iOS 13 konfigurieren
  7. Erstelle eine Extension für den CountryViewController, die diesen konform zu dem Protokoll UIContextMenuInteractionDelegate macht und implementiere die Methode contextMenuInteraction(_:, configurationForMenuAtLocation:). Stelle zudem eine Methode enableContextMenu bereit, die dem imageView des Controllers eine UIContextMenuInteraction setzt:

    @available(iOS 13.0, *)
    extension CountryViewController: UIContextMenuInteractionDelegate {
    
        func enableContextMenu() {
            imageView.isUserInteractionEnabled = true
            imageView.addInteraction(UIContextMenuInteraction(delegate: self))
        }
    
        func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
            guard let country = country else { return nil }
            return createContextMenu(country: country)
        }
    
    }
  8. So ist die Logik zur Bereitstellung des Kontext-Menüs komplett 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 werden über diese Delegate-Methode um ein Kontextmenü erweitert):

    @available(iOS 13.0, *)
    extension GalleryCollectionViewController {
    
        override func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
            let country = self.countries[indexPath.row]
            return createContextMenu(country: 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 schon 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 dazu kann über die UIActivityViewController-Klasse angezeigt werden:

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