[ACCEPTED]-SwiftUI: Presenting multiple ShareLinks in a menu-menu
ShareLink
inside a menu doesn't currently work. A 11 Menu is technically a new View Controller 10 (UIContextMenuActionsOnlyViewController
) presented over the active window. The 9 Share Action Sheet needs a View controller 8 to present from. When a ShareLink
inside a Menu
is tapped, it 7 dismisses the menu VC, along with the share 6 action sheet. You can verify this by checking 5 the view hierarchy when a Menu is open.
One 4 workaround is to manually create Button/MenuItem/s 3 and show a share action sheet on button 2 tap from the underlying View; which avoids 1 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
}
}
I've managed to achieve the desired behavior 1 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))
}
}
More Related questions
We use cookies to improve the performance of the site. By staying on our site, you agree to the terms of use of cookies.