0

I have a macOS SwiftUI app that displays a lot of text fields in a grid. Originally I was using SwiftUI TextField's, but they have the problem that you cannot set the focus order of them (the order they receive focus when you press the tab button). I need to change the focus order from horizontal row-by-row, to vertical column-by-column. There does not appear to be an easy way to do this in SwiftUI.

I found a solution for iOS here, that I tried to modify for mac. The problem is that the delegate functions are not getting called. My current code is below. How do I fix this?

Edit: I've updated my code in light of some of the comments, but the problem still remains the same: no delegate methods are being called.

import SwiftUI

struct OrderedTextField: NSViewRepresentable {
    @Binding var text: String
    @Binding var selectedField: Int
    
    var tag: Int
    
    func makeNSView(context: NSViewRepresentableContext<OrderedTextField>) -> NSTextField {
        let textField = NSTextField()
        textField.delegate = context.coordinator
        textField.tag = tag
        textField.placeholderString = ""
        return textField
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(text: $text)
    }
    
    func updateNSView(_ nsView: NSTextField, context: NSViewRepresentableContext<OrderedTextField>) {
        context.coordinator.newSelection = { newSelection in
            DispatchQueue.main.async {
                self.selectedField = newSelection
            }
        }
        
        if nsView.tag == self.selectedField {
            nsView.becomeFirstResponder()
        }
    }
}

extension OrderedTextField {
    class Coordinator: NSObject, NSTextFieldDelegate {
        @Binding var text: String
        var newSelection: (Int) -> () = { _ in }
        
        init(text: Binding<String>) {
            print("Initializing!")
            _text = text
        }
    
        func textShouldBeginEditing(_ textObject: NSText) -> Bool {
            print("Should begin editing!")
            return true
        }
        
        func textDidBeginEditing(_ notification: Notification) {
            print("Began editing")
        }
    
        func textDidChange(_ notification: Notification) {
            print("textDidChange")
        }
        
        func textShouldEndEditing(_ textObject: NSText) -> Bool {
            print("should end editing")
            return true
        }
        
        func textDidEndEditing(_ notification: Notification) {
            print("did end editing")
        }
    }
}
Jeff Rogers
  • 123
  • 8
  • Also, I have tried using @FocusState to implement this using pure SwiftUI code. It works for a small number of TextField's but because my app is somewhat complicated the compiler cannot handle it yet. – Jeff Rogers Jul 12 '21 at 20:44
  • 2
    possible duplicate of https://stackoverflow.com/a/36191763/2303865 – Leo Dabus Jul 12 '21 at 21:03
  • Something like that may work. I would have to rearchitect what I'm doing to try it. It doesn't address the problem of why the delegate functions aren't being called. Which is probably due to some misunderstanding of how things are supposed to work. – Jeff Rogers Jul 12 '21 at 22:33
  • See [becomeFirstResponder()](https://developer.apple.com/documentation/appkit/nsresponder/1526750-becomefirstresponder/): "Use the NSWindow makeFirstResponder(_:) method, not this method, to make an object the first responder. Never invoke this method directly." – Willeke Jul 13 '21 at 00:09
  • `NSTextFieldDelegate` and `UITextFieldDelegate` don't have the same methods. – Willeke Jul 13 '21 at 00:12
  • I've changed it locally to only use NSTextFieldDelegate methods and they are still never called. – Jeff Rogers Jul 13 '21 at 00:20
  • What is 'makeNSView' for? – El Tomato Jul 13 '21 at 02:11
  • Does this answer your question? [Select next NSTextField with Tab key in Swift](https://stackoverflow.com/questions/36000867/select-next-nstextfield-with-tab-key-in-swift) – El Tomato Jul 13 '21 at 02:13
  • That is using Interface Builder and I'm not sure how to covert that solution to SwiftUI. It probably would work but I'd prefer to stick with SwiftUI if it's possible. – Jeff Rogers Jul 13 '21 at 03:06

0 Answers0