这里必须吐槽简书一下,写了那么长的文章保存后关闭一下竟然就没了??程序员改罚这月的工资了!
算了,没心情再长篇大论得写了,简单说说吧。
本文要实现的效果如下:
Touch Bar 中的内容从左到右依次是两个 Popover 按钮、一个嵌套面板、一个自定义颜色风格的按钮。
然后我们来看实现。
首先要知道,Touch Bar 的显示是取决于响应链的,First Responder 为响应链的最顶端,NSApplication
为响应链的最底端,系统会从最底端开始冒泡执行 NSResponder
的 makeTouchBar
方法,这个方法会返回一个 NSTouchBar
对象。NSTouchBar
并不是一个 view,而应该是一个 model,它通过 identifier 来指定其包含的内容,然后系统根据这些 identifer 来向 delegate 索要 NSTouchBarItem。identifier 的类型就是 NSString
,自己取值就好了,通常是 "com.<项目名>.<类名>.xxxx" 的模式。
NSTouchBarItem
也不是一个 view,它有一个 view
属性,表示真正显示的 view。它有很多派生类,关于它们的用法大家看 API 文档就好了,介绍得很详细。
在开发中,我们需要实现 NSResponder
子类的 makeTouchBar
方法,通常情况我们实现 Window Controller 的就好了,因为每个窗口的 Touch Bar 可能不尽相同,而 View Controller 和 View 也可以实现自己的 Touch Bar。
我们来看看例子中 makeTouchBar
方法:
- (NSTouchBar *)makeTouchBar {
NSTouchBar *touchBar = [[NSTouchBar alloc] init];
touchBar.delegate = self;
touchBar.customizationIdentifier = ViewControllerCustomizationIdentifier;
touchBar.defaultItemIdentifiers = @[FontSizeItemIdentifier, FontFamilyItemIdentifier, NSTouchBarItemIdentifierOtherItemsProxy, ResetStyleIdentifier];
return touchBar;
}
可以看到,这个 Touch Bar 正好有我们界面中所示的那四个元素,其中的 NSTouchBarItemIdentifierOtherItemsProxy
是一个代理项,它是用来显示比自己更接近响应链顶端的 Touch Bar 的。举个例子,我们的窗口里有一个 NSTextView
,它有自己的 Touch Bar,而它在获取焦点的时候就是顶端的 NSResponder
,所以系统会选择现实它的 Touch Bar。但是如果在冒泡过程中,系统发现它的上一级 NSResponder
的 Touch Bar 中有代理项,那么系统就会把 First Responder 的 Touch Bar 嵌入到这个 Touch Bar 的代理项中。但是如果某一层 Touch Bar 没有包含代理项,那么系统只能单独显示 First Responder 的 Touch Bar 了。
这里提一下如何刷新 Touch Bar,有时候你可能想重置 Touch Bar 的内容,最简单的方法就是 self.touchBar = nil
,这时系统会重新调用 makeTouchBar
来创建新的 Touch Bar,效果就相当于 Table View 的 reloadData
。
然后我们来看一下代理方法:
- (NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier {
if ([identifier isEqualToString:FontSizeItemIdentifier]) {
NSPopoverTouchBarItem *item = [[NSPopoverTouchBarItem alloc] initWithIdentifier:identifier];
item.collapsedRepresentationLabel = @"Font Size";
item.showsCloseButton = YES;
NSTouchBar *secondaryTouchBar = [[NSTouchBar alloc] init];
secondaryTouchBar.delegate = self;
secondaryTouchBar.defaultItemIdentifiers = @[FontSizeSliderItemIdentifier];
item.pressAndHoldTouchBar = secondaryTouchBar;
item.popoverTouchBar = secondaryTouchBar;
return item;
}
if ([identifier isEqualToString:FontSizeSliderItemIdentifier]) {
NSSliderTouchBarItem *item = [[NSSliderTouchBarItem alloc] initWithIdentifier:identifier];
item.label = @"Font Size";
item.slider.minValue = 10;
item.slider.maxValue = 72;
item.slider.floatValue = [NSFontManager sharedFontManager].selectedFont.pointSize;
item.slider.target = self;
item.slider.action = @selector(sliderDidChange:);
[item.slider addConstraint:[NSLayoutConstraint constraintWithItem:item.slider attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:250]];
return item;
}
if ([identifier isEqualToString:FontFamilyItemIdentifier]) {
NSPopoverTouchBarItem *item = [[NSPopoverTouchBarItem alloc] initWithIdentifier:identifier];
item.collapsedRepresentationLabel = @"Font Family";
NSTouchBar *secondaryTouchBar = [[NSTouchBar alloc] init];
secondaryTouchBar.delegate = self;
secondaryTouchBar.defaultItemIdentifiers = @[FontFamilyScrubberItemIdentifier];
item.popoverTouchBar = secondaryTouchBar;
return item;
}
if ([identifier isEqualToString:FontFamilyScrubberItemIdentifier]) {
FontFamilyTouchBarItem *item = [[FontFamilyTouchBarItem alloc] initWithIdentifier:identifier];
return item;
}
if ([identifier isEqualToString:ResetStyleIdentifier]) {
NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
NSMutableAttributedString *titleString = [[[NSAttributedString alloc] initWithString:@"Reset Style" attributes:@{NSForegroundColorAttributeName: [NSColor blackColor], NSFontAttributeName: [NSFont systemFontOfSize:0]}] mutableCopy];
[titleString setAlignment:NSTextAlignmentCenter range:NSMakeRange(0, titleString.length)];
NSButton *button = [[NSButton alloc] init];
button.bezelStyle = NSBezelStyleRounded;
button.bezelColor = [NSColor colorWithCalibratedRed:1.00 green:0.81 blue:0.21 alpha:1.00];
button.attributedTitle = titleString;
item.view = button;
return item;
}
return nil;
}
这里就是根据 identifier 去创建 NSTouchBarItem
了。
NSPopoverTouchBarItem
表示一个可以展开的项目,collapsedRepresentationLabel
属性可以制定其未展开时按钮的标题,当然你也可以制定一个自定义 view,如果是自定义 view,你需要让 view 在适当的时机(比如按下时)之行 NSPopoverTouchBarItem
的 showPopover
方法来展开 popover。Popover 的内容是另外一个 NSTouchBar
对象,它也有自己的 identifier 和 delegate,创建好子 Touch Bar 后,将其赋给 popoverTouchBar
属性就可以了,如果你想支持按下后滑动选择的效果,可以再把它赋给 pressAndHoldTouchBar
属性,但这时的子 Touch Bar 应当只包含一个 Slider,不然不会有正常的行为。
NSSliderTouchBarItem
表示一个滑动条,里面有一个 slider 属性,拿出来就可以当普通的 NSSlider
用,给它设置 target-action 等。默认情况下,slider 会填满整个 Touch Bar,如果你不想这样,可以像上面代码一样,给 slider 添加一个 constraint。
普通按钮项需要借助 NSCustomTouchBarItem
来实现,把它的 view 属性设置为一个 NSButton
就好了,但是要注意 button 的 bezelStyle
属性,一定是 NSBezelStyleRounded
,按钮的背景颜色和文本颜色的修改方式大家看代码就能明白了。AttributedString 的字体属性用 [NSFont systemFontOfSize:0]
来表示一个默认值,由于自己创建的字符串不包含对其属性,默认是居左的,所以还要设置一下对其方式,让其居中。
下面来说说 NSScrubber
这个东西,它是一个用来从一组选项中选取一项的控件,支持滑动选择
通常使用它你需要子类化一个 NSCustomTouchBarItem
, 在 Popover 的 Touch Bar 里把它添加进去。在子类初始化方法中配置 NSScrubber
:
- (instancetype)initWithIdentifier:(NSTouchBarItemIdentifier)identifier {
self = [super initWithIdentifier:identifier];
if (self) {
[self setup];
}
return self;
}
- (void)setup {
NSScrubber *scrubber = [[NSScrubber alloc] init];
scrubber.scrubberLayout = [[NSScrubberFlowLayout alloc] init];
scrubber.mode = NSScrubberModeFree;
scrubber.selectionBackgroundStyle = [NSScrubberSelectionStyle outlineOverlayStyle];
scrubber.delegate = self;
scrubber.dataSource = self;
[scrubber registerClass:[NSScrubberTextItemView class] forItemIdentifier:TextItemIdentifier];
self.fontNames = @[@"Arial", @"Courier", @"Gill Sans", @"Helvetica", @"Impact", @"Menlo", @"Times New Roman", @"苹方", @"手札体", @"娃娃体", @"圆体"];
self.view = scrubber;
}
NSScrubber
的 Delegate 和 Data Source 很类似于 Table View 的,直接看代码吧:
- (NSInteger)numberOfItemsForScrubber:(NSScrubber *)scrubber {
return self.fontNames.count;
}
- (NSScrubberItemView *)scrubber:(NSScrubber *)scrubber viewForItemAtIndex:(NSInteger)index {
NSScrubberTextItemView *view = [scrubber makeItemWithIdentifier:TextItemIdentifier owner:nil];
view.textField.stringValue = self.fontNames[index];
return view;
}
- (NSSize)scrubber:(NSScrubber *)scrubber layout:(NSScrubberFlowLayout *)layout sizeForItemAtIndex:(NSInteger)itemIndex {
NSString *string = self.fontNames[itemIndex];
NSRect bounds = [string boundingRectWithSize:NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
attributes:@{NSFontAttributeName: [NSFont systemFontOfSize:0]}];
return NSMakeSize(bounds.size.width + 20, 30);
}
- (void)scrubber:(NSScrubber *)scrubber didSelectItemAtIndex:(NSInteger)selectedIndex {
}
注意你需要自己实现子视图的尺寸计算工作,在代理方法中计算文本尺寸,加上预留的 padding 返回给 NSScrubber
。
本文简单的介绍了一下 Touch Bar 的开发模式,实质上还是类似 Table View 一样,由 Data Source 和 Delegate 组成。很多属性大家可以自己尝试,本文就不再啰嗦了。
网友评论