skip to Main Content

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

3

Answers


  1. 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
            
        }
    }
    
    
    Login or Signup to reply.
  2. I’ve managed to achieve the desired behavior with custom popover bridged from UIKit.

    import SwiftUI
    
    struct ShareSheetView: View {
        @State private var isShowingPopover = false
        var body: some View {
            NavigationStack {
                VStack {
                    Image(systemName: "globe")
                        .imageScale(.large)
                        .foregroundColor(.accentColor)
                    Text("Hello, world!")
                    
                    Button("Show Popover") {
                        isShowingPopover = true
                    }
                    
                }
                .padding()
                .toolbar {
                    ToolbarItem(placement: .primaryAction) {
                        Button {
                            isShowingPopover.toggle()
                        } label: {
                            Image(systemName: "square.and.arrow.up")
                        }
                        .uiKitPopover(isPresented: $isShowingPopover) {
                            VStack {
                                ShareLink(
                                    item: URL(string: "https://www.apple.com")!,
                                    preview: SharePreview(
                                        "Test 123",
                                        image: Image(systemName: "plus")
                                    )
                                )
                                Divider()
                                ShareLink(
                                    item: URL(string: "https://www.microsoft.com")!,
                                    preview: SharePreview(
                                        "Tests 321",
                                        image: Image(systemName: "minus")
                                    )
                                )
                            }
                            .fixedSize()
                            .padding()
                        }
                    }
                }
            }
        }
    }
    
    struct ShareSheetView_Previews: PreviewProvider {
        static var previews: some View {
            ShareSheetView()
        }
    }
    
    struct PopoverViewModifier<PopoverContent>: ViewModifier where PopoverContent: View {
        @Binding var isPresented: Bool
        let onDismiss: (() -> Void)?
        let content: () -> PopoverContent
        let permittedArrowDirections: UIPopoverArrowDirection = []
        
        func body(content: Content) -> some View {
            content
                .background(
                    Popover(
                        isPresented: $isPresented,
                        onDismiss: onDismiss,
                        content: self.content
                    )
                )
        }
    }
    
    struct Popover<Content: View> : UIViewControllerRepresentable {
        @Binding var isPresented: Bool
        let onDismiss: (() -> Void)?
        @ViewBuilder let content: () -> Content
        let permittedArrowDirections: UIPopoverArrowDirection = []
        
        func makeCoordinator() -> Coordinator {
            return Coordinator(parent: self, content: self.content())
        }
        
        func makeUIViewController(context: Context) -> UIViewController {
            return UIViewController()
        }
        
        func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
            context.coordinator.host.rootView = self.content()
            if self.isPresented, uiViewController.presentedViewController == nil {
                let host = context.coordinator.host
                host.preferredContentSize = host.sizeThatFits(in: CGSize(width: Int.max, height: Int.max))
                host.modalPresentationStyle = UIModalPresentationStyle.popover
                host.popoverPresentationController?.delegate = context.coordinator
                host.popoverPresentationController?.sourceView = uiViewController.view
                host.popoverPresentationController?.sourceRect = uiViewController.view.bounds
                host.popoverPresentationController?.permittedArrowDirections = permittedArrowDirections
                uiViewController.present(host, animated: true, completion: nil)
            }
        }
        
        class Coordinator: NSObject, UIPopoverPresentationControllerDelegate {
            let host: UIHostingController<Content>
            private let parent: Popover
            
            init(parent: Popover, content: Content) {
                self.parent = parent
                self.host = UIHostingController(rootView: content)
            }
            
            func presentationControllerWillDismiss(_ presentationController: UIPresentationController) {
                self.parent.isPresented = false
                if let onDismiss = self.parent.onDismiss {
                    onDismiss()
                }
            }
            
            func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
                return .none
            }
        }
    }
    
    extension View {
        func uiKitPopover<Content>(isPresented: Binding<Bool>, onDismiss: (() -> Void)? = nil, content: @escaping () -> Content) -> some View where Content: View {
            self.modifier(PopoverViewModifier(isPresented: isPresented, onDismiss: onDismiss, content: content))
        }
    }
     
    
    Login or Signup to reply.
  3. This was fixed in iOS 16.1! Thanks for the report.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search