Touch Bar 快速开发入门

作者: Cyandev | 来源:发表于2016-10-29 22:28 被阅读1461次

    这里必须吐槽简书一下,写了那么长的文章保存后关闭一下竟然就没了??程序员改罚这月的工资了!

    算了,没心情再长篇大论得写了,简单说说吧。

    本文要实现的效果如下:


    Touch Bar 中的内容从左到右依次是两个 Popover 按钮、一个嵌套面板、一个自定义颜色风格的按钮。

    然后我们来看实现。

    首先要知道,Touch Bar 的显示是取决于响应链的,First Responder 为响应链的最顶端,NSApplication 为响应链的最底端,系统会从最底端开始冒泡执行 NSRespondermakeTouchBar 方法,这个方法会返回一个 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 在适当的时机(比如按下时)之行 NSPopoverTouchBarItemshowPopover 方法来展开 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 组成。很多属性大家可以自己尝试,本文就不再啰嗦了。

    相关文章

      网友评论

      • 心对:9999
      • 魔法黛:真机在哪里搞到的?
        魔法黛:@Cyandev :flushed::flushed::flushed::flushed::smirk::smirk::joy::joy:
        Cyandev:@魔法黛 哪里有真机:joy: Simulator
      • goodthing:好迅速啊!
        Cyandev:@爱编程也爱自己 其实新 MacBook Pro 发布的当天就研究完 Touch Bar 了,一开始不想写的,后来还是写一下吧:stuck_out_tongue_winking_eye:

      本文标题:Touch Bar 快速开发入门

      本文链接:https://www.haomeiwen.com/subject/yhpyuttx.html