macOS Swift开发:应用进入全屏时如何调整TextField内文本大小
macOS Swift NSTextField 文本随控件尺寸动态缩放解决方案
macOS 系统的 NSTextField 默认不会自动调整字体大小匹配控件尺寸,需要手动计算适配字号实现该需求,以下是两种常用实现方案:
方案1:KVO 监听 frame 变化(无需自定义子类)
适合仅在单个页面使用、不需要复用该能力的场景:
- 声明属性持有监听对象和TextField实例
// 持有KVO监听避免被释放 private var textFieldFrameObservation: NSKeyValueObservation? private let targetTextField = NSTextField()
- 初始化控件时添加frame监听
override func viewDidLoad() { super.viewDidLoad() // 基础配置 targetTextField.isEditable = false // 仅作为Label使用时设为false,需要编辑则设为true targetTextField.lineBreakMode = .byTruncatingTail targetTextField.maximumNumberOfLines = 1 // 多行场景设为0 // 添加frame变化监听 textFieldFrameObservation = targetTextField.observe(\.frame, options: [.new]) { [weak self] _, _ in self?.adjustTextFieldFontToFit() } // 后续添加控件到视图、设置约束的逻辑保持不变 }
- 实现字号适配方法
private func adjustTextFieldFontToFit() { let containerSize = targetTextField.bounds.size let textContent = targetTextField.stringValue as NSString var currentFontSize: CGFloat = 200 // 从足够大的初始字号开始向下匹配 var fittedFont: NSFont? // 循环计算适配的最大可用字号 repeat { currentFontSize -= 1 guard let testFont = NSFont.systemFont(ofSize: currentFontSize, weight: .regular) else { break } let textBoundingRect = textContent.boundingRect( with: NSSize(width: containerSize.width, height: .greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: [.font: testFont] ) // 文本尺寸小于控件尺寸时结束匹配 if textBoundingRect.height <= containerSize.height && textBoundingRect.width <= containerSize.width { fittedFont = testFont break } } while currentFontSize > 1 // 可自行修改最小字号限制 if let finalFont = fittedFont { targetTextField.font = finalFont } }
方案2:自定义复用型 NSTextField 子类
适合多个页面需要用到该能力的场景,一次定义全局复用:
class ScalingTextField: NSTextField { // 可外部配置的字号上下限 var maxFontSize: CGFloat = 200 var minFontSize: CGFloat = 1 // 控件尺寸变化时重新计算字号 override func layout() { super.layout() adjustFontToFitBounds() } // 文本内容变化时重新计算字号 override var stringValue: String { didSet { adjustFontToFitBounds() } } private func adjustFontToFitBounds() { let textContent = stringValue as NSString var testFontSize = maxFontSize var fittedFont: NSFont? repeat { testFontSize -= 1 guard let testFont = NSFont.systemFont(ofSize: testFontSize, weight: .regular) else { continue } let textBoundingRect = textContent.boundingRect( with: NSSize(width: bounds.width, height: .greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: [.font: testFont] ) if textBoundingRect.width <= bounds.width && textBoundingRect.height <= bounds.height { fittedFont = testFont break } } while testFontSize > minFontSize if let finalFont = fittedFont { font = finalFont } } }
使用时直接把你的TextField实例类型改为ScalingTextField即可,无需额外配置。
注意事项
- 多行文本场景下,把
maximumNumberOfLines设为0,计算textBoundingRect时高度限制改为控件的实际高度即可 - 使用自定义字体时,把代码中的
NSFont.systemFont替换为对应自定义字体的初始化方法即可 - 对性能要求高的场景,可以把逐次减1的匹配逻辑替换为二分法,大幅减少循环次数
内容的提问来源于stack exchange,提问作者hurdy gurdy man
相关产品推荐
相关产品推荐

