Pull to refresh

Deep in Cocoa

Reading time6 min
Views3.9K
в этой статье я попытаюсь рассказать немного больше о Cocoa и его основных принципов. Скажу сразу, что материал не будет всеобъемлющим, поэтому матчасть учим здесь.

Откройте XCode и создайте новое Cocoa Application, назовите его DotView. Что будет делать наша программа? Она будет рисовать на компоненте NSView точку. Что такое NSView? Это базовый класс для графических виджетов Cocoa, поэтому если вы хотите создать свой собственный виджет, то скорее всего это будет наследник NSView.

Теперь добавим в проэкт новый класс. File => New File и в открывшемся окне выбираем «Objective-C NSView subclass», нажимаем Next, в качестве имени задаем DotView, не забываем поставить галочку напротив «Also create DotView.h» и клацаем Finish:
создаем класс DotView
Теперь посмотрим на содержание файла DotView.m. Мастер уже добавил две функции: — (id)initWithFrame:(NSRect)frame и — (void)drawRect:(NSRect)rect. Первая функция вызывается при создании нового объекта типа DotView, а вторая вызывается каждый раз, когда нужно перерисовать наш компонент, поэтому логично комманды отрисовки разместить там.

Добавим в функцию drawRect пару строк:
NSRect bounds = [self bounds];
[[NSColor whiteColor] set];
NSRectFill(bounds);

первой коммандой мы получаем текущие размеры нашего компонента. NSRect — обычная C-струкутра.
Второй коммандой мы сначала посылаем объекту NSColor сообщение whiteColor, в следствие чего он вернет нам белый цвет, а потом посылаем полученному нами цвету сообщение set, которое устанавливает цвет Current Graphics Contents(кто знает как правильно перевести это на русский?) белым.
Третья комманда — вызов функции, которая зальет область, переданную ей как парамерт цветом, установленым в Current Graphics Contents. Из этого примера ясно, что Cocoa — это не только набор объектов, в ней еще есть и куча полезных частоиспользуемых функций.

Ну а теперь напишем код для отрисовки точки.
создаем структуру, описывающюю прямоугольную область, в которой будет находиться наша точка
NSRect dotRect = NSMakeRect(50, 50, 100, 100)

это строка уже должна быть понятна
[[NSColor blueColor] set];

создаем новую кривую безье, которая будет овалом в заданной области(в нашем случае это будет круг) и сразу же посылаем созданной кривой сообщение fill, которое нарисует кривую и зальет ее текущим цветом.
[[NSBezierPath bezierPathWithOvalInRect:dotRect] fill];

в конце функция drawRect должна выглядеть так:
— (void)drawRect:(NSRect)rect {
NSRect bounds = [self bounds];
[[NSColor whiteColor] set];
NSRectFill(bounds);
NSRect dotRect = NSMakeRect(50, 50, 100, 100);
[[NSColor blueColor] set];
[[NSBezierPath bezierPathWithOvalInRect:dotRect] fill];
}


Сохраняем файл, клацаем по MainMenu.nib и попадаем в Interface Builder.
Размещаем в окне компонент CustomView, растягиваем его и привязываем к границам окна(вкладка size панели Inspector). На вкладке Identity панели Inspector в комбобоксе Class выбираем DotView, теперь на компоненте надпись не Custom View, а DotView. Сохраняем, возвращаемся в XCode, Build and Go и получаем результат:


Немного увеличим фунциональность нашего приложения. Откроем файл DotView.h и добавим к классу DotView два поля NSPoint center — центр нашей точки, float radius — радиус точки:
interface DotView: NSView {
NSPoint center;
float radius;
}

Теперь при создании экземпляра класса нам необходимо инициализировать эти переменные, поэтому добавим в функцию initWithFrame: задание начальных значений.
— (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
center.x = center.y = 100.0;
radius = 50;
}
return self;
}

Ессно, теперь придется немного переделать функцию отрисовки точки, а именно — изменить строку создания границы:
NSRect dotRect = NSMakeRect(center.x-radius, center.y-radius, 2*radius,2*radius);


Сделаем так, чтобы при нажатии кнопки мыши, точка перемещалась в ту точку, где находился курсор. Для этого определим фунцию -(void)mouseDown:(NSEvent*)event. Эта функция вызывается каждый раз, когда на нашем компоненте нажали кнопку мыши. event — переменная, в которой хранится вся необходимая информация относительно события.
Внутри функции напишем следущее:
получаем координаты той точки, куда кликнули
NSPoint point = [event locationInWindow];

теперь присваиваем центр нашей точки той координате, которая соответсвует координате точки по которой кликнули на нашем виджете
center = [self convertPoint:point fromView:nil];

а теперь нам надо перерисовать виджет, чтобы изменения вступили в силу. Но вместо прямого вызова функции drawRect мы скажем виджету, что его надо перерисовать:
[self setNeedsDisplay:YES];

на этом фунция заканчивается.

Чтобы было интересней, сделаем так, чтобы положение точки можно было изменять не только кликом, но еще и перетягиванием, для этого определим функцию — (void) mouseDragged:(NSEvent*)event:
— (void) mouseDragged:(NSEvent*)event {
[self mouseDown:event];
}

