Last active
May 30, 2024 01:08
-
-
Save danielpunkass/548f4ecd2f5debb987d02a46a1d48450 to your computer and use it in GitHub Desktop.
NSTextField subclasses that grow/shrink their width or height to fit text content
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// RSDimensionHuggingTextField.swift | |
// RSUIKit | |
// | |
// Created by Daniel Jalkut on 6/13/18. | |
// | |
import Cocoa | |
// You probably want to use one of RSHeightHuggingTextField or RSWidthHuggingTextField, below | |
open class RSDimensionHuggingTextField: NSTextField { | |
public enum Dimension { | |
case vertical | |
case horizontal | |
} | |
var huggedDimension: Dimension | |
init(frame frameRect: NSRect, huggedDimension: Dimension) { | |
self.huggedDimension = huggedDimension | |
super.init(frame: frameRect) | |
} | |
// For subclasses to pass in the dimension setting | |
public init?(coder: NSCoder, huggedDimension: Dimension) { | |
self.huggedDimension = huggedDimension | |
super.init(coder: coder) | |
} | |
public required init?(coder: NSCoder) { | |
// We don't yet support dimension being coded, just default to vertical | |
self.huggedDimension = .vertical | |
super.init(coder: coder) | |
} | |
open override var intrinsicContentSize: NSSize { | |
get { | |
let defaultCellSize = super.intrinsicContentSize | |
guard let textCell = self.cell else { | |
return defaultCellSize | |
} | |
// Set up the bounds to induce unlimited sizing in the desired dimension | |
var cellSizeBounds = self.bounds | |
switch self.huggedDimension { | |
case .vertical: cellSizeBounds.size.height = CGFloat(Float.greatestFiniteMagnitude) | |
case .horizontal: cellSizeBounds.size.width = CGFloat(Float.greatestFiniteMagnitude) | |
} | |
// Do the actual sizing | |
var nativeCellSize = textCell.cellSize(forBounds: cellSizeBounds) | |
// Special case - work around a bug in which cellSize(forBounds:) returns a width | |
// greater than the supplied bounds. This only seems to occur when the string just | |
// barely doesn't fit. Coerce it into computing a taller value by temporary setting | |
// a slightly longer string... I'm limiting this to vertical hugging fields for now | |
// because that's the only scenario where I know it causes a problem. FB7448962. | |
if self.huggedDimension == .vertical && (nativeCellSize.width > cellSizeBounds.width) { | |
// Use a copy of the cell to avoid sending the view into a life updateConstraints/loopfkdsj | |
if | |
let measuringCell = textCell.copy() as? NSTextFieldCell, | |
let mutableString = measuringCell.attributedStringValue.mutableCopy() as? NSMutableAttributedString | |
{ | |
mutableString.append(NSAttributedString(string: "X")) | |
measuringCell.attributedStringValue = mutableString | |
nativeCellSize = measuringCell.cellSize(forBounds: cellSizeBounds) | |
} | |
} | |
// Return an intrinsic size that imposes calculated (hugged) dimensional size | |
var intrinsicSize = NSSize(width: NSView.noIntrinsicMetric, height: NSView.noIntrinsicMetric) | |
switch self.huggedDimension { | |
case .vertical: | |
intrinsicSize.height = nativeCellSize.height | |
case .horizontal: | |
intrinsicSize.width = nativeCellSize.width | |
} | |
return intrinsicSize | |
} | |
} | |
open override func textDidChange(_ notification: Notification) { | |
super.textDidChange(notification) | |
self.invalidateIntrinsicContentSize() | |
// It seems important to set the string from the cell on ourself to | |
// get the change to be respected by the cell and to get the cellSize | |
// computation to update! | |
if let changedCell = self.cell { | |
self.stringValue = changedCell.stringValue | |
} | |
} | |
} | |
open class RSHeightHuggingTextField: RSDimensionHuggingTextField { | |
@objc init(frame frameRect: NSRect) { | |
super.init(frame: frameRect, huggedDimension: .vertical) | |
} | |
public required init?(coder: NSCoder) { | |
super.init(coder: coder, huggedDimension: .vertical) | |
} | |
public override init(frame frameRect: NSRect, huggedDimension: Dimension = .vertical) { | |
super.init(frame: frameRect, huggedDimension: huggedDimension) | |
} | |
} | |
open class RSWidthHuggingTextField: RSDimensionHuggingTextField { | |
@objc init(frame frameRect: NSRect) { | |
super.init(frame: frameRect, huggedDimension: .horizontal) | |
} | |
public required init?(coder: NSCoder) { | |
super.init(coder: coder, huggedDimension: .horizontal) | |
} | |
public override init(frame frameRect: NSRect, huggedDimension: Dimension = .horizontal) { | |
super.init(frame: frameRect, huggedDimension: huggedDimension) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@michaeljtsai Thanks, I'll have to take a look and see if something similar would work well for my uses too.