Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange scrolling on NSTextField

I have a NSTextField that I try to resize automatically given a certain criteria whenever the content of it changes.

Sometimes, when start typing the content start moving up (or down) the visible part of the text field as shown in the following gif:

gif

If i click inside the NSTextField, the content appears in the right position again.

Firing up the visual debugger in XCode I saw that when this case happen, the private subview of NSTextField: _NSKeyboardFocusClipView has a frame whose Y coordinate has a negative number.

I am not sure what causes that.

Here is my textField resize behavior:

import Cocoa

struct TextFieldResizingBehavior {
  let maxHeight: CGFloat = 100000.0
  let maxWidthPadding: CGFloat = 10
  let minWidth: CGFloat = 50
  let maxWidth: CGFloat = 250

  func resize(_ textField: NSTextField) {
    let originalFrame = textField.frame

    var textMaxWidth = textField.attributedStringValue.size().width
    textMaxWidth = textMaxWidth > maxWidth ? maxWidth : textMaxWidth
    textMaxWidth += maxWidthPadding

    var constraintBounds: NSRect = textField.frame
    constraintBounds.size.width = textMaxWidth
    constraintBounds.size.height = maxHeight

    var naturalSize = textField.cell!.cellSize(forBounds: constraintBounds)

    // ensure minimun size of text field
    naturalSize.width = naturalSize.width < minWidth ? minWidth : naturalSize.width

    if originalFrame.height != naturalSize.height {
      // correct the origin in order the textField to grow down.
      let yOffset: CGFloat = naturalSize.height - originalFrame.height
      let newOrigin = NSPoint(
        x: originalFrame.origin.x,
        y: originalFrame.origin.y - yOffset
      )
      textField.setFrameOrigin(newOrigin)
    }

    textField.setFrameSize(naturalSize)

    Swift.print(
      "\n\n>>>>>> text field resized " +
        "\nnaturalSize=\(naturalSize)" +
        "\noriginalFrame=\(originalFrame)-\(originalFrame.center)" +
        "\nnewFrame=\(textField.frame)-\(textField.frame.center)"
    )
  }
}

which is invoked on the NSTextFieldDelegate method:

extension CanvasViewController: NSTextFieldDelegate {
  override func controlTextDidChange(_ obj: Notification) {
    if let textField = obj.object as? NSTextField {
      textFieldResizingBehavior.resize(textField)
    }
  }

And finally my textfield is declared in the viewController like:

lazy var textField: NSTextField = {
    let textField = NSTextField()
    textField.isHidden = true
    textField.isEditable = false
    textField.allowsEditingTextAttributes = true
    return textField
  }()

full code in: https://github.com/fespinoza/linked-ideas-osx

like image 836
fespinozacast Avatar asked May 10 '17 21:05

fespinozacast


1 Answers

One part of the problem is that you are directly invoking the becomeFirstResponder method. You should not do this.

According to the documentation:

Use the NSWindow makeFirstResponder(_:) method, not this method, to make an object the first responder. Never invoke this method directly.

Furthermore, do you really need the textfield to be able to grow both horizontally and vertically? Making it dynamic based on height alone would obviously be much more straight-forward.

like image 84
lax1089 Avatar answered Nov 19 '22 08:11

lax1089