2018年仍需继承NSControl开发AppKit自定义控件吗?
传统做法是针对要实现的自定义控件类型继承NSControl和/或NSCell(例如支持自定义绘制的NSButton)。
但在WWDC 2014-204中,苹果表示NSCell正走向“正式弃用”。而在WWDC 2012-217中,苹果建议继承NSView并借助层支持和层属性来绘制简单按钮。
若通过继承NSView创建自定义控件(如“增强型按钮”),则会失去NSControl提供的诸多功能,例如预配置的action和target属性、鼠标追踪、键盘激活,以及其他一些可能未意识到的功能。
如果继承NSControl甚至NSButton,如何正确接管所有绘制工作?苹果编程指南并未就此更新内容。具体而言,NSButton子类能否仅重写自身及NSCell的所有draw...方法,然后在updateLayer中执行自定义操作?能否确保原有的NSButton/NSButtonCell绘制代码不再执行绘制?
若要构建一个带有自定义背景但仍使用字符串类型title或attributedTitle的按钮该怎么做?NSButton支持此功能,但title绘制与updateLayer如何交互?
我的使用场景是创建一个比传统
NSButton具备更多视觉状态和视觉设计的自定义按钮。使用层支持的NSView和updateLayer能轻松实现各种状态,但我更希望继承NSControl甚至NSButton以保留这些类已有的全部功能。
编辑1:根据下方评论修改表述,更准确地反映NSCell当前的弃用状态。
针对你的自定义按钮场景,最优方案是继承NSButton结合层支持实现,既能保留NSControl/NSButton的核心功能,又能灵活自定义视觉效果,具体做法分点说明:
1. 接管绘制:禁用NSCell并使用层绘制
苹果虽未完全移除NSCell,但现代控件开发更推荐直接在控件层级处理绘制。对于NSButton子类:
- 初始化时开启层支持:
self.wantsLayer = YES; - 重写
updateLayer方法实现自定义绘制,这是层支持视图的高效绘制方式,比传统drawRect:更优 - 要阻止原生绘制代码执行,可重写
drawRect:并让它空实现,同时如果需要可以重写buttonCell属性返回自定义NSButtonCell子类,或者直接在updateLayer中完全覆盖绘制,原生绘制流程就不会生效了
2. 保留title/attributedTitle功能
如果想保留原生标题绘制+自定义背景:
- 在
updateLayer中先绘制自定义背景,再调用[super updateLayer];让系统处理标题绘制;也可以反过来,先让系统绘制标题再叠加自定义元素,根据视觉需求调整顺序 - 若要完全自定义标题绘制,可直接获取
self.title或self.attributedTitle,用Core Text在updateLayer中自行绘制,完全控制标题的样式、位置和状态变化
3. 处理多视觉状态
NSButton本身提供了state属性(如NSOnState、NSOffState)及高亮状态,你可以:
- 重写
setState:方法,状态改变时调用[self setNeedsUpdateLayer];触发重绘 - 利用
NSButton自带的鼠标追踪功能,重写mouseEntered:、mouseExited:、mouseDown:等方法维护自定义状态变量,在updateLayer中根据变量绘制对应视觉效果
4. 替代方案:继承NSControl(若不需要NSButton原生行为)
如果要做完全自定义的控件而非按钮扩展,继承NSControl也可行:
- 启用层支持,重写
updateLayer处理绘制 NSControl自带target/action、键盘响应等功能,只需实现交互逻辑(如点击触发action)- 无需处理
NSCell相关代码,直接在NSControl层级完成所有绘制和交互
总结
对你的场景来说,继承NSButton+层支持是最省心的选择,不用重复实现NSControl的核心功能,又能灵活自定义视觉状态。尽量避免直接继承NSView,因为要自己实现鼠标追踪、键盘激活、target/action等功能会增加大量工作量,而这些NSControl家族已经帮你做好了。
内容的提问来源于stack exchange,提问作者kennyc

