Pull to refresh

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

Reading time 11 min
Views 25K
Кросспост из блога "Программирование на Python и
Objective-C под Mac OS и для iPhone / iPod Touch
"
Посвящено комментариям #1, #2 и #3 (оу, чёрт, hellraiser09 не читай последний)


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

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



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



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



  3. Нажмите правой кнопкой мыши (или 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 я расскажу в следующем посте уже совсем скоро.

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


Эпилог



Под конец хочу сказать благодарственные слова:
  1. создателю данного блога — hellraiser09, который стал первопроходцем в данной теме на Хабрахабр. Жаль, что он забросил развивать свой блог, но я постараюсь заменить его насколько меня хватит.
  2. хочу сказать огромное спасибо людям сделавших блоговый движок Byteflow за их работу и помощь в настройке блога


Давно руки чесались напечатать несколько пожеланий блоггерам. Я понимаю, что Хабр — прекрасное место для того, чтобы пропиарить свой блог (собственно, я сам сейчас этим и занимаюсь). Но, пожалуйста, прежде чем заниматься раскруткой уделите внимание его наполнению. Попробуйте в течении месяца писать в блог. Если вам надоест — не тратьте время хабрачитателей. Сколько раз видя слово «кросспост» я нажимал на ссылку и, перейдя на сайт автора, разочарованно уходил с него буквально сразу. Знаете почему? Потому что там пусто или тупо нечего читать!

Если вы все-таки решили сделать рекламу на Хабре, то наполните блог интересными статьями, чтобы первым посетителям было что почитать, кроме поста «Знакомство» и «Я буду писать в этом блоге о биопсии мозга». Если вы хотите, чтобы вас было интересно читать, то не старайтесь писать часто, но пишите содержательно, чтобы даже ваши старые посты было полезно перелистать заново. Доставляйте уникальную информацию и не пишите банальных вещей, глупостей и коротких постов, не несущих смысла.

Всем спасибо за внимание.
Tags:
Hubs:
+4
Comments 24
Comments Comments 24

Articles