Xcode: разработка cocoa-приложений

В данной заметке мы напишем простейшее приложение для Mac OS, используя родные средства: Xcode, Objective-C, Cocoa. Что понадобится: компьютер Apple (или Hackintosh), среда разработки Xcode, глаза, руки и среднее образование. Немного очевидного. Разработка для Mac OS ведется на языке программирования Objective-C, для построения GUI используется API Cocoa, для написания приложений IDE Xcode и Interface Builder. Кому не знакомо хотя бы одно слово, окромя Interface Builder, тот может смело пройти по ссылкам и почитать.

Итак, мы напишем простое полноценное приложение, которое будет делать весьма бесполезное — генерировать произвольные цифры. Бесполезность в нашем случае определена целью, мы хотим простое приложение, которое будет работать, радовать и даст фундамент для экспериментов. Запустим Xcode (кто не справился, дальше лучше не читать), и там выберем «New Project», в появившемся окне выбора шаблонов выбираем раздел Mac OS X → Application → Cocoa Application:

Перед вами предстанет собственно интерфейс Xcode. Xcode уже позаботилась о создании структуры проекта и подготовке скелета приложения, нам остается написать самую малость. Созданный шаблон вполне себе функционален, в чем можно убедиться нажав «Build and Run» (Command + Enter). Приложение выглядит как пустое окно и готовое к использованию меню.

Выберите файл main.m нажав на него один раз и содержимое отобразится в окне редактора (двойной клик отроет файл в отдельном окне). Скорее всего, вам никогда не нужно будет редактировать этот файл, по умолчанию метод main() просто вызывает NSApplicationMain(), который в свою очередь загружает пользовательский интерфейс и остальную необходимую для функционирования информацию из файла xib. Файл xib создается в Interface Builder (ниже будет яснее). Когда xib загружен, приложение просто ожидает действий пользователя, когда пользователь щелкает мышкой, вводит текст и прочее — автоматически вызываются необходимые методы.

Слева в окне Xcode разверните дерево Resources и найдите там MainMenu.xib, после двойного клика на MainMenu.xib откроется Interface Builder. Будет переполох, куча окон и окошечек, для своего удобства сверните все остальные приложения (Hide Others, Option + Command + H). Interface Builder позволяет вам эффективно создавать и редактировать объекты пользовательского интерфейса (окошки, кнопки, поля ввода и гораздо более интересные вещи) и сохраняет ваши труды в xib файл. Так же, Interface Builder позволяет связывать элементы пользовательского интерфейса непосредственно с вашим кодом (методами классов, переменными).

Screen-shot-2010-01-21-at-4.13.46-AM-300x226.png

Тут собраны все возможные виджеты, которые вы можете добавить в свое приложение.

Окно RandomApp (по центру) — это непосредственное место проектировки интерфейса, где представлено, как выглядит ваше приложение сейчас (а точнее экземпляр класса NSWindow как выглядит).

MainMenu.xib (Doc Window) — внизу по центру. Большинство объектов в вашем проекте видимы (кнопки, текст), но контроллеры и некоторые другие объекты визуально не отображаются на форме. Так вот, все эти объекты собраны в этом окне.

Attributes (справа) — это окно инспектора (Гаджета). Тут вы можете увидеть атрибуты выбранного объекта, изменить его свойства, размер и прочее.

MainMenu — вид меню вашего приложения.

Нарисуем интерфейс нашего приложения. Так как эта задачи вполне простая, останавливаться тут особо не будем. Только пара замечаний: чтобы поместить объект в окно проектирования приложения, достаточно перетащить его из Library Window на ваш холст. Т.е. выбираем кнопку в Library Window и тащим его в RandomApp, а там уже масштабируем и вертим. Чтобы изменить текст на кнопке достаточно два раза по кнопке щелкнуть.

2.png

В общем, должно получиться примерно так:

3.pngА то есть, две кнопки (Push Button) и один Label. Вернемся к коду. Переключитесь обратно в Xcode (Interface builder не закрывайте). Как вам известно (или стало известно из заметки в вики про Objective C), каждый класс в Objective C представлен двумя файлами: заголовочным и реализацией. Заголовочный файл (header file) по сути интерфейс, в котором декларируются переменные и методы которые будет иметь класс. В реализации (implementation) непосредственно уже реализуются методы, инициализируются переменные и вообще всякое творится и случается.

В Xcode во фрейме Groups & Files (справа) выделяем дерево Classes и создаем новый файл File -> New File (Command + N), в появившемся окне выбора шаблона выбираем раздел Mac OS X, а там Cocoa class -> Objective-C class и называем его Foo.m.

В дереве проекта, в Classes появятся два файла Foo.h и Foo.m. Откроем Foo.h и добавим туда нужные нам переменные и методы. Переменные являющиеся указателями на другие объекты называются outlet; методы, которые можно вызывать из пользовательского интерфейса — action. Редактируем Foo.h как показано ниже:

#import

@interface Foo : NSObject {
IBOutlet NSTextField *textField;
}
– (IBAction)seed:(id)sender;
– (IBAction)generate:(id)sender;
@end

Немного о программировании. Данные короткий кусок кода говорит нам: Foo подкласс NSObject; Foo содержит в себе переменную textField, которая является указателем на объект NSTextField; Foo содержит два метода seed и generate.

