Tuesday, December 18, 2012

Использование Table View Mac http://habrahabr.ru/post/136265/

http://habrahabr.ru/post/136265/

Использование Table View

Доброго времени суток!

Уже достаточно давно пытаюсь заставить себя изучить очередной язык/платформу для программирования под Mac OS X/iOS. Интересует именно разработка приложений с нативным GUI, так как консольные приложения можно разрабатывать на чем угодно, начиная с C и C++ и заканчивая модным сейчас Nodejs. Как показала практика, кроссплатформенные фреймворки вроде Qt тут мало подходят, хотя бы потому что не обеспечивают нативный Look and Feel, к которому привыкли пользователи этой ОС.

На хабре есть достаточное количество материалов по языку Objective-C и фреймворку Cocoa. С точки зрения GUI, интерес представляет именно Cocoa, а тут большинство статей ограничивается кнопочками и текстовыми полями. Постараюсь исправить это недоразумение и описать работу с Table View на примере приложения, отображающего список процессов.

Статья не претендует на полноту изложения и абсолютную корректность материала и ориентирована, прежде всего, на начинающих разработчиков. Ошибки и конструктивную критику с радостью выслушаю в комментариях. Кому интересно, добро пожаловать под кат.


Теория


Для разработки нам потребуются некоторые знания о Table View. Давайте с них и начнем. Подробное описание работы с Table View приведено в [1], здесь я ограничусь кратким изложением основных идей.

Типы Table View

Существует два вида Table View:
  • cell-based — каждая ячейка таблицы представлена подклассом NSCell. В большинстве случаев этого достаточно, но создает трудности при разработке таблиц с комплексными ячейками, т.к. требует создания подкласса NSCell. В версиях Mac OS X 10.6 и более ранних существовал только этот тип таблиц.
  • view-based — каждая ячейка представляет собой отдельный view. Это позволяет строить комплексные таблицы даже с поддержкой анимации без особых трудностей. Данный тип таблицы появился только в Mac OS X 10.7.

В этой статье мы рассмотрим работу с cell-based table view, так как нам достаточно его функций, решение получится более простым и будет работать не только с новыми версиями OS X.

Передача данных в Table View

Данные в Table View можно передавать двумя способами:
  • С помощью реализация протоколов (интерфейсов) NSTableViewDelegateNSTableViewDataSource, а затем связывать Table View с классом, реализующим эти протоколы посредством Interface Builder. Необходимо привязать Outlets dataSource и delegate. А в этом классе в самом простом случае необходимо реализовать два метода:
    • numberOfRowsInTableView: — возвращает общее количество строк в таблице
    • tableView:objectValueForTableColumn:row: — возвращает элемент для строки, переданной в аргументе row. Номер столбца в этом случае определяется различными способами, используя второй аргумент objectValueForTableColumn.
  • С использованием контроллера и биндингов (Bindings). Эта техника основана на паттерне Model-View-Controller (MVC) [2] и технологии Cocoa Bindings. Про MVC, наверное, смысла рассказывать нет — про него все слышали, а кто не слышал, может ознакомиться по приведенной ссылке в Wiki. На Cocoa Bindings остановимся подробнее.

Cocoa Bindings

Cocoa Bindings появился в версии Mac OS X, начиная с версии 10.3. Основная его цель — сократить объем кода контроллера для связи объектов модели (Model) и представления (View).
Cocoa Bindings основан на двух механизмах:
  • Key-Value Coding (KVC) — механизм для доступа к полям объекта по именам этих полей.
  • Key-Value Observing (KVO) — механизм, позволяющий одним объектам следить за изменениями в других объектах.

Более подробно о Cocoa Bindings можно почитать в [3].

На этом с теорией закончили, можно переходить к практике.

Практика


Открываем Xcode и создаем новый проект «Cocoa Application». Назовем его «Process Monitor». Я использую последнюю на момент публикации версию Xcode (4.2.1), поэтому некоторые шаги могут отличаться.

Создание GUI

Открываем файл MainMenu.xib, в нем выбираем Window. Установим для него размеры 480 на 500. Это делается в Size Inspector (значок линейки в правом сайдбаре). Там же установим минимальные размеры — чекбокс и нужные размеры.

