Главная iPhone Mac OS X Форум О себе

Знакомство с Interface Builder. Связи между объектами.

Interface Builder LogoПроцесс создания любого приложения можно условно разделить на три этапа: создание интерфейса, непосредственное написание кода и отладка. В первой части своих статей я хочу познакомить вас с 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 окно инспектора свойств, на которое я буду ссылаться в процессе повествования.

Interface Builder Windows

Окно Library (библиотеки компонентов и ресурсов) разделено на две части: библиотека компонентов Objects (строка навигации, тектовое поле, поля ввода, кнопки и пр.) и ресурсы Media (изображения, звуки и пр.).

Панель диспетчера свойств позволяет установить атрибуты объекта, просмотреть и настроить соединения между объектами.

Окно nib-файла содержит объекты верхнего уровня вашего приложения. Хочу заострить ваше внимание на этом окне и дать разъяснения каждому из объектов.

Окно 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-файла в режим списка — такой вид облегчит нам дальнейшее конфигурирование интерфейса приложения.

Окно nib-файла в режиме списка

Создание связей между объектами

Наверно, одна из первых сложностей с которой сталкивается разработчик в IB — это связи между объектами. Новая концепция настройки методов и свойств, основанная на специфике языка Objective-C общаться при помощи отправки сообщений, вызывает недопонимание, хотя смысл работы достаточно прост. В процессе создания связей между объектами используются два термина: Outlet и Action.

Outlet представляет собой обычную переменную объекта, которая ссылается на объект в интерфейсе приложения.

Наш интерфейс уже содержит несколько примеров Outlets. В окне nib-файла выберите объект File’s Owner и переключитесь на закладку Connections в окне инспектора свойств. Для объекта File’s Owner определена переменная delegate, которая ссылается на объект делегата класса UIApplication, в нашем случае Empty App Delegate — главный класс нашего приложения.

Окно связей File's Owner

Выберите последний и посмотрите его свойства. Он в свою очередь также имеет одну переменную — window, которая содержит ссылку на объект UIWindow или, проще говоря, на главное окно нашего приложения.

Окно связей объекта Empty App Delegate

Подраздел 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 на нашу форму. Его размер автоматически изменится, чтобы охватить все свободное пространство в окне. Это нормально.

Добавление объекта 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.

Настройка свойств UIView

Перетащите элемент Label из Library в центр нашей формы. Измените его размеры, чтобы элемент занимал всю ширину окна, и сделайте выравнивание текста по центру.

Добавление объекта UILabel на форму

Теперь мы должны установить связь между этой переменной и объектом Label на нашей форме. Есть три пути:

  1. С нажатой клавишей Ctrl нажмите на объект View в окне приложения и не отпуская клавиши мыши проведите появившуюся линию до объекта Label на форме.

    Создание связи IBOutlet

    После этого отпустите кнопку и выберите из выпадающего списка возможных вариантов переменных, объявленных в классе UIView, переменную label.

    Создание связи IBOutlet

  2. Выделите объект Label на нашей форме и перейдите на закладку Connections в окне инспектора свойств. Нажмите левой кнопкой мыши на кружок напротив New Referencing Outlets и проведите появившуюся линию до объекта View на форме. После отпускания клавиши мыши выберите из появившегося списка переменных label.

    Создание связи IBOutlet

  3. Нажмите правой кнопкой мыши (или Ctrl+клик) по View в окне nib-файла. В появившемся окне связей класса нажмите на кружок напротив переменной label и соедините его линией с объектом Label на форме.

    Создание связи IBOutlet

Перетяните из библиотеки элементов Round Rect Button на нашу форму и назовите кнопку ”Напомнить”.

Добавление элемента UIButton на форму

После чего выделите объект View, перейдите на закладку Identity и в разделе Class actions добавьте новый элемент под названием touchIt. А затем любым из вышеописанных методов свяжите его кнопкой на форме. В выпадающем списке выберите метод Touch Up Inside.

Создание связи между элементом UIButton и UIView

Теперь при нажатии на кнопку будет отправлено сообщение в объект 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.

Генерация нового класса через Interface Builder

Оставьте имя класса по умолчанию — reView и нажмите сохранить. В новом окне поставьте галочку, которая добавит файлы нового класса в наш проект в XCode.

Генерация нового класса через Interface Builder

