上下文菜单的 SwiftUI 自定义视图

SwiftUI Custom View For Context Menu

我想在视图上按下长按手势时实现相同的自定义弹出视图,如图(来自 Tweeter App)所示,因此我可以同时显示自定义视图和上下文菜单。

您需要使用 UIKit 中的 UIContextMenu 制作自定义上下文菜单。

struct ContextMenuHelper<Content: View, Preview: View>: UIViewRepresentable {
    var content: Content
    var preview: Preview
    var menu: UIMenu
    var navigate: () -> Void
    init(content: Content, preview: Preview, menu: UIMenu, navigate: @escaping () -> Void) {
        self.content = content
        self.preview = preview
        self.menu = menu
        self.navigate = navigate
    }
    func makeUIView(context: Context) -> UIView {
        let view = UIView()
        view.backgroundColor = .clear
        let hostView = UIHostingController(rootView: content)
        hostView.view.translatesAutoresizingMaskIntoConstraints = false
        let constraints = [
            hostView.view.topAnchor.constraint(equalTo: view.topAnchor),
            hostView.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            hostView.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            hostView.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            hostView.view.widthAnchor.constraint(equalTo: view.widthAnchor),
            hostView.view.heightAnchor.constraint(equalTo: view.heightAnchor)
        ]
        view.addSubview(hostView.view)
        view.addConstraints(constraints)
        let interaction = UIContextMenuInteraction(delegate: context.coordinator)
        view.addInteraction(interaction)
        return view
    }
    func updateUIView(_ uiView: UIView, context: Context) {
    }
    func makeCoordinator() -> Coordinator {
        return Coordinator(self)
    }
    class Coordinator: NSObject, UIContextMenuInteractionDelegate {
        var parent: ContextMenuHelper
        init(_ parent: ContextMenuHelper) {
            self.parent = parent
        }
        func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
            return UIContextMenuConfiguration(identifier: nil) {
                let previewController = UIHostingController(rootView: self.parent.preview)
                return previewController
            } actionProvider: { items in
                return self.parent.menu
            }
        }
        func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
            parent.navigate()
        }
    }
}

extension View {
    func contextMenu<Preview: View>(navigate: @escaping () -> Void = {}, @ViewBuilder preview: @escaping () -> Preview, menu: @escaping () -> UIMenu) -> some View {
        return CustomContextMenu(navigate: navigate, content: {self}, preview: preview, menu: menu)
    }
}

struct CustomContextMenu<Content: View, Preview: View>: View {
    var content: Content
    var preview: Preview
    var menu: UIMenu
    var navigate: () -> Void
    init(navigate: @escaping () -> Void, @ViewBuilder content: @escaping () -> Content, @ViewBuilder preview: @escaping () -> Preview, menu: @escaping () -> UIMenu) {
        self.content = content()
        self.preview = preview()
        self.menu = menu()
        self.navigate = navigate
    }
    var body: some View {
        ZStack {
            content
                .overlay(ContextMenuHelper(content: content, preview: preview, menu: menu, navigate: navigate))
        }
    }
}

用法:

.contextMenu(navigate: {
    UIApplication.shared.open(url) //User tapped the preview
}) {
    LinkView(link: url.absoluteString) //Preview
        .environment(\.managedObjectContext, viewContext)
        .accentColor(Color(hex: "59AF97"))
        .environmentObject(variables)
}menu: {
    let openUrl = UIAction(title: "Open", image: UIImage(systemName: "sidebar.left")) { _ in
        withAnimation() {
            UIApplication.shared.open(url)
        }
    }
    let menu = UIMenu(title: url.absoluteString, image: nil, identifier: nil, options: .displayInline, children: [openUrl]) //Menu
    return menu
}

用于导航:

添加 isActive: $navigate 到您的 NavigationLink:

NavigationLink(destination: SomeView(), isActive: $navigate)

连同新的 属性:

@State var navigate = false

.contextMenu(navigate: {
    navigate.toggle() //User tapped the preview
}) {
    LinkView(link: url.absoluteString) //Preview
        .environment(\.managedObjectContext, viewContext)
        .accentColor(Color(hex: "59AF97"))
        .environmentObject(variables)
}menu: {
    let openUrl = UIAction(title: "Open", image: UIImage(systemName: "sidebar.left")) { _ in
        withAnimation() {
            UIApplication.shared.open(url)
        }
    }
    let menu = UIMenu(title: url.absoluteString, image: nil, identifier: nil, options: .displayInline, children: [openUrl]) //Menu
    return menu
}