Добавляем в окно элемент Table View из Object Library (значок куба в правом нижнем сайдбаре) (поиск по слову «table»). По умолчанию создается таблица cell-based. В этом можно убедиться, если открыть Attributes Inspector (в правом сайдбаре, слева от линейки). Изменяем размеры Table View так, чтобы она занимала все окно. Затем открываем Size inspector и на картинке Autosizing выбираем все элементы — это необходимо, чтобы Table View растягивалась вместе с окном.

Теперь нам необходимо добавить столбцы в таблицу. Будем отображать три столбца — PID процесса, иконку и название процесса. По умолчанию в таблицу добавлено два столбца. Нужно изменить это число на 3. Для этого выбираем таблицу (Table View) кликом в окне. Тут нужно сделать небольшое отступление. Дело в том, что объекты окна вложены друг в друга и выделить нужный совсем не просто. Есть два варианта: это либо выбор нужного элемента в иерархии объектов в окне Objects (думаю, он появился в Xcode 4, раньше я его не видел), либо Command+Control+Shift Click на окне с выбором нужного объекта. После выбора таблицы переходим в Attributes Inspector и устанавливаем значение Columns в 3.

Теперь присвоим имена столбцам. Это делается двойным кликом по заголовку столбца. Имя первого столбца — PID, второй оставим пустым, третий — Process Name.

Изменяем размер столбцов таблицы. Для этого выделяем столбец и в Size Inspector устанавливаем его размеры. Для первого (PID): 60, для второго (Icon): 40, для третьего (Process Name) — все оставшееся место.

Заменим тип второго столбца таблицы с текстового на изображение. Для этого в Object Library найдем Image Cell (поиск по «image») и перетащим ее на второй столбец.

После всех этих действий можно нажать кнопку Run и увидеть такое окно:


Добавление контроллера и биндингов

Теперь добавим controller, который будет взаимодействовать с таблицей. Самый подходящий тип котроллера для таблицы это NSArrayController. Найдем его в Object Library (поиск по «array») и перетащим в список Objects.

Установим биндинги для таблицы. Для этого выделим Table View и перейдем в Bindings Inspector (предпоследняя вкладка в правом сайдбаре). Там в разделе Table Content выберем Content и установим Bind to: Array Controller с Controller Key: arrangedObjects.

Теперь воспользуемся техникой KVC и укажем какие данные должен отображать каждый столбец. Для этого мы по очереди выделяем каждый столбец (Table Column) и в Bindings Inspector устанавливаем Value в Bind to: Array Controller с Controller Key: arrangedObjects. А для каждого столбца значения Model Key Path соответственно: processIdentifier, icon, localizedName. Почему установлены такие значения, будет описано ниже.

Так мы связали таблицу с контроллером. Осталось связать контроллер с данными.

Первым делом определим эти самые данные. Откроем файл AppDelegate.h и определим свойство contents типа NSArray:
 @property (retain) NSArray* contents; 

В файле AppDelegate.m напишем:
@synthesize contents; 

Теперь вернемся к MainMenu.xib и скажем контроллеру какие данные использовать. Идем в Bindings Inspector и в разделе Controller Content устанавливаем Content Array в Bind to: App Delegate, а Model Key Path: contents.
На этом установка биндингов окончена, нам осталось только заполнить массив contents в AppDelegate нужными данными.

Получение списка запущенных процессов

Существует несколько способов получения списка всех запущенных процессов. Об этом я как-нибудь напишу в отдельной статье. А сейчас рассмотрим самый простой и очевидный способ — с использованием Cocoa. Он получает не полный список процессов, но пусть нас это пока не волнует.
Определим в AppDelegate метод updateProcessList:
- (void)updateProcessList {     NSWorkspace* workspace = [NSWorkspace sharedWorkspace];     self.contents = [NSArray arrayWithArray:[workspace runningApplications]]; } 

Сделаем этот метод первым в классе, чтобы была возможность вызывать его из других методов без определения в заголовочном файле.
Немного прокомментирую код. В первой строчке мы получаем объект класса NSWorkspace. Объект этого класса существует в программе в единственном экземпляре, своеобразный синглтон, и получается методомsharedWorkspace.
У этого объекта есть метод runningApplications, возвращающий список запущенных приложений в массиве. Все просто. И этот массив мы используем в качестве данных для контроллера.

Каждый элемент этого массива представляет собой объект класса NSRunningApplication. У этого класса есть несколько свойств, среди которых есть processIdentifiericonlocalizedName. Помните, мы устанавливали их для столбцов таблицы? Так вот, благодаря KVC, во время отображения таблицы будут использоваться эти значения из NSRunningApplication.