На данный момент класс DotView имеет следующий вид:

Сохраняем, запускам и радуемся результату.

Target/Action


Каждый виджет в Cocoa имеет target и action. Target — это объект, которому посылается сообщение, когда с виджетом происходит что-то заранее определенное. Например, когда мы изменяем положение слайдера, то он посылает сообщение своему target. Action — это собственно сообщение, которое оно посылает. Это сообщение всегда имеет один аргумент.

Теперь нам нужно добавить в программу возможность изменения размера точки. Для этого определим в нашем классе функцию -(void) changeSize:(id)sender. Аргумент sender — виджет, который послал это сообщение. Функция будет выглядеть вот так:
— (void) changeSize:(id)sender {
radius = [sender floatValue];
[self setNeedsDisplay:YES];
}

Поскольку этот метод не принадлежит классу NSView, то нужно написать его определение в DotView.h. Нам надо, чтобы IB увидел этот метод, поэтому в качестве возвращаемого типа, следует написать IBAction. Содержание DotView.h будет таким:
#import <Cocoa/Cocoa.h>

interface DotView: NSView {
NSPoint center;
float radius;
}
— (IBAction) changeSize:(id)sender;
end

Сохраняем все и переходим в IB. Добавим на форму Slider и зададим ему в качестве Action changeSize с нашего компонета DotView(перетянем указатель мыши с зажатым контролом со слайдера на DotView и после отпускания кнопки мыши в выпадающем списке выберем changeSize: ). Также на вкладке Attributes панели Inspector для слайдера поставьте галочку напротив «continouos» чтобы он посылал сообщение во время изменения значения, а не после отпускания кнопки мыши.
Сохраняем, идем в XCode, компилим, запускаем проэкт и получаем то, что и хотели: точка двигается, размеры изменяет


Теперь надо разобраться что произошло на самом деле. У слайдера есть два поля id target и SEL action. Когда мы соединили слайдер и DotView c помощью IB, то IB задал полю target объект DotView, а полю SEL — selector(changeSize:). IB упростил нам работу и не надо писать много кода, который ничего особенного не представляет(хотя любое Cocoa приложение можно написать самому без помощи IB).

Управление памятью


Каждый объект, который является наследником NSObject имеет поле reference count. Что это поле значит? Это поле показывает сколько объектов заинтересовано в данном. Когда объект создается, его reference count равно 1. Если мы заинтересованы в объекте, то должны послать ему сообщение retain, тогда его reference count увеличится на 1. Если мы больше не заинтересованы в объекте, то должны послать ему сообщение release, что уменьшит его reference count на 1 и проверит, если reference count стало ноль, но пошлет объекту сообщение dealloc.

В Cocoa принято, что тот кто создает объект, тот его и удаляет. Если вам нужен объект, то посылайте ему сообщение retain, но тогда и не забывайте посылать сообщение release.

Усовершенствуем наше приложение. Теперь точка будет менять цвет. Для этого в файле DotView.h добавим новую instance переменную NSColor *color. Также добавим в класс новый метод -(IBAction) changeColor:(id)sender;, сохраним файл и вернемся в DotView.m.

Теперь опишем функцию changeColor:
— (void) changeColor:(id)sender {
NSColor *newColor = [sender color];
[self setColor:newColor];
}

заметьте, что мы ничего не делаем с новым цветом, мы просто берем указатель на текущий цвет у sender и передаем его как параметр в функцию setColor.

Теперь опишем функцию setColor:
— (void) setColor:(NSColor*) newColor {
if (newColor != color) {
[color release];
color = [newColor retain];
[self setNeedsDisplay:YES];
}
}

В этом методе хорошо видна идея управления памяти в Cocoa. Старому значению посылаем сообщение release, а новому — retain. Чтобы не вносить функцию setColor в заголовочный файл, опишите ее раньше чем changeColor:.

Также надо не забыть инициализировать цвет, т.е. добавим в функцию initWithFrame: следующюю строку:
color = [[NSColor blueColor] retain];


Теперь необходимо описать функцию dealloc:
— (void) dealloc {
[color release];
[super dealloc];
}

Замечаем, что переменной color посылаем не dealloc, а release, т.к. не исключено, что кто-то заинтересован в переменной. Также необходимо послать сообщение delloc super классу.

Теперь нужно подкорректировать функию drawRect:. Вместо
[[NSColor blueColor] set];

пишем
[color set];

сохраням все файлы.

Далее нужно поместить на форму компонент, с помощью которого и будем менять цвет. Для этого переходим в IB и переносим на форму компонент Color Well(стандартный компонент выбора цвтеа) и в качестве action задаем ему changeColor: из DotView.

Сохраняем, идем в XCode, запускаем и любуемся: двигаем слайдер — изменяется размер точки, изменяем цвет — изменяется цвет.

В следующей части я расскажу про Model-View-Controller, Key-Value Coding и Key-Value Observing в Cocoa.

Кто сильно заинтересовался, то в качестве домашнего задания сделайте так, чтобы точка не могла выйти за пределы компонента.

P.S.: если что, то файл проекта здесь
Tags:
Hubs:
+20
Comments18

Articles