苹果在2016年秋天发布了新款MacBook Pro,其中有一个新的功能就是用Touch Bar来代替了传统的功能键(F1-F12),它功能强大,开发者可以将其设计成各种样式,并允许用户自定义,为用户和笔记本电脑之间的交互添加了新的方式。个人用过之后,还是非常不错的,尽管它实现的功能可以用快捷键或是鼠标操作来代替,但是在实际使用中,确实是直观并且方便了许多。

    当然,我们并不只能是用用就罢了,作为开发者,我们还要学习如何去使用它提供的API,使得我们自己开发的App也可以支持Touch Bar。今天就来简单讲解一下如何对Touch Bar进行开发。

    由于我们的任务仅仅是学习如何开发Touch Bar,因此,实例程序不做任何的功能,仅仅用来演示,例程中会设置一个文本标签以及若干Touch Bar按钮,当点击对应按钮的时候文本标签会显示不同的文字。而且还会讲解如何让用户可以自定义TouchBar内容。

    重要提醒:TouchBar功能只能用于macOS 10.12.1以及之后的版本。

    我们创建一个标准的Cocoa应用程序,程序会生成一个StoryBoard,一个AppDelegate类和一个ViewController类。我们还需要创建一个WindowsController类(原因等一下会说明)。如图所示(OC和swift会有不同)

iOS UIToolbar设置按钮 苹果touchbar_ide

iOS UIToolbar设置按钮 苹果touchbar_swift_02


    Touch Bar工具的相应是气泡式的,由ApplicationDelegate首先开始触发makeTouchBar()函数,WindowController会相应,但是到此会截止,因此,我们必须在WindowController出来实现此功能,那么在此之前,我们需要把默认的NSWindowController改成我们新建的WindowsController类。

iOS UIToolbar设置按钮 苹果touchbar_swift_03

然后在WindowsController类中实现makeTouchBar()方法,当然直接在这里实现也是可以的,但是,我们在视图上的控件就不方便绑定在这里了,因此,最好的方法是把控制代码写到ViewController类中,所以这里需要一个调用:

swift中:

// WindowsController.swift
import Cocoa

class WindowsController: NSWindowController {

    override func windowDidLoad() {
        super.windowDidLoad()
    
        // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
    }
    @available(OSX 10.12.2, *)
    override func makeTouchBar() -> NSTouchBar? {
        return contentViewController?.makeTouchBar()
    }
    
}

OC中:

// WindowsController.m
#import "WindowsController.h"

@interface WindowsController ()

@end

@implementation WindowsController

- (void)windowDidLoad {
    [super windowDidLoad];
    
    // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
}

- (NSTouchBar *)makeTouchBar {
    return [self.contentViewController makeTouchBar];
}
@end

这里,makeTouchBar()方法就是用于初始化TouchBar的,contentViewController方法会找到其绑定的ViewController中,所以,接下来的任务就是在ViewController中实现功能了。

    既然我们在WindowsController中调用的是ViewController的makeTouchBar()方法,所以,我们要在这个方法中生成一个touchBar对象,然后返回回去。那么生成这个对象以后,我们还需要给它指定默认有那个按钮,用户可添加的有哪些,用户不可删除的又有哪些。比如说默认的控件,就需要用defaultIdentifiers方法,该方法要求传入一个数组,其实就是一个标识符的数组,在swift中被包装成了对象,而在OC中就是NSString的一个typedef而已,下面先给出这一部分代码然后再详细讲解。

swift中:

// ViewController.swift
import Cocoa

class ViewController: NSViewController, NSTouchBarDelegate {

    @IBOutlet weak var label1: NSTextField!
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Do any additional setup after loading the view.
    }

    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }

    @available(OSX 10.12.2, *)
    override func makeTouchBar() -> NSTouchBar? {
        let touchBar = NSTouchBar()
        touchBar.delegate = self
        touchBar.customizationIdentifier = NSTouchBarCustomizationIdentifier("tb")
        touchBar.defaultItemIdentifiers = [NSTouchBarItemIdentifier("t1"), NSTouchBarItemIdentifier("t2"), NSTouchBarItemIdentifier("t3")]
        
        touchBar.customizationAllowedItemIdentifiers = [NSTouchBarItemIdentifier("t1"), NSTouchBarItemIdentifier("t2"), NSTouchBarItemIdentifier("t3"), NSTouchBarItemIdentifier("t4"), NSTouchBarItemIdentifier("t3")]
        touchBar.customizationRequiredItemIdentifiers = [NSTouchBarItemIdentifier("t1")];
        return touchBar
    }
    @available(OSX 10.12.2, *)
    func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItemIdentifier) -> NSTouchBarItem? {
        var tmp: String!
        switch identifier {
        case NSTouchBarItemIdentifier("t1"):
            tmp = "t1"
        case NSTouchBarItemIdentifier("t2"):
            tmp = "t2"
        case NSTouchBarItemIdentifier("t3"):
            tmp = "t3"
        default:
            tmp = "haha"
        }
        let touchBarItem = NSCustomTouchBarItem(identifier: identifier)
        touchBarItem.customizationLabel = "This is " + tmp
        touchBarItem.view = NSButton(title: tmp, target: self, action: #selector(tbClick(_:)))
        return touchBarItem
    }
    func tbClick(_ sender: NSButton) {
        label1.stringValue = sender.title
    }
}



OC中:

// ViewController.m
#import "ViewController.h"


@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // Do any additional setup after loading the view.
}


