「这是我参与2022首次更文挑战的第14天,活动详情查看:2022首次更文挑战」
Windows 是 Mac 上所有应用程序的基础。 与 UIWindow 不同,它是 UIView 的子类,大多数人很少需要考虑,NSWindow 与 NSView 完全分开。 它旨在处理多窗口系统所需的大量功能。 Windows 可以调整大小和缩放、增长以适应全屏或最小化以适应 Dock。 它们可以被隐藏、关闭、从一个屏幕移动到另一个屏幕,以及彼此重叠。 Windows 也可以有父子关系,就像视图一样。
Panels是窗口的子集,专为辅助任务而设计。 它们通常用于诸如检查员之类的事情。 关键区别在于,当用户离开应用程序时,面板会自动隐藏。
最后,Sheets是在 Mac 应用程序中显示模式 UI 的主要方式。 在多窗口系统中使用模态窗口可能会很痛苦,因为您可能会忘记模态窗口并最终对为什么无法与应用程序的主窗口进行交互感到困惑。 Sheet通过将这些模态窗口绑定到父级来解决这个问题。
虽然您几乎从不与 iOS 上的窗口进行交互(因为它们占据了整个屏幕),但窗口是 Mac 上的关键组件。从历史上看,Mac 应用程序有多个窗口,每个窗口都有自己的角色,非常类似于 iOS 上的视图控制器。因此,AppKit 有一个 NSWindowController 类,它传统上承担了许多您将在 iOS 上的视图控制器中处理的任务。视图控制器是 AppKit 的一个相对较新的补充,到目前为止,它们默认没有接收操作,并且错过了很多生命周期方法、视图控制器包含以及您习惯于 UIKit 的其他功能。
但 AppKit 发生了变化,因为 Mac 应用程序越来越依赖于单个窗口。从 OS X 10.10 Yosemite 开始,NSViewController 在许多方面与 UIViewController 相似。默认情况下,它也是响应者链的一部分。请记住,如果您将 Mac 应用程序定位到 OS X 10.9 或更早版本,则 Mac 上的窗口控制器更类似于您习惯的 iOS 视图控制器。正如 Mike Ash 所写,在 Mac 上实例化窗口的一个好模式是每个窗口类型有一个 nib 文件和一个窗口控制器。
此外,NSWindow 不是 UIWindow 的视图子类。相反,每个窗口都在 contentView 属性中保存对其顶级视图的引用。
如果您正在为 OS X 10.9 或更低版本进行开发,请注意默认情况下视图控制器不是响应程序链的一部分。 相反,事件将通过视图树冒泡,然后直接进入窗口和窗口控制器。 在这种情况下,如果您希望视图控制器处理事件,则必须手动将其添加到响应者链中。
除了响应者链的不同,AppKit 对动作的方法签名也有更严格的约定。 在 AppKit 中,action 方法总是如下所示:
- (void)performAction:(id)sender;
在 iOS 上完全没有参数或带有发送者和事件参数的变体在 OS X 上不起作用。此外,在 AppKit 中,控件通常包含对一个目标和一个动作对的引用,而您 可以使用 addTarget:action:forControlEvents: 方法将多个目标-动作对与 iOS 上的控件相关联。
默认情况下,AppKit 视图不受 Core Animation 层的支持; 图层支持支持已追溯集成到 AppKit 中。 但是,虽然使用 UIKit 时您永远不必担心这一点,但使用 AppKit 时,您需要做出决定。 AppKit 区分了 layer-backed 和 layer-hosting 视图,并且 layer-backing 可以在每个视图树的基础上打开和关闭。
启用图层支持的最直接方法是在窗口的内容视图上将 WantLayer 属性设置为 YES。 这将导致窗口视图树中的所有视图都有自己的支持层,因此无需在每个单独的视图上重复设置此属性。 这可以在代码中完成,也可以在 Interface Builder 的 View Effects Inspector 中完成。
与 iOS 相比,在 Mac 上,您应该将支持层视为实现细节。 这意味着您不应尝试直接与层交互,因为 AppKit 拥有这些层。 例如,在 iOS 上,您可以简单地说:
self.layer.backgroundColor = [UIColor redColor].CGColor;
但在 AppKit 中,您不应该触摸图层。 如果您想以这种方式与图层交互,那么您必须更进一步。 重写 NSView 的 wantUpdateLayer 方法以返回 YES 使您能够更改图层的属性。 如果你这样做,AppKit 将不再调用视图的 drawRect: 方法。 相反,updateLayer 将在视图更新周期中被调用,您可以在此处修改图层。
例如,您可以使用它来实现具有统一背景颜色的非常简单的视图(是的,NSView 没有 backgroundColor 属性):
@interface ColoredView: NSView
@property (nonatomic) NSColor *backgroundColor;
@end
@implementation ColoredView
- (BOOL)wantsUpdateLayer { return YES; }
- (void)updateLayer
{
self.layer.backgroundColor = self.backgroundColor.CGColor;
}
- (void)setBackgroundColor:(NSColor *)backgroundColor
{
_backgroundColor = backgroundColor;
[self setNeedsDisplay:YES];
}
@end
此示例假定您将在其中插入此视图的视图树已启用图层支持。 对此的替代方法是简单地覆盖 drawRect: 方法来绘制彩色背景。
另一个重要的问题是,图层支持的视图默认将其重绘策略设置为 NSViewLayerContentsRedrawDuringViewResize。 这类似于非图层支持的视图的行为,但如果为动画的每一帧引入绘图步骤,则可能会损害动画性能。
为避免这种情况,您可以将 layerContentsRedrawPolicy 属性设置为 NSViewLayerContentsRedrawOnSetNeedsDisplay。 这样,您可以控制何时需要重绘图层内容。 帧变化不会再自动触发重绘; 您现在负责通过调用 -setNeedsDisplay: 来触发它。
以这种方式更改重绘策略后,您可能还需要查看 layerContentsPlacement 属性,它是视图等效于图层的 contentGravity 属性。 这允许您指定现有图层内容在调整大小时如何映射到图层。
使用 Core Animation 层有一个完全不同的选项——称为层托管视图。 简而言之,通过图层托管视图,您可以随心所欲地使用图层及其子图层。 您为此付出的代价是您不能再向该视图添加任何子视图。 层托管视图是视图树中的叶节点。
要创建一个图层托管视图,您首先必须将一个图层对象分配给图层属性,然后将 WantLayer 设置为 YES。 请注意,这些步骤的顺序至关重要:
- (instancetype)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.layer = [[CALayer alloc] init];
self.wantsLayer = YES;
}
}