NSViewControllerRepresentable not responding to events when embedded into SwiftUI List

吃可爱长大的小学妹 提交于 2021-01-29 18:24:54

问题


I have the following code snippets:

The list element:

struct PaletteListRowUIView: View {
    @State var colorPalette: ColorPalette
    var isSelected: Bool
    var onSelect: () -> Void
    
    var body: some View {        
        return VStack(alignment: .leading) {
            HStack {
                Group {
                    if(isSelected) {
                        Ellipse().fill(Color.white)
                    } else {
                        Ellipse().stroke(Color.white)
                    }
                }.contentShape(Ellipse())
                .aspectRatio(1, contentMode: .fit).frame(width: 10, height: 10)
                .gesture(TapGesture().onEnded(onSelect))

                RenameableText(text: $colorPalette.name, onFinished: {/* saves to core data */})
            }
        }
    }
}

The list itself:

struct PaletteListUIView: View {
    @EnvironmentObject var colorPaletteManager: ColorPaletteManager
    
    var body: some View {
        List {
            ForEach(Array(self.colorPaletteManager.colorPalettes.enumerated()), id: \.element.id) { idx, palette in
                PaletteListRowUIView(
                    colorPalette: palette,
                    isSelected: self.colorPaletteManager.isSelected(index: idx),
                    onSelect: { self.colorPaletteManager.selectPalette(at: idx) })
            }.frame(height: 50)

        }.frame(width: 256)
    }
}

And a wrapped NSTextField:

class RenameableTextController: NSViewController {

    @Binding var text: String
    let onFinished: () -> Void
    var originalValue: String?
    var isInEditMode: Bool = false
    private var monitors: [Any?] = []
    private var tapper: NSClickGestureRecognizer?

    init(text: Binding<String>, onFinished: @escaping () -> Void) {
        self._text = text
        self.onFinished = onFinished
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func loadView() {
        let textField = NSTextField()
        textField.cell = CustomTextFieldCell()
        textField.stringValue = text
        textField.isEditable = false
        textField.isBordered = false
        textField.bezelStyle = .roundedBezel
        textField.backgroundColor = .clear
        self.view = textField
    }
    
    override func viewWillDisappear() {
        self.makeTextFieldInEditable()
    }
    
    override func mouseUp(with event: NSEvent) {
        print("double")
        if(event.clickCount == 2) {
            self.makeTextFieldEditable()
        }
    }
    
    func makeTextFieldEditable() {
        self.isInEditMode = true
        let textField = self.view as! NSTextField
        self.originalValue = textField.stringValue
        textField.delegate = self
        textField.isEditable = true
        textField.isBordered = true

        self.view.window?.makeFirstResponder(textField)
        textField.currentEditor()?.selectedRange = .init(location: textField.stringValue.count, length: 0)
        monitorForEvents()
    }
    
    func makeTextFieldInEditable() {
        let textField = self.view as! NSTextField
        textField.resignFirstResponder()
        if(isInEditMode) {   // This line is necessary because otherwise a depencency cycle is created in SwiftUI
            self.view.window?.makeFirstResponder(nil)
        }
        textField.isEditable = false
        textField.isBordered = false
        removeMonitors()
        self.isInEditMode = false
    }
    
    override func cancelOperation(_ sender: Any?) {
        let textField = self.view as! NSTextField
        textField.stringValue = originalValue!
        makeTextFieldInEditable()
    }
}

extension RenameableTextController: NSTextFieldDelegate {

    func controlTextDidChange(_ obj: Notification) {
        if let textField = obj.object as? NSTextField {
            self.text = textField.stringValue
        }
    }

    func controlTextDidEndEditing(_ obj: Notification) {
        makeTextFieldInEditable()
        onFinished()
    }
}

extension RenameableTextController {
    
    func monitorForEvents() {
        let localMonitor = NSEvent.addLocalMonitorForEvents(matching: [.leftMouseUp]) { event in
            self.makeTextFieldInEditable()
            return event
        }
        monitors.append(localMonitor)
    }
    
    func removeMonitors() {
        monitors.forEach { monitor in
            if let monitor = monitor {
                NSEvent.removeMonitor(monitor)
            }
        }
        monitors = []
    }
}

struct RenameableText: NSViewControllerRepresentable {

    @Binding var text: String
    let onFinished: () -> Void

    func makeNSViewController(
        context: NSViewControllerRepresentableContext<RenameableText>
    ) -> RenameableTextController {print("called")
        return RenameableTextController(text: $text, onFinished: onFinished)
    }

    func updateNSViewController(
        _ nsViewController: RenameableTextController,
        context: NSViewControllerRepresentableContext<RenameableText>
    ) {
    }
}

I use the RenameableText in order to allow renaming an entry in the list by double clicking it. But this works only like intended if you replace the List with a VStack. As soon as I use the List SwiftUI View the NSViewControllerRepresentable does not receive the mouseUp event any more. The interesting part is, that Group View containing the Ellipse is still responding to the TapGesture().

Has anyone experienced similar behaviour and knows how to solve this or is this a bug that should be reported?

来源:https://stackoverflow.com/questions/63982519/nsviewcontrollerrepresentable-not-responding-to-events-when-embedded-into-swiftu

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!