[recovery mode] Внедрение NSTouchBar на Swift

Недавно Apple представила миру новую линейку MacBook Pro. И одной из особенностей свежей версии стало то, что верхний ряд системных кнопок в ней удален — вернее, заменен на на мультитач экран. Разработчикам это нововведение должно быть интересно в первую очередь, ведь на панели выделена область, которую можно использовать в собственных приложениях. Компания Apple даже предоставила API для ее использования. В этой статье мы расскажем и покажем, как осваивали возможности NSTouchBar. Полученные знания мы в последствии применили в апдейте MaCleaner.

067bec2b486c412b80170e161f53135b.jpg

Для начала давайте разберемся, что вообще представляет собой NSTouchBar.

0d0dfe4e5dc64d339073ec30c649740c.png

Application Region — это, собственно, та самая часть, которая отводится под нужды приложения. Именно здесь будет отображаться все, что необходимо для корректной его работы.

Control Strip — системная панель. Сюда разработчику вход закрыт — ни помещать свои объекты, ни как-то еще ее модифицировать мы не можем. Здесь находятся кнопки, которые раньше были на месте NSTouchBar — изменение яркости, громкости и другие.

System Button — системная кнопка

Для того чтобы внедрять поддержку NSTouchBar в свои проекты, необязательно иметь новенький MacBookPro с тачбаром. В Xcode есть его симулятор, который вызывается через Window > Show Touch Bar. Однако воспользоваться им вы сможете только при условии, что у вашего Xcode версия минимум 8.1, а у системы — 10.12.1, причем с билдом не ниже 16B2657.

Итак, вы усердно трудились и написали чудесное приложение — оно висит в статус баре, показывает во всплывающем окне системное оформление рабочего стола и меняет его на тот прикид, который выберет пользователь. Выглядит все это так:

e46e7afc4ce242bb97510f7acd1abd05.png
aff9fa2d1c3d4ff2b270c40dba6b8d55.png
Если вы загорелись этим чудесным приложением — можете скачать версию без NSTouchBar здесь и работать над ним вместе с нами.

Теперь введем в наш проект поддержку NSTouchBar. Для начала подключим NSTouchBar и выведем в нем «Hello World!». Добавим в наш MainPopoverController следущее расширение:

@available(OSX 10.12.1, *)
extension MainPopoverController : NSTouchBarDelegate {
    
    override open func makeTouchBar() -> NSTouchBar? {
        let touchBar = NSTouchBar()
        touchBar.delegate = self
        touchBar.customizationIdentifier =  NSTouchBarCustomizationIdentifier("My First TouchBar")
        touchBar.defaultItemIdentifiers = [NSTouchBarItemIdentifier("HelloWorld")]
        touchBar.customizationAllowedItemIdentifiers = [NSTouchBarItemIdentifier("HelloWorld")]
        return touchBar
    }
    
}

В методе makeTouchBar () мы создаем объект NSTouchBar и указываем его делегат. Каждый NSTouchBar и каждый NSTouchBarItem должны иметь уникальные идентификаторы. Поэтому мы прописываем идентификатор для нашего NSTouchBar, затем указываем идентификаторы объектов, которые в него поместим, и, наконец, задаем порядок отображения объектов. Теперь добавим метод, который будет заполнять наш NSTouchBar объектами:

func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItemIdentifier) -> NSTouchBarItem? {
        switch identifier {
        case NSTouchBarItemIdentifier("HelloWorld"):
            let customViewItem = NSCustomTouchBarItem(identifier: identifier)
            customViewItem.view = NSTextField(labelWithString: "Hello World!")
            return customViewItem
        default:
            return nil
        }
}

В этом методе заполнять NSTouchBar можно как угодно. Для данного примера мы создали NSCustomTouchBarItem и поставили в него NSTextField с текстом «Hello World!». Теперь пришло время запустить проект и наслаждаться нашим эксклюзивным NSTouchBar! Запускаем — и…

fa00bdb13dc24d6bb5124b772e6b4ee4.png
a0cd945989e74a20a1ad055a6e147ce1.png

Где же наш «Hello World!»? А дело вот в чем. Для того, чтобы мы могли сгенерировать NSTouchBar в MainPopoverController, хотя бы один объект в поповере должен получить фокус. В нашем поповере есть два объекта — сам NSView и CollectionView. У NSView (и CollectionView) acceptsFirstResponder по умолчанию всегда возвращает false — таким образом, когда мы запускаем приложение, фокус не помещается ни на один объект, а значит, и NSTouchBar не генерируется. Мы выйдем из положения следующим образом: создадим свой NSView и переопределим acceptsFirstResponder так, чтобы он возвращал true.

