Знакомство с Interface Builder. Связи между объектами.
Процесс создания любого приложения можно условно разделить на три этапа: создание интерфейса, непосредственное написание кода и отладка. В первой части своих статей я хочу познакомить вас с Interface Builder (далее просто IB) — средством для визуального создания и тестирования интерфейсов, входящей в состав SDK разработчика под Mac OS, на примере разработки интерфейса для iPhone. Способ создания интерфейса программ для Mac OS X сильно не отличается от приведенных ниже принципов, поэтому данное руководство можно использовать для разработки интерфейсов для “большой” Mac OS с некоторыми различиями, о которых я упомяну, когда придет время.
Создание интерфейса с помощью Interface Builder обычно включает в себя:
- создание nib файла, который представляет собой не просто хранилище интерфейса программы, но и хранит связи между объектами, а также инициализирует многие из них без дополнительного кода
- создание нового окна приложения (а также меню для настольной Mac OS)
- добавление элементов интерфейса в окно приложения с помощью встроенной библиотеки и настройка их атрибутов
- разработчик также может при помощи IB создавать новые классы, методы и атрибуты, а затем создавать экземпляр класса на основе этого описания и все это без единой строчки кода!
- в любой момент при помощи сочетания клавиш command+R можно протестировать интерфейс программы
Предлагаю пройтись по этим пунктам и дать пояснение особо сложным местам. Заранее предупрежу, что я не буду вдаваться в подробности и давать расшифровку банальным присвоениям свойств элементам интерфейса, разобраться в которых не состави труда любому начинающему программисту, а заострю внимание именно на проблемных и сложных для понимания концепциях, с которыми столкнулся во время своего ознакомления с программой и, уверен, что такие же вопросы могли возникнуть и у вас, если вы уже пробовали разобраться в доступных на сайте developer.apple.com примерах приложений для iPhone.
Состав окон в Interface Builder
Предлагаю начать наше знакомство с IB с открытия интерфейса программы из проекта, с которым мы знакомились в одном из прошлых постов. Тем, кому не довелось прочесть мои предыдущие статьи, просто создайте новый проект с именем “Empty” для iPhone на основе шаблона “Window-Based Application”, и откройте прилагающийся к нему xib-файл интерфейса (xib и nib одно и то же). Затем вызовите сочетанием клавиш Shift+Command+I окно инспектора свойств, на которое я буду ссылаться в процессе повествования.
Окно Library (библиотеки компонентов и ресурсов) разделено на две части: библиотека компонентов Objects (строка навигации, тектовое поле, поля ввода, кнопки и пр.) и ресурсы Media (изображения, звуки и пр.).
Панель диспетчера свойств позволяет установить атрибуты объекта, просмотреть и настроить соединения между объектами.
Окно nib-файла содержит объекты верхнего уровня вашего приложения. Хочу заострить ваше внимание на этом окне и дать разъяснения каждому из объектов.
File’s Owner и First Responder являются так называемыми прокси-объектами — это “хранилища” для объектов, на использование которых ссылаются в интерфейсе программы, но они не включены в nib-файл. Прокси-объекты при загрузке nib-файла в теле программы не инициализируются, вместо этого они замещаются реальными объектами в запущенном приложении. Такие замены происходят автоматически, но вы можете вручную указать замещаемые объекты при загрузке файла интерфейса в программу. Проще говоря, прокси-объекты — это всего лишь ссылки.
Под File’s Owner в нашем приложении подразумевается объект, который загружает nib-файл в программу. В нашем случае это UIApplication, который делегирует полномочия по управлению программой нашему классу EmptyAppDelegate. Когда в будущем вы будете создавать дополнительные интерфейсы, а их в программе может быть несколько и они могут подгружаться в приложение в разное время в зависимости от необходимости, то увидите, что File’s Owner будет являться ссылкой на объект класса NSObject. Это сделано специально, так как в случае загрузки nib-файла в приложении за него может отвечать объект любого класса, но у всех классов есть один предок - NSObject.
Объект First Responder в процессе работы приложения постоянно меняется. Cocoa использует несколько факторов, чтобы определить какой из объектов должен играть роль First Responder в данный момент: какое окно сейчас активно, какой view получил фокус и пр. Типичные действия за которые отвечает First Responder в программе: работа с буфером обмена, манипуляции с текстом, операции уровня документа (undo, redo, save), определенные пользователем действия и пр. Сейчас его назначение не совсем ясно, но по мере его использования в своих примерах я расскажу подробности его применения и все встанет на свои места.
EmptyAppDelegate — класс, которому делегировал полномочия UIApplication, т.е. File’s Owner в нашем случае (подробнее о данной процедуре можно прочесть в посте “Изучая пустоту“).
Window — объект, представляющий главное окно нашего приложения. Ваше приложение не должно иметь более одного окна — экземпляра класса UIWindow. В ситуациях, когда вам необходимо сменить интерфейс, используйте смену View вместо смены окон. Об этом я расскажу ниже.
Переключите просмотр окна nib-файла в режим списка — такой вид облегчит нам дальнейшее конфигурирование интерфейса приложения.
Создание связей между объектами
Наверно, одна из первых сложностей с которой сталкивается разработчик в IB — это связи между объектами. Новая концепция настройки методов и свойств, основанная на специфике языка Objective-C общаться при помощи отправки сообщений, вызывает недопонимание, хотя смысл работы достаточно прост. В процессе создания связей между объектами используются два термина: Outlet и Action.
Outlet представляет собой обычную переменную объекта, которая ссылается на объект в интерфейсе приложения.
Наш интерфейс уже содержит несколько примеров Outlets. В окне nib-файла выберите объект File’s Owner и переключитесь на закладку Connections в окне инспектора свойств. Для объекта File’s Owner определена переменная delegate, которая ссылается на объект делегата класса UIApplication, в нашем случае Empty App Delegate — главный класс нашего приложения.
Выберите последний и посмотрите его свойства. Он в свою очередь также имеет одну переменную — window, которая содержит ссылку на объект UIWindow или, проще говоря, на главное окно нашего приложения.
Подраздел Referencing Outlets представляет собой список ссылающиеся на наш объект переменных других объектов.
В коде приложения такие переменные классов отличаются от обычных спецификатором типа — IBOutlet. Откройте EmptyAppDelegate.h файл в XCode.
#import <UIKit/UIKit.h>
@class EmptyViewController;
@interface EmptyAppDelegate : NSObject {
IBOutlet UIWindow *window;
}
@property (nonatomic, retain) UIWindow *window;
@end
Спецификатор IBOutlet у переменной window и любых других переменных означает, что присвоение нового значения подобной переменной влияет на интерфейс нашей программы: меняет значение элемента, его доступность, цвет, шрифт и многие другие свойства. Смысл этой фразы станет куда более понятен после рассмотрения примера.
Связи типа Action в Interface Builder необходимы для передачи сообщений от одного объекта к другому. Всякий раз когда пользователь взаимодействует с интерфейсом программы — нажимает кнопки, перемещает слайдер, вводит текст в поле ввода генерируются сообщения, обработку которых можно реализовать в своем коде.
В окне Library зайдите на вкладку Objects и перетащите элемент UIView на нашу форму. Его размер автоматически изменится, чтобы охватить все свободное пространство в окне. Это нормально.
Здесь хочу сделать небольшое отступление. Как вы помните, я уже говорил в одном из моих прошлых постов, что объект типа UIWindow является дочерним от UIView. В нашей форме уже присутствует объект типа UIWindow, который определяет главное окно приложения, и я мог бы реализовать все приведенные ниже примеры взяв его в качестве основы для размещения элементов интерфейса. Но с точки зрения программирования приложения для iPhone правильнее использовать объект UIView как область размещения интерфейса программы. Обычно в приложении для iPhone используется даже не одно, а множество Views, которые меняются в процессе работы приложения для представления различных данных. Вспомните, например, программу Настройки на вашем iPhone / iPod Touch. Смена окон при выборе раздела настроек (почта, музыка, основные) реализована как раз за счет многочисленных View в приложении, а за процесс смены View отвечает объект класса UIViewController, но о нем я расскажу в следующей статье. Кроме того, я хотел бы рассказать вам об одном из преимуществ Interface Builder, демонстрация которого без реализации нового класса в IB невозможна.
Вернемся к нашей форме. Выделите объект View на нашей форме, кликнув по пустому месту в окне, и перейдите на закладку Identity инспектора свойств. В разделе Class Outlets добавьте новую переменную с именем label и типом UILabel, а в разделе Class Identity дайте название нашему новому классу reView.
Перетащите элемент Label из Library в центр нашей формы. Измените его размеры, чтобы элемент занимал всю ширину окна, и сделайте выравнивание текста по центру.
Теперь мы должны установить связь между этой переменной и объектом Label на нашей форме. Есть три пути:
- С нажатой клавишей Ctrl нажмите на объект View в окне приложения и не отпуская клавиши мыши проведите появившуюся линию до объекта Label на форме. После этого отпустите кнопку и выберите из выпадающего списка возможных вариантов переменных, объявленных в классе UIView, переменную label.
- Выделите объект Label на нашей форме и перейдите на закладку Connections в окне инспектора свойств. Нажмите левой кнопкой мыши на кружок напротив New Referencing Outlets и проведите появившуюся линию до объекта View на форме. После отпускания клавиши мыши выберите из появившегося списка переменных label.
- Нажмите правой кнопкой мыши (или Ctrl+клик) по View в окне nib-файла. В появившемся окне связей класса нажмите на кружок напротив переменной label и соедините его линией с объектом Label на форме.
Перетяните из библиотеки элементов Round Rect Button на нашу форму и назовите кнопку ”Напомнить”.
После чего выделите объект View, перейдите на закладку Identity и в разделе Class actions добавьте новый элемент под названием touchIt. А затем любым из вышеописанных методов свяжите его кнопкой на форме. В выпадающем списке выберите метод Touch Up Inside.
Теперь при нажатии на кнопку будет отправлено сообщение в объект View. А точнее, в момент окончания нажатия на кнопку (Touch Up Inside вызывается в момент отпускания кнопки), будет вызван метод touchIt класса reView.
При создании связей типа Action и Outlet разрешены соединения типа один-ко-многим. То есть мы могли добавить на нашу форму еще несколько элементов и связать их с нажатием на кнопку. В этом случае вызов Touch Up Inside приводил к реакции сразу нескольких элементов. Или в случае многих связей с элементом Label на форме мы могли бы изменять его значение из разных классов в процессе работы программы.
Заметьте также, что от типа связи (Outlet или Action) зависит направление перетягивания линии связи: от кнопки к view — action, когда нажатие на кнопку вызывает метод во view; от view к label — outlet, когда изменение переменной внутри реализации класса вызывает изменения в объекте на форме.
Напоследок выделите объект Label в окне приложения и очистите его значение.
Как вы видите, процесс создания actions в нашем приложении не сильно отличает от создания outlets. Давайте теперь взглянем на код, отрабатывающий действие.
Interface Builder предоставляет ряд удобств в плане создания новых классов, которые позволят меньше уделять внимания коду и сокращают время разработки. Как вы успели заметить, мы создали новый класс производный от UIView в Interface Builder, но у нас отсутствуют файлы, его реализующие. Для этого у IB есть мощнейшая функция, облегчающая нам работу. Выделите объект View в окне nib-файла, а затем войдите в меню File и вызовите команду Write Class Files.
Оставьте имя класса по умолчанию — reView и нажмите сохранить. В новом окне поставьте галочку, которая добавит файлы нового класса в наш проект в XCode.
Переключитесь в XCode и убедитесь, что чудо произошло. Файлы reView.h и reView.m добавлены в наш проект. Перетяните их из корня проекта в папку Classes для субординации.
Откройте файл reView.h
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
@interface reView : /* Specify a superclass (eg: NSObject or NSView) */ {
IBOutlet UILabel *label;
}
— (IBAction)touchIt;
@end
Здорово, не правда ли? Все что нам остается, так это описать свойство label и указать родительский класс:
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
@interface reView : UIView {
IBOutlet UILabel *label;
}
@property (nonatomic, retain) UILabel *label;
— (IBAction)touchIt;
@end
Переключитесь в реализацию класса опишите реализацию метода touchIt:
#import "reView.h"
@implementation reView
@synthesize label;
— (IBAction)touchIt {
label.text = @"Позвони родителям!";
label.textColor = [UIColor redColor];
}
@end
Позволю себе здесь сделать еще одно отступление. Когда я первый раз столкнулся с программой на Objective-C, я не мог понять зачем ставиться какой-то идентификатор @ перед всем строковыми выражениями. Когда я нашел ответ, то понял, что не могу не поделиться им с вами. Директива @”” скрывает под собой создание нового объекта класса NSConstantString, чтобы программист каждый раз при работе со строками не тратил время на его объявление.
Для тех кто читал мой пост “Введение в Objective-C” дам более глубокое объяснение работы данной директивы. В документации нигде не дано описания этого класса, но известно, что не нужно заботиться об освобождении объектов-строк данного класса после использования. Я так полагаю, что инициализация происходит в следующем виде:
[[[NSConstantString alloc] initWithSomething:"Наша строка"] autorelease]
Я думаю, вы уже догадались как работает этот код. Инициализуется новый объект класса NSConstantString и сразу же делается отложенное освобождение объекта из памяти вызовом метода autorelease. Как вы помните, объект в этом случае будет освобожден позже: после окончания работы участка кода, где был вызван этот метод.
Объект NSConstantString может быть передан в качестве параметра в функцию или как значение в переменную, где будет произведено retain (в общем случае) его значения, что приведет к увеличению количества ссылок на объект на 1. А значит к моменту вызова release в AutoReleasePool количество ссылок на объект будет равно 2, и мы сохраним объект до окончания работы с ним.
В реализации метода touchIt мы использовали еще один новый для нас класс UIColor. Он возвращает объект, описывающий указанный при инициализации цвет. Объект UIColor инициализируется с помощью метода класса redColor, поэтому не требует вызова alloc метода, а также освобождения объекта после использования.
Сохраните все изменения и запустите наше приложение.
Посреди нашей формы красуется нопка “Напомнить”, нажатие на которую приводит появлению красной надписи “Позвони родителям!”.
Interface Builder работает в тесном контакте с XCode. Поэтому когда вы объявляете новую переменную или метод со спецификатором типа IBOutlet или IBAction в заголовочном файле, они сразу же становятся доступны в окне связей IB. Чтобы посмотреть как это происходит, измените объявление класса reView, в соответствии с примером:
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
@interface reView : UIView {
IBOutlet UILabel *label;
IBOutlet UITextField *textField;
}
@property (nonatomic, retain) UILabel *label;
@property (nonatomic, retain) UITextField *textField;
— (IBAction)touchIt;
@end
Переключитесь в окно IB и вызовите окно связей объекта reView. Теперь мы может создать новую связь между нашей переменной textField и объектом на форме. Но учтите, что это должен быть обязательно элемент типа UITextField. После создания связи вы можете поиграться со свойствами элемента в коде программы и поглядеть как это будет выглядеть в форме после запуска приложения. Советую для лучшего понимания потренироваться с как можно большим количеством элементов интерфейса, а в более сложные примеры мы углубимся в следующих статьях.
Данный обзор был подготовлен на Mac mini G4 с установленной Mac OS X 10.5.4 и iPhone SDK (build 9M2199a). Как мне удалось запустить iPhone SDK на платформе PowerPC я расскажу в следующем посте уже совсем скоро.
Комментарии
Спасибо за классные статьи! Продолжай в том же духе!
Статья отличная! Вот вопрос у меня: Какая у Вас версия мак оси и это официальная СДК ? Просто за неимением мака я поставил hackintosh 10.5.2 и официальную скачать не могу, не подходит она под версию оси… И в моей СДК (я так предполагаю, что она предпоследняя) нету Window — Based — Application, у мнея всего 3 варианта создания начального приложения… Может можно както обновиться?
У меня SDK официальная, последняя бета с сайта apple. Не исключено, что предыдущие беты были доступны для запуска на более старых версиях 10.5. В требованиях к последней бете стоит 10.5.3. Я не в курсе, каким образом можно обновиться до нее или до 10.5.4 на Хакинтоше — этот вопрос стоит задать на профильном форуме. Но точно знаю, что в первых релизах SDK были доступны только 3 шаблона для iPhone при создании нового проекта. Т.е. по сути можно пробовать программировать и не на свежей версии комплекта разработчика, но во что это может вылиться я не знаю.
Не уверен, что можно обновляться, т.к. при этом, наверняка, слетят SSE и TCPM-патчи, поэтому лучше всего попробовать найти 10.5.3, если она существует в природе.
10.5.3 нету ( и обновляться я пробовал — система падает, пробовал даже по “хакинтош” способам тоже самое. Да вот как раз шаблона у меня 3, причем некоторых классов вообще нету. Ладно буду ждать 10.5.3 может кто соберет ее )
2ТитаренкоА: я правильно поянл, что у тебя 10.5.2 хакинтош и не ставится 10.5.3?.. А какую ошибку при panic пишет? уж часом не про AppleIntelCPUPowerManagement?
Спсибо за статью :) Реально помогло.
Отличная статья,нигде лучше не нашел!
У меня вопрос, можно ли на айфоне захватить входящий звонок?И возможно ли при входящем звонке автоматический запуск приложений?Заранее спасибо!
Думаю, что средствами ОФИЦИАЛЬНОГО SDK — никак. Ибо, если мне не изменяет склероз, входящий звонок — одно из событий по которону приложение должно освободить память и тихо смытся
Если я правильно понял, нельзя организовать запуск собственного приложения при входящем звонке.А НЕ ОФФИЦИАЛЬНУЮ ВЕРСИЮ SDK можно где-нибудь отыскать?На code.google.com описана Framework.Telephony,но но с чем ее едят не понятно.Спасибо!
Можно собрать toolchain самому. Где-то натыкался на подробную инструкцию.
ИМХО, тут дело не в SDK, а в особенностях операционной системы.
Огромное спасибо! Очень полезная статья.
Не работает в SDK 2.1, при нажатии кнопки ничего не происходит. Пробовал делать все сам, пробовал архив проекта — не получается. Не могу понять в чем причина..
Только что попробовал в последнем SDK — программа из архива работает. Ты запускал в эмуляторе? У тебя Хакинтош?
Почти такая же проблема. Скачал пример, все работает отлично, когда попытался сделать все сам при нажатие на кнопку ничего не происходит. В какую сторону копать?
Евгений, Спасибо за очень позновательную статью!!! Отлично написано и очень информативно!!!
У тех кого не получается запустить с первого раза, начните проект сначала, и следуйте инструкциям написанным в статье!!! Немного внимательности и терпение и все должно получится!!!!
а нужно ли в класс reView дописывать деструктор?
По-хорошему, нужно. Но также следует знать, что в iPhone реализована специфическая система управления памятью. После работы программы вся выделенная под нее память (в том числе и выделенная динамически) освобождается, и вызов dealloc в конце работы приложения становится лишь формальностью. Безусловно, если программа еще работает, а памяти уже не хватает для ее нормального функционирования, то может потребоваться ручная очистка занятых и неиспользуемых областей памяти.
значит будем дописывать! =) спасибо за отличные статьи!
Форма комментирования для «Знакомство с Interface Builder. Связи между объектами.»
Спасибо за статью! “Hello world!” с социально значимым подтекстом рулит! :)
Позволите вопрос? Мне осталось неясно, с какой целью мы создавали в нашем классе reView свойство (@property (nonatomic, retain) UILabel *label). Почему нельзя просто общаться напрямую с аутлетами? В ходе экспериментов закомментировал объявление свойства и синтез его get/set-методов — поведение программы не изменилось нисколько.
Да, я тоже хочу задать этот вопрос. Насколько необходимо создание свойств, и грамотно ли сразу работать с аутлетами?
Я балдею от твоих статьях, огромное спасибо. Жду твоей книги “Программирование под Ймобилку” — подумай у тебя классно получается.
Очень супер мега спасибо!
Спасибо))
Смысл происходящего ясен. Но у меня, к сожалению, никак не получается связать label — ни одним из трех указанных способов. Пробовал и с UIVeiw, и Re View, просто в UIWindow, просто застрял на этом! Привязать получается только к ViewController — но так желаемого результата не достичь. Казалось бы куда уж подробнее, но может кто-нибудь все же подскажет.
Ув. автор, а не сохранились ли бОльшие версии скриншотов к этому великолепному уроку, для вставки в конспект?
Извините, к сожалению, нет.
Спасибо, ценная информация!
Спасибо, ценная информация!
Смысл происходящего ясен. Но у меня, к сожалению, никак не получается связать label — ни одним из трех указанных способов. Пробовал и с UIVeiw, и Re View, просто в UIWindow, просто застрял на этом! Привязать получается только к ViewController — но так желаемого результата не достичь. Казалось бы куда уж подробнее, но может кто-нибудь все же подскажет.