Теперь добавим вызов созданного метода во время запуска приложения. Для этого нужно добавить строчку:
[self updateProcessList]; 

в метод applicationDidFinishLaunching.

Завершение приложения

В Mac OS X принято что приложение не обязательно должно завершаться после того как закрыто последнее окно. В нашем примере в этом большого смысла нет, поэтому добавим автоматическое завершение. Делается это очень просто — добавлением одного метода в AppDelegate.m:
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {     return YES; } 

Заключение


Теперь наше приложение готово. Если все сделано правильно, то после запуска можно увидеть что-то подобное:

В качестве упражнения, предлагаю читателю реализовать периодическое обновление списка процессов. Для этого можно использовать класс NSTimer и метод scheduledTimerWithTimeInterval этого класса [5].

Итак, в этой статье мы рассмотрели процесс создания простого приложения с использованием Table View. Большая часть действий проводилась в Interface Builder и не требовала программирования вообще. Все что можно было упростить, было упрощено — не были рассмотрены контроллеры окон (NSWindowController), контроллеры представлений (NSViewController), управление памятью и многое другое.

В следующей статье я планирую рассмотреть иерархический список (NSOutlineView) для отображения дерева процессов. И, если кода там наберется достаточно, сделаю репозиторий на github'е.

Всем спасибо за внимание!

Ссылки


  1. Table View Programming Guide (en)
  2. Model–view–controller pattern (en)
  3. Работа Cocoa Bindings (ru)
  4. NSWorkspace Class Reference (en)
  5. NSTask Class Reference (en)


Upd. Набрался кармы и перенес в Mac OS X.
+20
76

комментарии (26)

+2
farcaller,15 января 2012 в 18:26#
Хороший пример. Я думаю, что стоило бы еще расширить его альтернативной версией, без биндингов, дабы показать, как бингинги иногда упрощают работу (впрочем, иногда они ее ой как усложняют).
+1
zerodivisi0n,15 января 2012 в 20:24#

Думал над этим. Решил оставить только в теории, чтобы не усложнять общее изложение и не запутать таких же новичков, как и я.
–4
QtRoS,15 января 2012 в 19:29#
«фреймворки вроде Qt… не обеспечивают нативный Look and Feel»

ЧТО?!
+5
anticyclope,15 января 2012 в 19:53#

— «ЧТО?!»
Джонатан Айв о Qt-приложении для Mac OS X
+1
StyleT,15 января 2012 в 21:04#

поддерживаю, все же интерфейс должен быть написан с использованием нативных средств, за что люблю Mac OS X, так это за единый стиль GUI почти всех приложений
+3
zerodivisi0n,15 января 2012 в 20:18#

Пользуюсь KeePassX, которая как раз написана на Qt. И могу сказать что она ужасна как в плане внешнего вида, так и в плане юзабилити.
+3
ShadowMaster,15 января 2012 в 19:32#
Блог называется «Разработка под Apple iOS», а разработка программы под Mac?
0
zerodivisi0n,15 января 2012 в 20:13#

Глядя на URL блога (http://habrahabr.ru/blogs/macosxdev/136265/), думаю что macosxdev обозначает общее название для двух этих платформ.
0
zerodivisi0n,15 января 2012 в 20:29#

И еще думаю что при разработке под iOS используются те же подходы.
Поэтому статья должна быть интересна и разработчикам под iOS.
+1
zerodivisi0n,15 января 2012 в 21:19#

Ниже muryk написал что это не так.
+1
int80h,15 января 2012 в 20:45#

Изначально блог был как раз про разработку про мак, iPhone SDK тогда еще даже не вышла, потом по не понятной причине его переименовали (и как верно уже заметил заметили, в URL следы остались), а альтернативы не оставили. Тут уместнее другой вопрос — что тут делают посты про iOS?
+1
muryk,15 января 2012 в 21:01#
Пожалуйста, поправьте или перенесите этот топик таким образом, чтобы читатель ясно понимал — все, о чем здесь сказано имеет отношение к программированию только и исключительно под Mac OS X. В iOS используется совершенно другой подход и иные техники работы с таблицами.
+1
muryk,15 января 2012 в 21:02#

Забыл добавить: спасибо! )
+1
zerodivisi0n,15 января 2012 в 21:17#

