Работа Cocoa Bindings
Программирование и работа всего пользовательского интерфейса в Mac OS X основана на модели (паттерне) Model-View-Controller (MVC). Этот паттерн четко разделяет данные, с которыми идет работа (Model), визуальное представление, служащее для показа и/или редактирования этих данных (View) и связующих их код (Controller).
Рис 1. Схема паттерна MVC.
Все взаимодействие между моделью и ее визуальным представлением идет только через Controller. При этом можно использовать сразу несколько различных компонент View (одни и те же данные могут быть представлены как текстовые поля, набор слайдер, диаграмма, график и т.п) и Controller обеспечивает их синхронизацию.
Рис 2. Работа с несколькими View
При этом Cocoa предоставляет большой набор готовых классов, служащих как для показа (редактирования) данных (объекты, унаследованные от NSView) и большой набор классов, облегчающих написание модели. Типичная задача Controller‘а — передача значения от модели к ее визуальному представлению (представлениям) и при изменении значения в визуальном представление — передача изменений в модель (и остальные визуальные представления). И неудивительно, что основная часть кода Controller‘а очень однообразна и проста, хотя и требует написания кода. Начиная с Mac OS X 10.3 Panther в Cocoa входит очень удобный и мощный механизм, получивший название Cocoa Bindings (иногда используется название Controller Layer). Его использование позволяет заметно сократить объект кода, переложив основные функции Controller‘а на предоставленные готовые объекты.
В основе Cocoa Bindings лежат два понятия Key-Value Coding и Key-Value Observing и основанные на них готовые контроллеры — NSController, NSObjectController, NSArrayController, NSUserDefaultsController, NSTreeController.
Key-Value Coding (KVC)
Key-Value Coding это протокол, поддерживаемый практически всеми объектами Cocoa. Основная его задача — дать унифицированный механизм доступа к полям объектов по именам этих полей. Пусть у нас есть следующий класс:
@interface Employee : NSObject
{
NSString * name;
NSString * department;
float salary;
Employee * boss;
}
— (NSString *) name;
— (NSString *) department;
— (float) salary;
— (Employee *) boss;
— (void) setName: (NSString *) newName;
— (void) setDepartment: (NSString *) newDepartment;
— (void) setSalary: (float) newSalary;
— (void) setBoss: (Employee *) newBoss;
— (void) dealloc;
@end
Тогда используя KVC можно получить имя при помощи следующего вызова:
id name = [person valueForKey: @"name"];
Этот вызов приведет к посылке сообщения name, в результате чего будет получен указатель на строку, который и будет возвращен. Мы также можем использовать метод valueForKey: для получения значения поля salary, но результат мы получим не в виде числа типа float, а в виде указателя на объект класса NSNumber. Это связано с тем, что для обеспечения единообразного доступа ко всем полям, значения должны возвращаться как указатели на объект, унаследованный от NSObject.
Аналогично можно использовать метод setValue:forKey: для записи значения в поле:
[person setValue: [NSNumber numberWithFloat: 100000.0f] forKey: @"salary"];
[person setValue: @"IT" forKey: @"department"];
Таким образом KVC предоставляет унифицированный способ доступа к полям произвольных объектов по именам этих полей на основе следующих двух методов:
— (id) valueForKey: (NSString *) key;
— (void) setValue: (id) value forKey: (NSString *) key;
При этом нет необходимости самому реализовывать эти методы для каждого класса — есть стандартная реализация, используя информацию о классе объекта для получения доступа к его полям. Так если нужно прочесть значение поля boss, то сперва будет проверено наличие метода чтения boss или boss_, а при их отсутствии будет проверено наличие поля с именем boss или boss_. Если этого поля не будет найдено, то произойдет исключение (exception).
Для записи поля boss сперва проверяется наличие методов setBoss или setBoss_, а при неудаче — проверено наличие поля с именем boss или boss_. При неудаче выбрасывается исключение.
Таким образом для нормальной поддержки KVC достаточно просто для каждого поля дать методы доступа (accessor‘ы для чтения и записи) следуя указанной выше схеме наименования. Хотя этого можно и не делать (тогда просто будет прямой доступ к полю), это не желательно, так наличие этих методов понадобится нам далее для Key-Value Observing.
Все преобразования типов (например между float и NSNumber *) происходят автоматически.
Используемый в Mac OS X 10.5 Leopard язык Objective-C 2.0 поддерживает автоматическое создание accessor‘ов. Однако KVC дает больше, чем просто единообразный доступ к полям данного объекта. Довольно часто поле объекта является указателем на другой объект, поля которого также могут быть указателями и т.д. KVC позволяет сразу обратиться к полю сквозь всю цепочку названий промежуточных полей — достаточно их соединить между собой при помощи символа ”.”.
Так для получения имени начальника можно использовать следующий вызов:
id name = [person valueForKey: @"boss.name"];
Чтобы узнать имя начальника начальника можно использовать имя (путь) “boss.boss.name”. Кроме того Objective-C 2.0 позволяет использовать гораздо более привычныую форму доступа к полям по имени:
id name = person.boss.name;
person.department = @"Marketing";
Естественно, что все эти вызовы переводятся в стандартные вызовы valueForKey: и setValue:forKey:, но это автоматически делается компилятором, а разработчики получают возможность использовать более привычную форму работы с полями.
Для класса NSDictionary (NSMutableDictionary) используется расширенная реализация KVC, позволяющая использовать ключи в словаре в качестве имен полей (если эти ключи являются строками).
Key-Value Observing
Key-Value Observing (KVO) — это механизм, позволяющий одним объектам отслеживать изменения полей других объектов. Поддержка KVO встроена в базовый класс NSObject и для ее использования служат следующие два метода:
— (void) addObserver: (id) observer forKeyPath: (NSString *) keyPath
options: (NSKeyValueObservingOptions) options
context: (void *) context;
— (void) removeObserver: (NSObject *) observer forKeyPath: (NSString *) keyPath;
Посылка первого из этих сообщений регистрирует переданный объект observer как наблюдателя за полем с именем keyPath. При этом в параметре options передаются дополнительные опции для наблюдения (просто набор битов) и context — это просто некоторый указатель, передаваемый наблюдателю. При помощи сообщения можно прекратить наблюдения за заданным полем.
Фактически KVO реализовано таким образом, что не требует почти никаких усилий от программиста — когда поступает запрос на наблюдения за данным объектом, то у данного объекта (именного у него, а не у всего класса) происходит подмена setter‘а на метод, который кроме вызова этого setter‘а также пошлет сообщение observeValueForKeyPath:ofObject:change:context: наблюдателю.
— (void) observeValueForKeyPath: (NSString *) keyPath ofObject: (id) object
change: (NSDictionary *) change context: (void *) context;
Параметры object и keyPath содержат адрес наблюдаемого объекта и имя поля, значение которого изменилось. Поле context содержит переданный при запросе на наблюдение контекст, а словарь change содержит дополнительную информацию о происшедшем изменении.
При этом важно, что все что нужно для того, чтобы за заданным полем объекта можно было наблюдать — это наличие setter‘а для этого поля. Аналог Cocoa Bindings есть и в M$ WPF (правда под названием Data Binding, вообще в WPF моожно найти массу заимствований из Cocoa), но там для того, чтобы за полем можно было наблюдать необходимо добавление специального кода в setter. KVO этого не требует — все происходит полностью автоматически.
Key-Value Binding (Cocoa Bindings)
За счет использования KVC и KVO можно легко реализовать несколько почти универсальных Controller‘ов. Ведь фактически задача каждого Controller‘а это отслеживание изменений в модели и передача новых значений всем используемым View и отслеживание изменения значнеий в View и передача измененных значений всем остальным.
За счет использования Key-Value Observing можно легко реализовать отслеживание изменения в полях модели, а использование Key-Value Coding предоставляет универсальный способ доступа к значениям (отслеживание изменений значений в визуальной компоненте намного проще — обычно это и так ведет к посылке сообщения).
Таким образом можно просто связать поля модели с визуальными компонентами интерфейса так, что изменения в одном месте будет автоматически передваться во все остальные места. Важным моментом является то, что все эти связи можно задать прямо в Interface Builder’е и они не требуют написания вообще никакого дополнительного кода — всю работу делают специальные объекты-контроллеры.
Далее мы рассмотрим несколько простых примеров, иллюстрирующих использование Cocoa Bindings. Запустим XCode и создадим новый проект типа Cocoa Application.
Рис 3. Создаем новый проект
Далее добавим новый класс (Модель) в наш проект при помощи правого клика мышью на элемент Classes в окне проекта и выбрать Add|New File … (рис. 4).
Рис 4. Добавляем новый класс.
В качестве добавляемого класса выберем подкласс NSObject и зададим в качестве имени класса Model (рис 5 и 6).
Рис. 5. Задание типа добавляемого класса.
Рис 6. Задание имени добавляемого класса.
Далее откроем созданные файлы Model.h и Model.m и заполним описание и реализацию класса. Поскольку в данном примере Model будет просто хранить числовое значение, отображаемое и изменяемое при помощи нескольких визуальных компонент, то нам хватит одной instance-переменной, содержащей значение типа NSNumber * и соответствующих accessor-методов.
#import <Cocoa/Cocoa.h>
@interface Model : NSObject
{
IBOutlet NSNumber * value;
}
@end
#import "Model.h"
@implementation Model
— (void) dealloc
{
[value release];
[super dealloc];
}
@end
Вся остальная работа будет проводиться в Interface Builder‘е поэтому запустим его двойным щелчком мыши по файлу Main Menu.nib в окне проекта.
Далее выполним операцию синхронизации с XCode — при этом вся информация о созданных в XCode классах станет доступна из Interface Builder‘а. Для этого служит команда меню File|Synchronize.
Рис 7. Синхронизация с XCode.
Далее перейдем в окно Library и перетащим объект NSObject в окно Main Menu.nib. Для быстрого поиска нужного объекта (компоненты) в окне Library можно воспользоваться полем поиска в нижней части окна.
Рис 8. Выбор NSObject’а в окне компонент.
Рис 9. Перетаскивание объекта в окно Main Menu.nib.
Далее выделим перетащенный объект и в окне свойств выберем закладку Object Identity (рис. 10), в поле Class выберем Model.
Рис 10. Задание класса для объекта.
Обратите внимание, что после этого в разделе Class Outlets появится поле value класса Model.
Рис 11. Свойства модели в Interface Builder’е.
Далее приступим к работе с окном. Сначала изменим его заголовок на “Cocoa Bindings 1”. Затем добавим поле NSTextField для редактирования числового значения. Чтобы быстро найти эту компоненту в окне Library достаточно набрать “text” в поле поиска, после чего останутся только те компоненты, которые связаны с работой с текстом.
Рис 12. Поиск компоненты NSTextField.
После этого просто перетащим компоненту на окно и настроим ее свойства.
Рис 13. Добавляем текстовое поле в окно.
Далее добавляем объекты NSStepper справа от поля ввода и NSSlider внизу. Также сбоку поместим кнопку “Quit” и отделяющую ее вертикальную линию.
Рис 14. Окончательный вид окна.
Далее привяжем кнопку к сообщению terminate: у объекта First Responder, для этого достаточно удерживая нажатой клавишу Ctrl “протянуть” соединение от кнопки “Quit” к объекту First Responder. После этого в появившемся окне из всего списка методов выбираем terminate:.
Рис 15. Привязка кнопки “Quit”.
После этого все, что нам осталось сделать это подключить (bind) компоненты, позволяющие показывать и изменять значение к полю value нашей модели (объекту Model в окне Main Menu.nib).
Рис 16. Настройка привязывания для текстового поля.
Для этого для каждого из этих объектов выделим его и в окне свойств выберем закладку Bindings. Далее в разделе Value “включим” поле “Bind To” и выберем какому объекту мы хотим “привязать” — это объект Model.
Рис 17. Параметры привязывания для текстового поля.
После этого сохраним получившийся nib-файл. Обратите внимание, что при попытке сохранения файла мы получаем окно со свойствами nib-файла, в котором можно выбрать минимальную версию Mac OS X, на которую рассчитано приложение, и для выбранной версии обозначены все возможные несовместимости. Выберем в качестве минимальной версии Mac OS X 10.4.
Рис 18. Окно со свойствами nib-файлами и предупреждениями о возможных непереносимостях.
После этого вернемся обратно в XCode и запустим наш пример.
Рис 19. Работающее приложение.
Получившееся приложение позволяет отображать и изменять одно и то же значение при помощи трех разных визуальных компонент и за счет использования Cocoa Bindings все значения автоматически синхронизируются, каким бы способом мы его не меняли.
Поскольку все стандартные компоненты поддерживают KVC, то можно устанавливать Bindings на любые свойства стандартных компонент. В следующем примере мы привяжем основные свойства компоненты NSSlider к набору отдельных компонент и получим возможность на ходу изменять параметры для данной компоненты — без написания какого-либо кода вообще.
Создадим новый проект типа Cocoa Application (рис 20).
Рис 20. Создаеми новый проект.
Далее сразу же перейдем к редактированию nib-файла. Первое, что нам нужно будет сделать — это перенести на окно собственно сам NSSlider, свойства которого мы будем “привязывать” к другим компонентам. Достаточно в поле поиска окна Library набрать “sl” как мы сразу получим все основные компоненты связанные со слайдерами.
Рис 21. Выбор слайдеров.
Аналогичным образом перетащим остальные компоненты для получения окна, показанного на рисунке 22.
Рис 22. Окончательный вид окна.
Компоненты, расположенные сбоку от слайдера мы “подключим” к основным параметрам слайдера и в результате получим возможность управления слайдером прямо на ходу. Кнопку “Quit”, как и ранее, подключим к методу terminate: объекта First Responder.
Рис 23. Подключение кнопки “Quit”.
При помощи Ctrl-клика мышью по кнопке “Quit” мы можем вызвать панель со всеми outlet‘ми и action‘ми этой кнопки. Обратите внимание, что их назначение можно менять прямо из этого окна — достаточно “протащить” связь от окружности, соответствующей задаваемому элементу к назначению.
Рис 24. Настройка outlet‘ов и action‘ов для кнопки “Quit”.
Из палитры компонент (окна Library) перетащим объект NSObjectController в окно MainMenu.nib — это и будет наш контроллер, осуществляющий всю координацию изменений.
Следующим шагом будет подключение контроллера — для этого используем Ctrl-drag от контроллера в окне MainMenu.nib к слайдеру и в появившемся списке выберем contents.
Рис 25. Подключение контроллера к слайдеру.
Далее приступим к непосредственно к настройке связей для свойств слайдера. Сперва выделим текстовое поле с меткой “Number Of Ticks” и в окне свойств откроем закладку “Bindings”. Далее выставим связи как показано на рисунке 24 — само значение привязывается к свойству selection объекта ObjectController по ключу (пути) numberOfTickMarks.
Рис 26. Настройка связей для поля с меткой “Number Of Ticks:”.
Свойство selection для ObjectController’а соответсвует “контроллируемому” объекту, т.е. в нашем случае слайдеру.
Аналогичным образом “привяжем” к тем же параметрам объект NSStepper — он также будет использоваться для изменения числа отметок на слайдере. Поля с метками “Min Value:” и “Max Value:” привяжем к путям minValue и maxValue соответственно. Поле с меткой “Value:” привяжем к пути floatValue, а checkbox с меткой “Ticks Only:” привяжем к ключу allowsTickMarkValuesOnly.
Рис 27. Настройка привязывания для свойств слайдера.
Далее сохраним nib-файл и вернемся в XCode, соберем и запустим приложение.
Рис 28. Работающее приложение.
Убедимся, что все компоненты корректно изменяют свойства слайдера и изменение изначения слайдера синхронизировано с полем “Value:”. Таким образом мы автоматически получили полную синхронизацию свойств слайдера со значения ряда других компонент (в том числе и для случая, когда одному свойству соответствует сразу несколько компонент) ни написав при этом ни стройки кода — все было сделано на уровне ресурсов.
Следующий пример будет использовать еще один из стандартных контроллеров Cocoa — NSArrayController. Этот контроллер позволяет работать не с одним объектом, а сразу с массивом объектов заданного класса, поддерживая как создание/удаление/редактирование объектов в таблице, так и в отдельном окне (подход -Master-Detail View).
Создадим новый проект и сразу добавим в него новый класс Person (аналогично тому как это делалось в первом примере).
Рис 29. Создание класса Person.
Этот класс будет представлять из себя описание человека и содержать основные атрибуты — имя и фамилия (firstName и lastName), номер телефона (phone), e-mail (email), дату рождения (birthDate) и фотографию (photo).
Для использования Cocoa Bindings сделаем accessor‘ы ко всем этим атрибутам. Ниже приводится файл Person.h.
#import <Cocoa/Cocoa.h>
@interface Person : NSObject
{
NSString * firstName;
NSString * lastName;
NSString * phone;
NSString * email;
NSDate * birthDate;
NSImage * photo;
}
@property (retain) NSString * firstName;
@property (retain) NSString * lastName;
@property (retain) NSString * phone;
@property (retain) NSString * email;
@property (retain) NSDate * birthDate;
@property (retain) NSImage * photo;
@end
На следующем листинге приводится файл Person.m.
#import "Person.h"
@implementation Person
— (void) dealloc
{
[firstName autorelease];
[lastName autorelease];
[phone autorelease];
[birthDate autorelease];
[photo autorelease];
[super dealloc];
}
@synthesize firstName;
@synthesize lastName;
@synthesize phone;
@synthesize birthDate;
@synthesize email;
@synthesize photo;
@end
Далее откомпилируем файлы нашего проекта, перейдем в Interface Builder и выполним команду Synchronize.
Для начала перенесем объект NSTableView (рис 30) в наше окно.
Рис 30. Выбор компонента NSTableView.
После этого настроим свойства таблицы — нам нужно создать таблицу с двумя столбцами с именами “First Name” и “Last Name”.
Рис 31. Настройка таблицы из главного окна.
Перенесем в окно MainMenu.nib объект NSArrayController и приступим к его настройке. Для этого выделим перетащенный контроллер и в окне свойств в первой закладке зададим в поле CLass Name имя нашего класса Person, массивом экземпляров данного класса и будет управлять контроллер.
Рис 32. Свойства контроллера.
После этого добавим в таблицу Key все атрибуты класса Person. Далее добавим в окно три кнопки — Add …, Remove и Info ….
Рис 33. Итоговый вид главного окна.
Далее подключим первые две из этих кнопок к методам контроллера add: и remove:.
Рис 34. Подключение кнопки “Add …”.
Далее несколькими щелчками мыши выделим первый столбец таблицы и в окне свойств откроем для этого столбца закладку Bindings — Cocoa позволяет задавать привязку отдельных столбцов таблицы. Необходимо подключить весь столбец к атрибуту контроллера arrangedObjects (этот атрибут соответствует массиву управляемых контроллером объектов) и в качестве ключа (пути) выберем из списка ключей firstName.
Рис 35. Задание привязки для первого столбца таблицы.
Аналогично второй стобец таблицы подключим к ключу lastName.
Рис 36. Настройка столбца таблицы.
Далее “вытащим” новое окно из палитры компонент (Library) — это будет detail view, в котором можно будет редактировать все атрибуты объекта Person, выделенного в Master View.
Рис 37. Окончательный вид detail-окна.
Разместим в этом окне компоненты для показано на рис 35 (большая компонента справа — это объект класса NSImageView).
Далее точно также “привяжем” поля этого окна к контроллеру, однако в качестве значения Controller Path будем использовать selection, соответствующий выделенной строке в таблице (т.е. текущему объекту Person).
Рис 38. Настройка привязывания для поля First Name detail-окна.
Кнопку Info … подключим к метода detail-окна makeKeyAndOrderFron:.
Рис 39. Подключение кнопки “Info …”.
Подключим свойство Caption detail-окна к атрибуту lastName ключа selectionконтроллера. Далее сохраним nib-файл и соберем проект. После его запуска мы сможем добавлять новые строки (объекты) в таблицу при помощи кнопки Add … и удалять при помощи кнопки Remove.
Кнопка Info … делает detail-окно видимым и активным. Обратите внимание, что редактировать объекты можно как непосредственно в самой таблице, так и в detail-окне. Для добавления фотография выбранному объекту следует использовать механизм Drag-and-Drop — картинка просто перетаскивается в поле для картинке на detail-view.
Рис 40. Редактирование одной записи.
Мы получили почти готовое приложение для работы со списком людей (в нем пока еще нет сохранения и чтения списков из файлов и поиска) с достаточно большой функциональностью. И весь код, который нужно было для этого написать это просто класс, представляющий один контакт и для него описать accessor‘ы — все остальное (включая и создание этих самых accessor‘ов) было получено нами автоматически. При этом всю работу, связанную с возмоностью редактирвоания объектов (причем сразу в разных местах), взял на себя готовый объект-контроллер.
Весь исходный код к этой статье можно скачать по следующим ссылкам:
Боресков Алексей
steps3d.narod.ru
Комментарии
http://www.mikeash.com/?page=pyblog/key-value-observing-done-right.html — тут интересная статья о том, как улучшить механизм KVM. Он сделал wrapper такой, где можно указать свой Selector когда подписываешься на изменения переменных. Имхо интересно, зря apple не пошла в таком направлении.
что-то давно новых постов небыло…. очень жалко… у expoon будук коментарии по этому поводу???
Конечно, будут, как только у меня появится свободное время.
жду недождусь :)))))
Замечательное описание KVC…. термины: атрибут, ссылка to-one, ссылка to-many, пути, операторы — это все так несущественно :)))
Чувак, пожалуйста, продолжай писать. Ты — единственный источник русского мака.
Кхм, довольно сложно интерфейс в Mac OS реализуется. Вопрос к автору — как по-вашему, стОит ли писать несложное приложение на питоне под мак, используя wxPython либо Tkinter? Есть мнение, что на них невозможно будет писать сразу же, как только Apple перестанет поддерживать Carbon. Ну и второй вопрос — посоветуйте толковые книги по созданию интерфейсов на pyObjC. Желательно на русском, хотя можно и на ангельском. Спасибо =)
Спасибо за отличный блог! Хочется новых постов!)
Guys, how do we know that “floatValue” it is slider position? (not “value” or “sliderValue”) Thanks in advance. P.S. You can reply in Russian :)
Просто большинство контролов имеют значение, которое можно получать в рапзных фоматах. Для этого и существуют методы типа floatValue, кеоторые возвращают значение для контрола заданного типа Для слайдера значением является его текущее значение, которые мы хотим получить в виде float-числа Точно также можно полю для ввода числа послать этого же сообщение и получить перевод строки в число
А как же мне знать при биндинге что минимальное значение в слайдера пишеться именно: “minValue” а не по-другому?
Спасибо.
Всем привет, Помогите с созданием простого Имидж Эдитора.
Интересует вопрос:
Как отображать картинки (PNG, GIF, BMP) предварительно загрузив через главное меню приложения? Нужно загружать и редагировать картинки (перетаскивание, изменение Zorder, растягивание и т. д.)
Какие контролы/фреймворки при этом использовать?
Как отображать все загруженый картинке на форме?
Спасибо.
Всем привет, Помогите с созданием простого Имидж Эдитора.
Интересует вопрос:
Как отображать картинки (PNG, GIF, BMP) предварительно загрузив через главное меню приложения? Нужно загружать и редагировать картинки (перетаскивание, изменение Zorder, растягивание и т. д.)
Какие контролы/фреймворки при этом использовать?
Как отображать все загруженый картинке на форме?
Спасибо.
Есть несколько вариантов. Старый — это компоненты NSImageView/NSImageWell — у них есть метод setImage: с помощью кеоторого можно из программы задать уже загруженную картинку (NSImage *). Для Леопарда есть еще IKImageView — это класс из фреймворка ImageKit, и он поддерживает кучу всяких полезных вещей, вроде набора базовых фильтров, инстпектора и т.п. Лучше всего использовать именно его
Все отображение для всех этих контрролов заключается в вызове setImage: Ну и возможно придется ручками поресайзить компонент, хотя вроде это при норамльных настройках делается автоматически Если вопросы совместимости с 10.4 Вам не важны, то использйте IKImageView Наверное все-таки сделаю статейку по работе с ImageKit
steps3D, спасибо за ответ.
Склоняюсь больше к IKImageView, но этот контрол для “сингл имидж манипулейшена” как пишет Apple:
The IKImageView class provides an efficient way to display images in a view while at the same time supporting a number of image editing operations such as rotating, zooming, and cropping. It supports drag and drop, so that the user can drag an image to the view. If possible, image rendering uses hardware acceleration to achieve optimal performance. The IKImageView class is implemented as a subclass of NSView. Similar to NSImageView, the IKImageView class is used to display a single image.
Мне же нужно чтобы я добавил пару картинок и мог их передвигать, растягивать и тому подобное. Я не очень хочу опускаться к Quartz и Core Image. Потому и ищу простейший способ реализации задуманного.
Что посоветуете? Спасибо.
Я бы не советовал использовать IKImageView. Причины: 1) Он заточен для работы исключительно с одним изображением 2) Плохо документирован, например многие не знают как засетить его обьектом NSImage, понапридумывали разных конверторов, а все потому что в доке не описан метод setImage, и в интерфейсее тоже потому его надо звать вот так [outletImageView performSelector:@selector(setImage:) withObject: image]; 3) В нем включена анимация по умолчанию потому, например если он у тебя ресайзится и ты хочешь отресайзить изображение в нем, анимацию нужно отключать вручную иначе будет прыгающая картинка.
Вообщем с кастомизацией в этом обьекте не очень.
Лучше используй обычный NSView. А рисование делай стандартными средствами http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Introduction/Introduction.html
тут есть два момента: 1) Настройка качества интерполяции [theContext setImageInterpolation:NSImageInterpolationHigh]; 2) Кеширование в NSImage, которое нужно отключать [image setCacheMode:NSImageCacheNever];
Удачи!
Для работы с набором изображений в ImageKit есть очень хороший контрол IKImageBrowserView И лучше не использовать NSView и делать рисование руками — гораздо проще поместить несоколько NSImageView/IKImageView либо в обычный NSView, либо в NSScrollView Никакого рисования самому делать не надо — это сделают используемые компоненты ujhfplj kexit
1)IKImageBrowserView хороший контрол для выбора и редактирования порядка элементов, но не более. Собственно тоже специализированный контрол. Очень облегчил мне жизнь :) 2) В NSImageView отрисовка делается в наихудшим режимом интерполяции потому фотки смотрятся еще более менее, а вот графика, особенно чернобелая типа манга или комикс при масштабировании превращаются в кашу. 3) IKImageView как я уже писал очень неоднозначный контрол и очень тяжелый, потому не вижу смысла юзать его для вывода картинки, он хорошо подходит только если его ф-ционал вписывается в ваше приложение. При работе с ним желательно хорошо разбираться в Core Animation и Core Image так как там этого добра много. Особенно удобно работать в нем с различными фильтрами. 4) NSView самый простой контрол, потому в с ним и проще работать, конечно нужно написать немного кода. Но если вы пишете редактор то все равно рано или поздно прийдете к необходимости что-то писать. Собственно для отрисовки нужно перекрыть всего один метод drawRect. Отрисовка имиджа очень проста например методом NSImage — (void)drawInRect:(NSRect)dstRect fromRect:(NSRect)srcRect operation:(NSCompositingOperation)op fraction:(CGFloat)delta
Кроме того это наименее затратный способ в смысле производительности по сравнению с NSImageView и IKImageView
Отличный пост, спасибо! Приятно было все изученное на английском подтвердить прочитанным на русском ;)
Хотелось бы услышать мнение про delegate и notification, когда лучше использовать KVO, а когда пулять Notification или setDelegate? Когда, например, из Контролла мы просим класс Downloader скачать файл, то как оповестить Controller о том, что файл скачался и находится в таком-то месте? Точнее, как это сделать правильно. Например, я делаю сейчас так:
@implementation Downloader …
(void) connectionDidFinishLoading:(NSURLConnection *)connection { [self callBackWithData: receivedData]; }
(void) callBackWithData: (NSString *) getData { receivedData = getData; [fDelegate performSelectorOnMainThread: @selector(portCheckerDidFinishProbing:) withObject: self waitUntilDone: NO]; // вот тут delegate делаю в Controller в метод portCheckerDidFinishProbing:
}
…
Как правильней тут быть? Стоит ли здесь использовать KVO, в который запихивать URL и полученную информацию?
Я думаю, что в этом случае лучше делегат или notification, здесь все-таки не изменение переменной, котрое надо привязывать к интерфесу, а скорее редкое событие, которе надо отработать (Хотя KVO идет через механизм notication’ов)
А если у нас есть NSMutableArray, в который я пишу path всех измененных файлов на диске в папке /Users/username, то тогда лучше KVO использовать для передачи измененного NSMutableArray?
Алексей, два дня мучался и разбирался с delegate (обновив Xcode до версии 3.2 с выходом Snow Leopard) и все не мог понять, откуда берется этот notice:
http://www.pupsor.com/wp-content/uploads/uploader/delegate.png
*на случай, если картинка не прикрепится, прикладываю кусок класса @implementation Car
(IBAction) createCar: (id)sender {
NSLog(@”I’m Car class. Keep working…”);
sleep(5); //хотел показать реалистичность задачи, что машина не делается по веновею волшебной палочки;)
test = @”BMW 777xi”;
if([[NSApp delegate] respondsToSelector:@selector(carDidFinish:)]) { // check responds
} // carDidFinish в AppController находится
}
@end
и вот, никогда не видя таких нотифаев в Xcode, я думал, что мой код является неправильным (warning выдает). То есть Xcode 3.2 говорит мне No “-carDidFinish:” method found. Хотя respondsToSelector: отвечал мне, все нормально, пользуйте наздоровье, вижу carDidFinish. Причем, все работает и успешно делегейтится, но осадок-то в душе лежит. Я перерыл много документации, перечитал все, нашел кучу примеров и у них тот же самый warning ;) Киньте в меня камень и скажите, что Xcode просто не умеет видеть наперед, что в него засунут в [NSApp delegate] и в связи с этим просто предупреждает, что возможно ожидает косяк, правильно я понимаю?
Никаких косяков нет, все в порядке Просто [NSApp delegate] возвращает объект, т.е. id И это все, что про него значает компилятор На самом деле это объект класса Car (и поэтому он откликается на respondsToSelector), но компилятиор про это ничего не знает Можно либо просто игнорировать или сделать так Car * del = [NSApp delegate]; [car carDisFinish: test];
В этом слкчае ворнингов не должно быть
Всю жизнь программил под Вин, но вот понадобилось программить под МАС Х. И вот я чего не могу понять: 1. Все сделал как описано в примере и не могу понять как мне после добавления эл-тов в таблицу получить доступ к NSArrayController в другой части программы? Ведь они там храняться (в Array Controller)? 2. У меня при нажатии кнопки “Add” в таблицу добавляются пустые строки, потом надо выделить эту добавленную пустую строку и уже отредактировать нажатием “Info”. Как-то топорно получается. Как переделать так чтобы при нажатии “Add” вылазил еще один диалог (аналогичный “Info”) и уже введенная в нем инфа добавлялась по нажатию кнопки на нем? Мне кажется это будет более понятный интерфейс… Очень буду признателен за ответы.
Форма комментирования для «Работа Cocoa Bindings»