Search code examples
swiftmacosswiftuinstextfield

SwiftUI Representable TextField changes Font on disappear


I am using a Representable ViewController for a custom NSTextField in SwiftUI for MacOS. I am applying a customized, increased font size for that TextField. That works fine, I had a question regarding that topic in my last question here.

I am setting the Font of the TextField in my viewDidAppear() method now, which works fine. I can see a bigger font. The problem is now, when my view is not being in focus, the text size is shrinking back to normal. When I go back and activate the window again, I see the small font size. When I type again, it gets refreshed and I see the correct font size.

That is my code I am using:

class AddTextFieldController: NSViewController {
    @Binding var text: String
    let textField = NSTextField()
    var isFirstResponder : Bool = true

    init(text: Binding<String>, isFirstResponder : Bool = true) {
        self._text = text
        textField.font = NSFont.userFont(ofSize: 16.5)
        super.init(nibName: nil, bundle: nil)
        NSLog("init")
    }

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

    override func loadView() {
        textField.delegate = self
        //textField.font = NSFont.userFont(ofSize: 16.5)
        self.view = textField
    }

    override func viewDidAppear() {
        self.view.window?.makeFirstResponder(self.view)
        textField.font = NSFont.userFont(ofSize: 16.5)
    }
}

extension AddTextFieldController: NSTextFieldDelegate {

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

  }
}

And this is the representable:

struct AddTextFieldRepresentable: NSViewControllerRepresentable {

  @Binding var text: String

  func makeNSViewController(
    context: NSViewControllerRepresentableContext<AddTextFieldRepresentable>
  ) -> AddTextFieldController {
    return AddTextFieldController(text: $text)
  }

  func updateNSViewController(
    _ nsViewController: AddTextFieldController,
    context: NSViewControllerRepresentableContext<AddTextFieldRepresentable>
  ) {
    }
}

Here is a demo:

enter image description here

I thought about using Notification when the view is coming back, however not sure

NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: NSApplication.willBecomeActiveNotification, object: nil)

Solution

  • Here is a solution. Tested with Xcode 11.4 / macOS 10.15.4

    class AddTextFieldController: NSViewController {
        @Binding var text: String
        let textField = MyTextField()  // << overridden field
    

    and required custom classes (both!!) for cell & control (in all other places just remove user font usage as not needed any more):

    class MyTextFieldCell: NSTextFieldCell {
        override var font: NSFont? {
            get {
                return super.font
            }
            set {
                // system tries to reset font to default several
                // times (here!), so don't allow that
                super.font = NSFont.userFont(ofSize: 16.5)
            }
        }
    }
    
    class MyTextField: NSTextField {
        override class var cellClass: AnyClass? {
            get { MyTextFieldCell.self }
            set {}
        }
    
        override var font: NSFont? {
            get {
                return super.font
            }
            set {
                // system tries to reset font to default several
                // times (and here!!), so don't allow that
                super.font = NSFont.userFont(ofSize: 16.5)
            }
        }
    }