Переключитесь в XCode и убедитесь, что чудо произошло. Файлы reView.h и reView.m добавлены в наш проект. Перетяните их из корня проекта в папку Classes для субординации.

Добавление сгенерированных классов в проект в XCode

Откройте файл 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 метода, а также освобождения объекта после использования.

Сохраните все изменения и запустите наше приложение.

Окно запущенного приложения в симуляторе iPhone

Посреди нашей формы красуется нопка “Напомнить”, нажатие на которую приводит появлению красной надписи “Позвони родителям!”.

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 я расскажу в следующем посте уже совсем скоро.

Скачать архив проекта (14Кб)

Комментарий

Пингбэки

Создать пользовательское событие @pyobjc.ru 22.10.2008 13:55
смотри примерhttp://pyobjc.ru/2008/08/03/znakomstvo-s-interface-builder-svyazi-mezhdu-obektami/и http://pyobjc.ru/2008/08/10/pishem-pravilnyj-tekstovyj-redaktor/

Комментарии

Vasily Mikhailitchenko 5.08.2008 15:11

Спасибо за классные статьи! Продолжай в том же духе!

ответить
Titarenko Aleksey 6.08.2008 18:40

Статья отличная! Вот вопрос у меня: Какая у Вас версия мак оси и это официальная СДК ? Просто за неимением мака я поставил hackintosh 10.5.2 и официальную скачать не могу, не подходит она под версию оси… И в моей СДК (я так предполагаю, что она предпоследняя) нету Window — Based — Application, у мнея всего 3 варианта создания начального приложения… Может можно както обновиться?

ответить
Evgeniy Krysanov 6.08.2008 18:48

У меня SDK официальная, последняя бета с сайта apple. Не исключено, что предыдущие беты были доступны для запуска на более старых версиях 10.5. В требованиях к последней бете стоит 10.5.3. Я не в курсе, каким образом можно обновиться до нее или до 10.5.4 на Хакинтоше — этот вопрос стоит задать на профильном форуме. Но точно знаю, что в первых релизах SDK были доступны только 3 шаблона для iPhone при создании нового проекта. Т.е. по сути можно пробовать программировать и не на свежей версии комплекта разработчика, но во что это может вылиться я не знаю.

ответить

Форма комментирования для «Знакомство с Interface Builder. Связи между объектами.»

Обязательное поле. Не больше 30 символов.

Обязательное поле

captcha image Пожалуйста, введите символы, которые вы видите на изображении

ZYV 6.08.2008 19:21

Не уверен, что можно обновляться, т.к. при этом, наверняка, слетят SSE и TCPM-патчи, поэтому лучше всего попробовать найти 10.5.3, если она существует в природе.

ответить
Titarenko Aleksey 12.08.2008 15:15

10.5.3 нету ( и обновляться я пробовал — система падает, пробовал даже по “хакинтош” способам тоже самое. Да вот как раз шаблона у меня 3, причем некоторых классов вообще нету. Ладно буду ждать 10.5.3 может кто соберет ее )

ответить
Teacher 29.08.2008 15:39

2ТитаренкоА: я правильно поянл, что у тебя 10.5.2 хакинтош и не ставится 10.5.3?.. А какую ошибку при panic пишет? уж часом не про AppleIntelCPUPowerManagement?

ответить
mcmx 7.09.2008 12:01

Спсибо за статью :) Реально помогло.

ответить
Xasan 17.09.2008 9:10

Отличная статья,нигде лучше не нашел!

ответить
Xasan 17.09.2008 9:24

У меня вопрос, можно ли на айфоне захватить входящий звонок?И возможно ли при входящем звонке автоматический запуск приложений?Заранее спасибо!

ответить
mcmx 17.09.2008 10:26

Думаю, что средствами ОФИЦИАЛЬНОГО SDK — никак. Ибо, если мне не изменяет склероз, входящий звонок — одно из событий по которону приложение должно освободить память и тихо смытся

ответить
Xasan 17.09.2008 10:37

Если я правильно понял, нельзя организовать запуск собственного приложения при входящем звонке.А НЕ ОФФИЦИАЛЬНУЮ ВЕРСИЮ SDK можно где-нибудь отыскать?На code.google.com описана Framework.Telephony,но но с чем ее едят не понятно.Спасибо!

