NSTextField 的焦点环(focus ring)由以下机制决定:
-
Cell-Based 架构
- NSTextField 使用
NSTextFieldCell来处理绘制和编辑 - 焦点环基于 cell 的 `focus焦点环边界既不来自 view 的 frame,也不完全来自约束设置的尺寸
- NSTextField 使用
-
Intrinsic Content Size 优先
- Cell 有自己的
intrinsicContentSize,基于字体和内容计算 - 即使外部 view 通过约束改变了 frame,cell 仍然使用自己计算的内容尺寸来绘制焦点环
- Cell 有自己的
-
系统绘制机制
- 焦点环由系统的
-drawFocusRingMask和-focusRingMaskBounds方法绘制 - 这些方法返回的是 cell 认为的"有效内容区域",而不是 view 的实际 bounds
- 焦点环由系统的
- 预期: 焦点环绘制在控件外部,可能会更大
- 实际: 焦点环位置变了,但尺寸仍然基于 cell 的内容区域
- 结论: 只改变位置,不改变大小
- 预期: 刷新后 cell 会感知到新的 frame尺寸
- 实际: Cell 独立计算,不受 frame 影响
- 结论: 布局更新不影响 cell 的内部逻辑
- 预期: 让 cell 使用整个 view 的 bounds
- 实际: 这些设置影响文本布局,但不影响焦点环大小
- 结论: Cell 行为略有改善,但焦点环问题依旧
- 预期: 完全控制焦点环的绘制区域
- 实际: Rust 的生命周期系统与 Objective-C runtime 冲突
- 结论: 技术上可行但在 Rust 中实现复杂度极高
不要自定义高度,而是使用系统的标准尺寸:
TextField::new()
.placeholder("Name")
.control_size(0) // Regular: 标准高度 (~22px)
.control_size(3) // Large: 大尺寸 (~28px, macOS 11+)
.bezel_style(1) // Rounded bezel
.builder()
.font_size(14.0); // 调整字体大小而不是控件高度优点:
- ✅ 焦点环完美匹配
- ✅ 符合系统设计规范
- ✅ 自动适配深色模式
- ✅ 无需额外workaround
缺点:
⚠️ 高度选择有限(只有 4 种系统预设)
禁用焦点环,用自定义边框/背景色来表示焦点状态:
// 创建带自定义焦点效果的 TextField wrapper
fn custom_focus_textfield() -> UIBuilder<NSView> {
let container = rect()
.height(Size::Fixed(44.0))
.corner_radius(6.0)
.background((240, 240, 240)); // 灰色背景
let field = TextField::new()
.placeholder("Name")
.focus_ring_type(1) // 禁用系统焦点环
.no_bezel() // 无边框
.builder();
container.child(field)
}
// TODO: 添加焦点状态监听,改变容器背景色/边框优点:
- ✅ 完全自定义外观
- ✅ 焦点效果可以是任意尺寸
- ✅ 可以实现现代 UI 设计
缺点:
⚠️ 需要实现焦点状态监听⚠️ 不符合 macOS 原生体验⚠️ 需要自己处理可访问性
不增加 TextField 高度,而是在外面包一层有 padding 的容器:
let field_wrapper = rect()
.padding(EdgeInsets::symmetric(12.0, 8.0)) // 上下各 12px
.child(
TextField::new()
.placeholder("Name")
.bezel_style(1)
.builder()
);优点:
- ✅ TextField 使用标准高度,焦点环正常
- ✅ 视觉上看起来更大(因为有padding)
- ✅ 保持原生体验
缺点:
⚠️ 增加视图层级⚠️ 点击区域变大但输入区域没变
承认焦点环限制,但优化其他视觉元素:
TextField::new()
.placeholder("Your Name")
.bezel_style(1)
.width(Size::Fixed(360.0))
.height(Size::Fixed(40.0)) // 自定义高度
.focus_ring_type(2) // Exterior - 至少让它更明显
.builder()
.font_size(15.0) // 大字体提升可读性
.color((0, 0, 0, 200)) // 深色文字contrast说明: 虽然焦点环小,但通过其他方式提升整体体验
const FORM_FIELD_HEIGHT: f64 = 40.0; // 定义但知道焦点环限制
TextField::new()
.placeholder("Name")
.bezel_style(1)
// 选项 1: 使用标准高度(焦点环完美)
.control_size(3) // Large
// 选项 2: 或自定义高度(接受焦点环小的问题)
// .height(Size::Fixed(FORM_FIELD_HEIGHT))
// .focus_ring_type(2) // 至少让它更visible
.builder()
.font_size(14.0);-
优先使用系统标准尺寸
- Regular (control_size(0)): ~22px 高度
- Large (control_size(3)): ~28px 高度
-
如果必须自定义高度
- 使用 28-44px 范围(太小或太大都不合适)
- 添加
.focus_ring_type(2)让焦点环更visible - 通过字体大小而非控件高度来改善可读性
-
考虑用户体验而非完美像素
- 焦点环小一点不影响实际使用
- Tab 键导航仍然正常工作
- 可访问性不受影响
这是 AppKit 的设计限制,不是bug。
NSTextField 的 cell-based 架构设计就是这样的。如果需要完全自定义的焦点效果:
- 使用方案 B(自定义视觉反馈)
- 或考虑使用 NSView + NSTextView 自己实现文本输入
对于大多数表单应用,建议使用方案 A 或 C,保持原生体验。