SwiftUI:.contextMenu 关闭时的通知(iOS)

vie*_*dev 5 ios swiftui

我在视图上使用.contextMenuwith .onDrag,这似乎非常棘手:

通过设置为 true,背景颜色变为灰色dragging。这是由.onDrag打开上下文菜单时已经发生的情况触发的(有点早但还可以)。当我使用按钮关闭菜单时,我可以设置dragging为 false。当我使用拖动时,取消初始化dragging时状态会更改回 false 。ItemProvider到目前为止,一切都很好。

问题

当我点击上下文菜单外部将其关闭时,我似乎无法将dragging状态设置回 false。添加.onDisappearButton菜单中不起作用。

我在这里做错了什么?有什么方法可以在上下文菜单关闭时收到通知,或者在拖动实际开始时发生状态更改dragging(以便在打开上下文菜单时背景不会立即变为灰色)?

代码如下视频。

在此输入图像描述

struct ContentView: View {
    @State var dragging = false
    var body: some View {
        ZStack {
            Rectangle()
                .foregroundColor(.blue)
                .frame(width: 100, height: 100)
                .onDrag {
                    dragging = true
                    let provider =  ItemProvider(contentsOf: URL(string: "Test")!)!
                    provider.didEnd = {
                        DispatchQueue.main.async {
                            dragging = false
                        }
                    }
                    print("init ItemProvider")
                    return provider
                }
                .contextMenu {
                    Button("Close Menu") {
                        dragging = false
                    }
                }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(dragging ? Color.gray : Color.white)
    }
}

class ItemProvider: NSItemProvider {
    var didEnd: (() -> Void)?
    deinit {
        print("deinit ItemProvider")
        didEnd?()
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑(2022 年 12 月):代码似乎可以在 iOS 16.2 中运行。对于早期的 iO​​S 版本,我仍然没有找到一个好的解决方案。

lor*_*sum 5

既然你提到了 UIKit,.contextMenu那就是UIContextMenuInteraction

您可以向 SwiftUI 视图添加UIContextMenuInteraction并有权UIContextMenuInteractionDelegate识别菜单何时关闭。

SwiftUI View > ViewModifier > UIViewRepresentable > Coordinator/UIContextMenuInteractionDelegate

struct CustomContextMenuView: View {
    @State var dragging = false
    var body: some View {
        ZStack {
            Rectangle()
                .foregroundColor(.blue)
                .frame(width: 100, height: 100)
                .onDrag {
                    dragging = true
                    let provider =  ItemProvider(contentsOf: URL(string: "Test")!)!
                    provider.didEnd = {
                        DispatchQueue.main.async {
                            dragging = false
                        }
                    }
                    print("init ItemProvider")
                    return provider
                }
            //Use custom context menu and add actions as [UIAction]
                .contextMenu(actions: [
                    UIAction(title: "Close Menu", handler: { a in
                        print("Close Menu action")
                        dragging = false
                    })
                ], willEnd:  {
                    //Called when the menu is dismissed
                    print("willEnd/onDismiss")
                    dragging = false
                }, willDisplay:  {
                    //Called when the menu appears
                    print("willDisplay/onAppear")
                    
                })
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(dragging ? Color.gray : Color.white)
        
    }
}
Run Code Online (Sandbox Code Playgroud)

.swift可以通过将以下代码粘贴到文件中并像上面的示例一样使用它来实现菜单。

extension View {
    func contextMenu(actions: [UIAction], willEnd: (() -> Void)? = nil, willDisplay: (() -> Void)? = nil) -> some View {
        modifier(ContextMenuViewModifier(actions: actions, willEnd: willEnd, willDisplay: willDisplay))
    }
}
struct ContextMenuViewModifier: ViewModifier {
    let actions: [UIAction]
    let willEnd: (() -> Void)?
    let willDisplay: (() -> Void)?
    
    func body(content: Content) -> some View {
        Interaction_UI(view: {content}, actions: actions, willEnd: willEnd, willDisplay: willDisplay)
            .fixedSize()
        
    }
}

struct Interaction_UI<Content2: View>: UIViewRepresentable{
    typealias UIViewControllerType = UIView
    @ViewBuilder var view: Content2
    let actions: [UIAction]
    let willEnd: (() -> Void)?
    let willDisplay: (() -> Void)?
    func makeCoordinator() -> Coordinator {
        return Coordinator(parent: self)
    }
    func makeUIView(context: Context) -> some UIView {
        
        let v = UIHostingController(rootView: view).view!
        context.coordinator.contextMenu = UIContextMenuInteraction(delegate: context.coordinator)
        v.addInteraction(context.coordinator.contextMenu!)
        return v
    }
    
    func updateUIView(_ uiView: UIViewType, context: Context) {
        
    }
    class Coordinator: NSObject,  UIContextMenuInteractionDelegate{
        var contextMenu: UIContextMenuInteraction!
        
        let parent: Interaction_UI
        
        init(parent: Interaction_UI) {
            self.parent = parent
        }
        func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
            UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: { [self]
                suggestedActions in
                
                return UIMenu(title: "", children: parent.actions)
            })
        }
        func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willDisplayMenuFor configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating?) {
            print(#function)
            parent.willDisplay?()
        }
        func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willEndFor configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating?) {
            print(#function)
            parent.willEnd?()
            
        }
    }
}
Run Code Online (Sandbox Code Playgroud)