ответить
mcmx 17.09.2008 10:41

Можно собрать toolchain самому. Где-то натыкался на подробную инструкцию.

ответить
Andry 17.09.2008 10:42

ИМХО, тут дело не в SDK, а в особенностях операционной системы.

ответить
Andry 11.09.2008 23:39

Огромное спасибо! Очень полезная статья.

ответить
justy 8.10.2008 0:25

Не работает в SDK 2.1, при нажатии кнопки ничего не происходит. Пробовал делать все сам, пробовал архив проекта — не получается. Не могу понять в чем причина..

ответить
Evgeniy Krysanov 8.10.2008 0:37

Только что попробовал в последнем SDK — программа из архива работает. Ты запускал в эмуляторе? У тебя Хакинтош?

ответить
Ex 3.12.2008 10:14

Почти такая же проблема. Скачал пример, все работает отлично, когда попытался сделать все сам при нажатие на кнопку ничего не происходит. В какую сторону копать?

ответить
Willson 17.12.2008 13:33

Евгений, Спасибо за очень позновательную статью!!! Отлично написано и очень информативно!!!

У тех кого не получается запустить с первого раза, начните проект сначала, и следуйте инструкциям написанным в статье!!! Немного внимательности и терпение и все должно получится!!!!

ответить
kamat0z 17.12.2008 16:33

а нужно ли в класс reView дописывать деструктор?

  • (void)dealloc { [label release]; [super dealloc]; }
ответить
Evgeniy Krysanov 17.12.2008 18:30

По-хорошему, нужно. Но также следует знать, что в iPhone реализована специфическая система управления памятью. После работы программы вся выделенная под нее память (в том числе и выделенная динамически) освобождается, и вызов dealloc в конце работы приложения становится лишь формальностью. Безусловно, если программа еще работает, а памяти уже не хватает для ее нормального функционирования, то может потребоваться ручная очистка занятых и неиспользуемых областей памяти.

ответить
kamat0z 17.12.2008 20:15

значит будем дописывать! =) спасибо за отличные статьи!

ответить
bigest 22.02.2009 11:12

Спасибо за статью! “Hello world!” с социально значимым подтекстом рулит! :)

Позволите вопрос? Мне осталось неясно, с какой целью мы создавали в нашем классе reView свойство (@property (nonatomic, retain) UILabel *label). Почему нельзя просто общаться напрямую с аутлетами? В ходе экспериментов закомментировал объявление свойства и синтез его get/set-методов — поведение программы не изменилось нисколько.

ответить
alnite 25.06.2009 9:25

Да, я тоже хочу задать этот вопрос. Насколько необходимо создание свойств, и грамотно ли сразу работать с аутлетами?

ответить
Алексей 1.09.2009 11:09

Я балдею от твоих статьях, огромное спасибо. Жду твоей книги “Программирование под Ймобилку” — подумай у тебя классно получается.

ответить
Reader 29.05.2010 0:38

Очень супер мега спасибо!

ответить
Constantine 4.06.2010 23:56

Спасибо))

ответить
glaz.sasha 22.07.2010 16:25

Смысл происходящего ясен. Но у меня, к сожалению, никак не получается связать label — ни одним из трех указанных способов. Пробовал и с UIVeiw, и Re View, просто в UIWindow, просто застрял на этом! Привязать получается только к ViewController — но так желаемого результата не достичь. Казалось бы куда уж подробнее, но может кто-нибудь все же подскажет.

ответить
Влад, знающий толк 26.07.2010 13:14

Ув. автор, а не сохранились ли бОльшие версии скриншотов к этому великолепному уроку, для вставки в конспект?

ответить
Evgeniy Krysanov 26.07.2010 13:17

Извините, к сожалению, нет.

ответить
Багтрекер 10.01.2011 15:20

Спасибо, ценная информация!

ответить
Багтрекер 10.01.2011 15:21

Спасибо, ценная информация!

ответить
Online Directory 26.10.2011 14:22

Смысл происходящего ясен. Но у меня, к сожалению, никак не получается связать label — ни одним из трех указанных способов. Пробовал и с UIVeiw, и Re View, просто в UIWindow, просто застрял на этом! Привязать получается только к ViewController — но так желаемого результата не достичь. Казалось бы куда уж подробнее, но может кто-нибудь все же подскажет.

ответить
.