Вам спасибо! Это моя первая статья на хабре!
+1
zerodivisi0n,15 января 2012 в 21:17#

По-моему что речь идет именно о Mac OS X понятно уже из первого предложения.
Во время публикации этот блог мне показался самым подходящим. Как написал int80h выше этот блог был первоначально предназначен для Mac OS X, потом переименован в iOS. Отсюда и путаница.
Альтернативный вариант — блог Mac OS X, но мне кажется там публикуются статьи не относящиеся к разработке.
0
ulmolot,16 января 2012 в 00:36#

Ну я бы не был столь категоричен, в ios тоже можно использовать KVC & KVO
0
muryk,16 января 2012 в 08:05#

KVC и KVO-то есть. А Cocoa Bindings нет. NSCell, NSArrayController тоже отсутствуют. Не говоря уж о таких критически важных для целевой аудитории таких статей нюансов, как разные имена классов и методов в UIKit и AppKit.

Однако, чего только на свете не бывает. Если научите, как применить KVC и KVO для заполнения данных таблицы без использования делегатов (пусть даже программно, без работы с IB) — буду благодарен.
0
ulmolot,16 января 2012 в 08:53#

На счёт биндингов честно, не знаю, надо попробовать, в своих проектах IB не использую. Но тем не менее, на мой взгляд, некоторые пересечения есть, пусть и не такие большие.

А на счёт заполненеия таблицы без делегата, только если использовать какие нибудь надстройки, можно посмотреть в сторону нимбуса, на сколько я помню, он может принимать массив описания ячеек и создавать тайбл вью.
0
Droy,15 января 2012 в 22:38#
Офигеть! Я больше не верю в случайности!
Я начал изучать Cocoa 2 недели назад и сейчас настал момент изучить Table View для одной цели. 3 дня искал нормальные туториалы на всех возможных ресурсах (developer.apple.com; stack Overflow etc.) Последние 3 дня. И тут на любимом ресурсе такая прекрасная статья.

Извините за текст. Эмоции нахлынули от радости. Надеюсь, правильно меня поймут.
+1
zerodivisi0n,15 января 2012 в 22:49#

Сам примерно сколько же времени изучаю Cocoa. Пытался разобраться с Table View. И вот вчера ночью придумалось решение. Сегодня решил об этом написать.
0
Droy,15 января 2012 в 23:08#

Спасибо вам огромное! И статья именно о том, что я искал последние 3 дня. Ни об объектах или работы с фреймворками, а именно Table View. Такое совпадение. Спасибо!
0
Droy,15 января 2012 в 23:23#

Обязательно пишите еще. Буду с удовольствием читать!
–2
den26,15 января 2012 в 23:51#
А причем тут Apple iOS? Ведь статья касается только Mac OS.
0
ulmolot,16 января 2012 в 00:32#
Хотелось бы увидеть, как реализовать тоже самое, но без использования IB.
+2
iStyx,16 января 2012 в 01:24#
Для этого можно использовать класс NSTimer и метод scheduledTimerWithTimeInterval этого класса [5].

Но не нужно. Для данного примера лучше использовать NSWorkspaceDidLaunchApplicationNotification иNSWorkspaceDidTerminateApplicationNotification [4].

Добавляем в applicationDidFinishLaunching:
 NSNotificationCenter *wsc = [[NSWorkspace sharedWorkspace] notificationCenter]; [wsc addObserver:self selector:@selector(updateProcessList:) name:NSWorkspaceDidLaunchApplicationNotification object:nil]; [wsc addObserver:self selector:@selector(updateProcessList:) name:NSWorkspaceDidTerminateApplicationNotification object:nil]; 


Изменяем прототип метода (и сам метод) updateProcessList:
 - (void) updateProcessList:(NSNotification*)notification; 


Меняем вызов этого метода в applicationDidFinishLaunching на:
 [self updateProcessList:nil]; 


Не забываем отписаться от уведомлений в applicationWillTerminate:
 NSNotificationCenter *wsc = [[NSWorkspace sharedWorkspace] notificationCenter]; [wsc removeObserver:self name:NSWorkspaceDidLaunchApplicationNotification object:nil]; [wsc removeObserver:self name:NSWorkspaceDidTerminateApplicationNotification object:nil]; 

No comments:

Post a Comment