Search code examples
iosswiftswiftuimenu

SwiftUI: Presenting multiple ShareLinks in a menu


I'd like to present share menu with multiple options. I've created a Menu and added all ShareLink views. I can tap the share button, but if I select a ShareLink, nothing happens. No error message.

This is the only way I can think of to create such a "share menu":

ToolbarItemGroup(placement: SwiftUI.ToolbarItemPlacement.navigationBarTrailing) {

    Menu {
          ShareLink(
               item: URL(string: "https://www.apple.com")!,
               preview: SharePreview(
                   "Test 123",
                    image: Image(systemName: "plus")
                    )
                )
               ShareLink(
                    item: URL(string: "https://www.microsoft.com")!,
                    preview: SharePreview(
                        "Tests 321",
                         image: Image(systemName: "minus")
                    )
                )

        } label: {
               Image(systemName: "square.and.arrow.up")
        }
}

screenshot


Solution

  • ShareLink inside a menu doesn't currently work. A Menu is technically a new View Controller (UIContextMenuActionsOnlyViewController) presented over the active window. The Share Action Sheet needs a View controller to present from. When a ShareLink inside a Menu is tapped, it dismisses the menu VC, along with the share action sheet. You can verify this by checking the view hierarchy when a Menu is open.

    One workaround is to manually create Button/MenuItem/s and show a share action sheet on button tap from the underlying View; which avoids using ShareLink directly.

    Workaround:

    ...
    ToolbarItemGroup(placement: SwiftUI.ToolbarItemPlacement.navigationBarTrailing) {
      Menu {
        Button(action: {
          showShareSheet(url: URL("https://www.apple.com")!)
        }) {
          Label("Share1", systemImage: "square.and.arrow.up")
        }
        Button(action: {
          showShareSheet(url: URL(string: "https://www.microsoft.com")!)
        }) {
          Label("Share2", systemImage: "square.and.arrow.up")
        }
      } label: {
        Image(systemName: "square.and.arrow.up")
      }
    }
    ...
    
    // UIActivityViewController can be customised. 
    // For examples, see https://www.hackingwithswift.com/articles/118/uiactivityviewcontroller-by-example
    func showShareSheet(url: URL) {
      let activityVC = UIActivityViewController(activityItems: [url], applicationActivities: nil)
      UIApplication.shared.currentUIWindow()?.rootViewController?.present(activityVC, animated: true, completion: nil)
    }
    
    // utility extension to easily get the window 
    public extension UIApplication {
        func currentUIWindow() -> UIWindow? {
            let connectedScenes = UIApplication.shared.connectedScenes
                .filter { $0.activationState == .foregroundActive }
                .compactMap { $0 as? UIWindowScene }
            
            let window = connectedScenes.first?
                .windows
                .first { $0.isKeyWindow }
    
            return window
            
        }
    }