Deep in Cocoa

    в этой статье я попытаюсь рассказать немного больше о 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.: если что, то файл проекта здесь
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 18

      +3
      Хабракат, быстренько, пока никто не видет, а то придут праведные хабрачеловеки и заминусуют.
        0
        каюсь, каюсь... хабракат уже поставил.

        А два минуса оперативно влепили
          0
          "видИт", грамотей :)
            0
            да ладно, не переживай. Редактрование комментариев на предмет орфографических офибок уже пора-бы и разрешить.

            P.S.:уже минус 3 балла. Станные люди.
              0
              Ой какой добрый программер :)
              З.Ы.(надо как-то попаразитировать)
          0
          Я в шоке!
          Давайте теперь будем оценивать статьи не по содержимому, а по тому как близко он написан к правилам русского языка или как много опечаток в тексте.
            0
            за наличие орфографических ошибок прошу прощения, просто на них как на зло, меньше всего обращал внимания :-(
              0
              Это — одна из болезней Хабра, вместе с "паранойей вирусной рекламы". Пора бы уже привыкнуть. :) Людям скучно, придраться не к чему, а разрядится в кого-нибудь охота. Вот и ноют на каждом углу о черном пиаре и вопиющей безграмотности. Весна...
                0
                Хабр попсеет. Тут все больше людей далеких от IT и поэтому намного интереснй пообсуждать качество сервиса Евросети или про то как плохо работать на дядю. И мне очень жалко что так происходит
                0
                Не стоит. Если статья хорошая, то, по большому счету, плевать сколько там ошибок и как близко он к правилам русского языка написан. Это — хорошая статья.
                0
                Спасибо Вам за просвещение.
                Жаль что люди минусуют статью не за содержание, а за мелочи которые может допустить каждый из нас.
                  0
                  Спасибо, плюсик вам за полезную статью.
                    0
                    Current Graphics Contents из статьи, наверно, на самом деле Current Graphics Context, что дословно переводится как "текущий графический контекст", как это ни странно :)
                      0
                      ну дословно и я перевести могу, но текущий графический контекст как то странновато звучит
                        0
                        Ну, как бы странновато это не звучало, а понятие все же устоявшееся и однозначное.

                        Вот почти полная аналогия из Java:

                        The Graphics class is the abstract base class for all graphics contexts that allow an application to draw onto components that are realized on various devices, as well as onto off-screen images.

                        A Graphics object encapsulates state information needed for the basic rendering operations that Java supports. This state information includes the following properties:
                        * A translation origin for rendering and clipping coordinates.
                        * The current clip.
                        * The current color.
                        * The current font.
                        ...

                        То есть:

                        ...Класс Graphics... — это базовый класс для всех графических контекстов...

                        Объект этого класса содержит информацию о состоянии, которая необходима для простейших операций по отображению...
                          0
                          В-общем как бы да, контент и контекст вещи разные. И в данном случае именно контекст имеется в виду.
                          На русский можно перевести наверное как-то типа "место, где мы в данный момент рисуем".
                        0
                        не совсем понял… почему в реализации класса changeSize и changeColor возвращают void, а в описании класса — IBAction?
                          0
                          на самом деле, IBAction это и есть void, просто его записывают вместо void в тех функция, которые небходимо, чтобы Interface Builder понял как Action.
                          Если мы хотим, чтобы IB увидел член класса, как Outlet, то необходимо перед его объявлением в заголовочном файле написать IBOutlet, а если хотим, чтобы сообщение было восприянто как Action, то надо в возвращаемом значении вписать IBAction

                        Only users with full accounts can post comments. Log in, please.