Теперь необходимо связать наш класс с нашим интерфейсом. Возвращаемся в Interface Builder, в окне Library выбираем Object (1) (внизу есть строка поиска, чтобы облегчить поиск элемента, так же можно упростить поиск выбрав в выпадающем меню раздел Cocoa -> Objects & Controllers) и тащим наш Object в Doc Window (2). Выделяем наш Object в Doc Window (2) и в окне инспектора (3) переходим на вкладку Identify Inspector и в выпадающем списке Class выбираем наш класс Foo (если класс Foo в списке отсутствует, значит вы ошиблись где то в коде, вернитесь в Xcode и проверьте правильность написанного у вас и указанного выше).

Теперь установим связи между интерфейсом пользователя и нашим кодом. Щелкните правой кнопкой мыши (если однокнопочная, то Control + click) на нашем классе Foo в Doc Window (1), рядом появится окно инспектора объекта (2) со всеми переменными и методами класса. Теперь нажмите левой кнопкой мыши на кружочек напротив переменной (Outlets) textField и не отпуская кнопки мыши перетащите указатель мыши на наш Label в окно построения интерфейса (3). Наглядно:

4.png

Итак, мы установили связи между объектами и теперь переменная textField указывает на Label нашего интерфейса пользователя. Теперь пришло время установить связи между кнопками и вызовом методов из класса Foo. Тут будет все наоборот, в окне построения интерфейса (1) нажмите правой кнопкой мыши на кнопку «Random (using Time)» и, не отпуская кнопку мыши, перетащите курсор в Doc Window (2) на наш объект класса Foo. Когда вы отпустите кнопку мыши, появится инспектор (3) и там, в Received Action, выберите метод seed. Так мы привязали нажатие кнопки к вызову метода. Потренеруйтесь и добавьте сами связь кнопки «Random» и метода generate класса Foo (аналогично вышеописанному).

5.png

В итоге, нажав правой кнопкой мыши на наш класс Foo в Doc Window вы должны получить следующую картину:

6.png

Вернитесь в Xcode. Напишем реализацию для нашего класса Foo. Откройте Foo.m и приведите его к следующему виду (если вам непонятно что здесь в коде написано, то пока я не могу вам помочь, об этом мы поговорим в следующих заметках):

@implementation Foo

- (IBAction)generate:(id)sender
{
int generated;
generated = (random() % 100) + 1;
NSLog(@»generated = %d», generated);
[textField setIntValue:generated];
}

- (IBAction)seed:(id)sender
{
srandom(time(NULL));
[textField setStringValue:@"Generator seeded"];
}
@end

Все таки немного лирики:

  • id — это указатель на любой тип объекта;
  • BOOL — это просто char, который используется для обозначения истина-ложь;
  • YES — это циферка 1;
  • NO — это циферка 0;
  • IBOutlet — это макрос, которые значит «ничего». Это просто хинт для Interface builder, чтобы ему легче было разбирать класс;
  • IBAction — тоже, что и void, а так же подсказка тому же Interface Builder;
  • @interface — начинается с @ специально для Objective C, ибо в чистом C данный символ не используется. Т.е. все зарезервированные слова, начинающиеся с @, призваны минимизировать конфликты между С и Objective C.

Ну и раз уж Objective-C — это С с расширениями, конечно же вы можете вызывать методы стандартных библиотек С и Unix, такие как random(), srandom().

Чтобы перемещаться между заголовочными файлами и реализацией пользуйтесь горячей клавишей Command + Option + UpArrow. Экономит время; Включите консоль, туда пишет NSLog и можно выводить свои сообщения. Включается в Preferences -> Debugging -> On Start Show Console;

Можно и запустить теперь. Убедитесь, что вы сохранили изменения в Interface Builder и в Xcode нажмите Build & Run (Command + Enter). У меня работает, надеюсь, что и у вас. Есть пара ляпов:
текст не помещается в label. Перейдите в Interface Builder, выделите Label, в инспекторе (1) выберите вкладку Attributes и там в Alignment выберите штучку «посередине»:

7.png

При старте в текстовом поле написано Label, что не очень красиво. Поэтому познакомимся с методом awakeFromNib(). Этот метод нет необходимости описывать в заголовочном файле, метод вызывается автоматически после того как подгружается интерфейс из xib файла. И так, добавьте в ваш Foo.m метод awakeFromNib(), теперь Foo.m должен у вас выглядеть так:

#import "Foo.h"

@implementation Foo

- (IBAction)generate:(id)sender
{
int generated;
generated = (random() % 100) + 1;
NSLog(@»generated = %d», generated);
[textField setIntValue:generated];
}

- (IBAction)seed:(id)sender
{
srandom(time(NULL));
[textField setStringValue:@"Generator seeded"];
}

-(void)awakeFromNib
{
NSCalendarDate *now;
now = [NSCalendarDate calendarDate];
[textField setObjectValue:now];
}
@end

В методе awakeFromNib мы берем текущую дату и выводим её в текстовое поле Label. Очень коротко и очень скомкано. Даже и не туториал. Но я к чему всё это, я хочу показать, что не все так страшно, как кажется. Мы создали приложение, я показал вам насколько всё просто устроено, не должно быть теперь страшно.

Я специально не вдаюсь в детали и не описываю в подробностях, ведь цель заинтересовать. Если вам таки интересно, но непонятно — то пишите смело в комментариях свои гневные вопросы и отзывы и я постараюсь ответить.