class MainPopoverView: NSView {

    override var acceptsFirstResponder: Bool {
        get {
            return true
        }
    }
  
}

Попробуем запустить приложение после использования этого класса в нашем поповере и посмотрим, что получилось:

bd0d2ea8e83c4b6284a121b4dc420c37.png
e4fd90c533c74d35810e3ddb79448112.png

Наш NSTouchBar наконец-то явил себя миру и даже с этим миром поздоровался. Теперь нам надо реализовать NSTouchBar применимо к функционалу нашего приложения. Мы хотим поместить в NSTouchBar все картинки из нашей коллекции с тем, чтобы при клике на картинку приложение ставило ее фоном для рабочего стола. Так как для всей галереи в NSTouchBar места не хватит, будем применять прокручиваемый NSScrubber. Для начала изменим метод, заполняющий наш NSTouchBar объектами, чтобы он использовал NSScrubber.

func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItemIdentifier) -> NSTouchBarItem? {
        switch identifier {
        case NSTouchBarItemIdentifier("HelloWorld"):
            let scrubberItem = NSCustomTouchBarItem(identifier: identifier)
            let scrubber = NSScrubber()
            scrubber.scrubberLayout = NSScrubberFlowLayout()
            scrubber.register(NSScrubberImageItemView.self, forItemIdentifier: "ScrubberItemIdentifier")
            scrubber.mode = .free
            scrubber.selectionBackgroundStyle = .roundedBackground
            scrubber.delegate = self
            scrubber.dataSource = self
            scrubberItem.view = scrubber
            return scrubberItem
        default:
            return nil
        }
}

Для того чтобы заполнять NSScrubber объектами, необходимо подписаться на NSScrubberDataSource; для обработки нажатия на определенный объект из него — на NSScrubberDelegate, а для того, чтобы указать размер объекта (а в NSTouchBar максимальная высота объекта 30, иначе объект будет обрезаться) — на NSScrubberFlowLayoutDelegate.

В итоге добавим следущее расширение для нашего MainPopoverController:

@available(OSX 10.12.1, *)
extension MainPopoverController: NSScrubberDataSource, NSScrubberDelegate, NSScrubberFlowLayoutDelegate {
    func numberOfItems(for scrubber: NSScrubber) -> Int {
        return wardrobe.wallpapers.count
    }
    
    func scrubber(_ scrubber: NSScrubber, viewForItemAt index: Int) -> NSScrubberItemView {
        let itemView = scrubber.makeItem(withIdentifier: "ScrubberItemIdentifier", owner: nil) as! NSScrubberImageItemView
        itemView.image = wardrobe.wallpapers[index].picture
        return itemView
    }
    
    func scrubber(_ scrubber: NSScrubber, didSelectItemAt index: Int) {
        scrubber.selectedIndex = -1
        if let screen = NSScreen.main() {
            do {
                try NSWorkspace.shared().setDesktopImageURL( wardrobe.wallpapers[index].url, for: screen, options: [:])
            } catch {
                print(error)
            }
        }
    }
    
    func scrubber(_ scrubber: NSScrubber, layout: NSScrubberFlowLayout, sizeForItemAt itemIndex: Int) -> NSSize {
        return NSSize(width: 60, height: 30)
    }
}

Первый метод возвращает количество объектов в NSScrubber. Второй — генерирует объект для определенной позиции в NSScrubber (в нашем случае это картинки для рабочего стола). Третий — обрабатывает нажатие на объект в NSScrubber и ставит выбранную картинку на рабочий стол. Ну, а четвертый метод возвращает размеры объекта в NSScrubber.

Теперь при запуске наш NSTouchBar имеет следущий вид:

3aca9e5761ef4531b721db836b12c98d.png
73d0bbc9d0e04e119410b6d6f55655ac.png

Теперь у нас в приложении есть рабочий NSTouchBar, который может менять оформление рабочего стола. По такому же алгоритму можно реализовать поддержку тачбара и в более сложных проектах, чем мы и планируем заняться в будущем. (ссылка на финальную версию проекта с реализованным NSTouchBar)
Спасибо за внимание и удачи в работе с новой диковинкой от Apple!

Комментарии (0)

© Habrahabr.ru