- (void)setRepresentedObject:(id)representedObject {
    [super setRepresentedObject:representedObject];

    // Update the view, if already loaded.
}

- (NSTouchBar *)makeTouchBar {
    NSTouchBar *touchBar = [[NSTouchBar alloc] init];
    [touchBar setDelegate:self];
    [touchBar setCustomizationIdentifier:@"tb"];
    [touchBar setDefaultItemIdentifiers:@[@"i1", @"i2", @"i3"]];
    [touchBar setCustomizationAllowedItemIdentifiers:@[@"i1", @"i2", @"i3", @"i4", @"i5"]];
    return touchBar;
}

- (NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier {
    NSCustomTouchBarItem *touchBarItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
    touchBarItem.customizationLabel = [NSString stringWithFormat:@"This is %@", identifier];
    touchBarItem.view = [NSButton buttonWithTitle:identifier target:self action:@selector(tbClick:)];
    return touchBarItem;
}

- (void)tbClick:(NSButton *)sender {
    self.label1.stringValue = sender.title;
}

@end

    有一点药说明的是,我的两份代码并不是完全对应关系,唯一的差别仅仅在数组中的内容而已(swift中写的t1, t2, t3, haha;OC中写的i1, i2, i3, i4, i5),这个只是我懒得改了而已,哈哈,其实没什么区别啦,其他地方都是完全一致的。



    在makeTouchBar()方法中,首先创建一个touchBar对象,然后为其设置一个标识符(如果不设置的话,菜单中的自定义Touch Bar按钮就是灰的不可点状态),之后为其设置默认的、可定义的和不可删除的按钮标识符,最后返回这个touchBar。

    到目前为止,TouchBar就已经设置好了,但是这里的设置很像是TableView那样,只是把数量和操作规则设置好了,而具体呈现出来的样子以及触发的事件等等还需要通过代理来进行设置。只有遵守了协议NSTouchBarDelegate并且实现了touchBar:makeItemForIdentifier:方法的对象才可充当代理,这里我们直接让ViewController来充当代理,所以要遵守该协议,swift已经在上面代码体现出,而OC代码需要对ViewController.h进行更改:

// ViewController.h
#import <Cocoa/Cocoa.h>

@interface ViewController : NSViewController<NSTouchBarDelegate>
@property (weak) IBOutlet NSTextField *label1;
@end

    顺势提一下,那个label1是在View中的一个文本控件关联过来的,这是基础Xcode开发的只是,这里就不再演示该过程。

    程序在完成了makeTouchBar()函数后,会对其中的每一个TouchBarCell调用一次代理方法,也就是touchBar:makeItemForIdentifier:方法。identifier就是它的标识符,所以,我们在该函数中的任务就是,通过标识符来判断这是哪一个cell,然后我们渲染出对应的控件,并且绑定相应的事件。TouchBar可以理解成一个容器,它有一个属性是View,你可以把它赋值成任何一种View(利用多态性,可以衍生为任一一个子类对象),比如说图片、标签、滑动条之类的,这里为了演示方便,我们就生成一个最简单的按钮,并且我们让所有的cell都实现同样的功能(实际开发时就根据这个标识符的不同,把它变成实际需要的View就可以了)。我们就让它的View成为一个NSButton,并且标题就是它的标识符。这里还有一个customizationLabel属性,这是用户当用户自定义的时候,给这个控件进行说明的(后面有截图)。历程中我们把按钮的点击触发事件设定为tbClick:方法,并且实现该方法就是把点击的按钮标题显示在窗口中的标题上。

    运行程序的效果:

iOS UIToolbar设置按钮 苹果touchbar_swift_04

与此同时的TouchBar:

iOS UIToolbar设置按钮 苹果touchbar_ide_05

当我们点击t1、t2或t3时,Label字样就会变成对应的字样。

那么,如果我们希望用户可以自定义工具条,就还需要一个步骤,要在AppDelegate中添加一行代码:

swift中:

// AppDelegate.swift
import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {



    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Insert code here to initialize your application
        if #available(OSX 10.12.2, *) {
            NSApplication.shared().isAutomaticCustomizeTouchBarMenuItemEnabled = true
        } else {
            // Fallback on earlier versions
        }
    }

    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }


}

OC中:

// AppDelegate.m
#import "AppDelegate.h"

@interface AppDelegate ()

@end

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    // Insert code here to initialize your application
    [[NSApplication sharedApplication] setAutomaticCustomizeTouchBarMenuItemEnabled:YES];
}


- (void)applicationWillTerminate:(NSNotification *)aNotification {
    // Insert code here to tear down your application
}


@end



    这行代码的意思就是在程序的视图(View)菜单项中自动添加一个自定义工具条的菜单项。如果不用这个的话,我们也可以自己绘制一个菜单项或者其他工具,然后把触发事件指向到toggleTouchBarCustomizationPalette:方法也同样可以实现。

    添加了以后再运行程序,就可以找到对应的菜单项:

iOS UIToolbar设置按钮 苹果touchbar_自定义_06

点开之后就可以自定义工具条:

iOS UIToolbar设置按钮 苹果touchbar_swift_07

在这里就能看到刚才说的每个控件对应的说明。我们可以试着把haha也放上去,点击Done之后,点击haha按钮,屏幕上文字也是变成了haha.

    到此为止,关于TouchBar开发的简单说明就到这里,要想做出真正酷炫的效果,那还是需要把它用到你的项目里,和其他技巧一起来使用。尽可能发挥想象,创新便会由此而生!