自定义 NSWindow 的交通灯位置

2,479 阅读2分钟

首发于公众号

在 macOS 系统下,App 左上角的 3 个控制按钮被称为“交通灯”,因为他们的颜色和现实中的交通信号灯一样(红(关闭),黄(最小化),绿(最大化))。

实际开发中会遇到按钮的默认位置不能满足设计的需求,需要调整它们的位置。

然而系统并没有提供方法直接调整他们的位置,开发中常用的方法有两种:

模仿系统按钮 使用黑魔法更改按钮位置

第一种方法,要模仿系统的行为需要一些额外的工作量,不考虑。 第二种方法工作量比较小,只是需要做一些分析工作,just do it !

查看一下这几个按钮的布局:

可以看到它们处于 NSTitlebarContainerView 中的 NSTitlebarView 里,而且使用的是自动布局(autolayout)的方式。

这样思路就很简单了,只要给这几个按钮设置约束,就可以达到更改位置的目的了。

在什么时机设置约束比较好呢?答案是在 Window 初始化完成之后就是最佳时机,代码如下:

@implementation TUINSWindow

- (instancetype)initWithContentRect:(CGRect)rect {
...
  [self relayoutWindowButtons];
...
}

- (void)relayoutWindowButtons {
    NSButton *closeButton = [self standardWindowButton:NSWindowCloseButton];
    NSButton *minButton = [self standardWindowButton:NSWindowMiniaturizeButton];
    NSButton *zoomButton = [self standardWindowButton:NSWindowZoomButton];

    NSView *titlebarView = closeButton.superview;
    closeButton.translatesAutoresizingMaskIntoConstraints = NO;
    [titlebarView addConstraints:@[
                                   [NSLayoutConstraint constraintWithItem:closeButton attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:titlebarView attribute:NSLayoutAttributeTop multiplier:1 constant:9],
                                   [NSLayoutConstraint constraintWithItem:closeButton attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:titlebarView attribute:NSLayoutAttributeLeft multiplier:1 constant:2]
                                   ]];

    minButton.translatesAutoresizingMaskIntoConstraints = NO;
    [titlebarView addConstraints:@[
                                   [NSLayoutConstraint constraintWithItem:minButton attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:titlebarView attribute:NSLayoutAttributeTop multiplier:1 constant:9],
                                   [NSLayoutConstraint constraintWithItem:minButton attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:titlebarView attribute:NSLayoutAttributeLeft multiplier:1 constant:20]
                                   ]];

    zoomButton.translatesAutoresizingMaskIntoConstraints = NO;
    [titlebarView addConstraints:@[
                                   [NSLayoutConstraint constraintWithItem:zoomButton attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:titlebarView attribute:NSLayoutAttributeTop multiplier:1 constant:9],
                                   [NSLayoutConstraint constraintWithItem:zoomButton attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:titlebarView attribute:NSLayoutAttributeLeft multiplier:1 constant:38]
                                   ]];
}

@end

TUINSWindow 继承自 NSWindow,在设置好了 style 之后就可以调用 relayoutWindowButtons 方法设置约束了。

NSWindow 提供了获取系统按钮的方法:- (NSButton *)standardWindowButton:(NSWindowButton)b; 根据之前的分析,按钮的父窗口就是 NSTitlebarView。

看一下调整之后的效果:

大功告成!

偷偷的说,我发现微信、企业微信的 macOS 版本,它们也是这么实现的。

如果要像微信 macOS 版本那样,只需要调整约束如下,在上面的约束中,不添加左约束就可以了:

- (void)relayoutWindowButtons {
    NSButton *closeButton = [self standardWindowButton:NSWindowCloseButton];
    NSButton *minButton = [self standardWindowButton:NSWindowMiniaturizeButton];
    NSButton *zoomButton = [self standardWindowButton:NSWindowZoomButton];

    NSView *titlebarView = closeButton.superview;
    closeButton.translatesAutoresizingMaskIntoConstraints = NO;
    [titlebarView addConstraints:@[
                                   [NSLayoutConstraint constraintWithItem:closeButton attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:titlebarView attribute:NSLayoutAttributeTop multiplier:1 constant:9]
                                   ]];

    minButton.translatesAutoresizingMaskIntoConstraints = NO;
    [titlebarView addConstraints:@[
                                   [NSLayoutConstraint constraintWithItem:minButton attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:titlebarView attribute:NSLayoutAttributeTop multiplier:1 constant:9]
                                   ]];

    zoomButton.translatesAutoresizingMaskIntoConstraints = NO;
    [titlebarView addConstraints:@[
                                   [NSLayoutConstraint constraintWithItem:zoomButton attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:titlebarView attribute:NSLayoutAttributeTop multiplier:1 constant:9]
                                   ]];
}

看一下效果,和微信对比一下,是不是一摸一样了:

全文完,感谢阅读 ^_^