MVC в Objective-C или калькулятор для iOS 5

Предыстория


Воодушевившись тем, что в недавном времени затарился i девайсами решил научится таки под них программировать. Конечно, в выбранном мной пути web разработчика это мало чем поможет, но мне всегда были интересны новые языки.

Первое на что упал мой взгляд- это виртуальная школа iTunesU. Здесь я нашел лекции курса CS 193P которые ведут разработчики эпл для студентов Стэнфордского университета. Лекции достаточно хорошо охватывают базовые особенности языка и нативных фрэймворков, однако есть одно НО– лекции на английском и никакого перевода к ним нету. Для меня лично проблем это не вызвало, потому что Aple по всей видимости выбирает на эти лекции только кандидатов с хорошо поставленной речью, и прожитые в США 8 месяцев дали мне возможность смотреть лекции в оригинале и учится у «носителей языка» (шучу-шуткую).

Так как каждый год выходит новая версия Xcode, а лекции стэнфорда в которых рассматривается именно текущая версия становятся доступны с опозданием в семестр, я решил написать серию статей, которые будут идти по практической части (!) данного курса. Так как я слушал лекции осени 2010 для меня будет практическт полезным написать программу в xcode 4.2 который был изначально разработан под 3ю версию. Здесь я не буду углублятся в особенности ObjC а буду давать голую практику.

Итак приступим


Для работы я буду использовать Xcode 4.2, iOS SDK 5 и все это будет работать под Mac OS X 10.7.2 Lion.
Как вы молгли заметить из заголовка я (читай– лектор) буду использовать модель данных MVC. Вообще, честно сказать я не понмаю как можено кодить под iOS по другому. Напишем мы программу калькулятор. Простой калькулятор который будет складывать, вычитать, умножать, делить и извлекать квадратный корень из чисел.
Структура программы будет такой:
1. у нас будут мозги калькулятора, которые будут выполнять все действия (класс brain) — модель;
2. у нас будет контроллер, который будет обрабатывать нажатия на кнопки;
3. у нас будет View, в котором всегда возникают проблемы у новоиспеченных iOS кодеров. Об этом подробнее дальше.

Новый проект


Итак начнем с создания прокта. Для этого идем Файл — Новый — Новый проект. Среди общего множества шаблонов выбираем «Single View Application». Я назову приложение Calc. Так же стоит отметить что при создании проекта я снял галочку «Use Storyboard». Для чего это надо: в SDK 5 вместо былого .xib появился новый формат .storyboard. Чем они отличаются я еще не разобрался, поэтому использую по старинке xib файлы для IB (далее Interface Builder).

Итак у нас создался проект. В нем уже есть контроллер и view. Чтобы проверить работоспособность всей системы можно сделать пробный билд. Если все ок, то на экране появится эмулятор айфона и запустится наше приложение с серым экраном.

Для начала нам нужны мозги калькулятора (процессор) которые будут выполнять все опрации. Нажимаем CMD+N и выбираем Objective-C class и назовем его CalcBrain подкласс NSObject класса чтобы наши мозги унаследовали базовые методы. Теперь в проекте на два файла больше: CalcBrain.h и CalcBrain.m

Перейдем к ViewController.h. Там вы не увидите ничего кроме импорта стандартной бибилиотеки UIKit и объявление самого контроллера. Тут нам надо будет объявить следующие вещи:

— outlets: переменные которые буду туказывать на определенные элементы Interface Builder;
— actions: собсственно методы которые будут вызываться при совершении элементами IB определенных действий.
— также нам потребуется объявить переменную нашего нового класса CalcBrain чтобы все это дело заработало.

Итак поехали.

#import <UIKit/UIKit.h>
#import "CalcBrain.h" 

@interface ViewController : UIViewController {
    IBOutlet UILabel *display;
    CalcBrain *brain;
}

-(IBAction)digitPressed:(UIButton *)sender; //++
-(IBAction)operationPressed:(UIButton *)sender; //++

@end


Тут я ничего объяснять не буду, потому что код можно интуитивно понять (если нет — увы, советую сначала почитать про ООП и прочие вкусности мира программистского).

Построение интерфейса


Сейчас мы немного отвлечемся от кода и займемся построением интерфейса. Я считаю что сейча необходимо сосредоточить все свое внимание, потому что на моем опыте много начинающих кодеров без прямой демонстрации действий просто валились именно
на этом этапе и опускали руки. Открываем ViewController.xib и видим уже существующие объекты:
Files's Owner — объект указывающий на класс к которому принодлежит данный xib (в нашем случае это ViewController- это значит что мы сможем использовать только экшены и оутлеты только этого сонтроллера);
First Responder;
View — непостредственно само представление нашего контроллера.

Сначала создадим кнопки. Начнем с цифр.

image

Создаем первую цифру которая в будующем будет семеркой.

image

Чтобы не мучаться дальше, сразу же привяжем к нашей кнопке экшн digitPressed. Для этого надо выбрать нашу кнопку и в Connections Inspector взять кружечек который находится напротив ивента Touch Down и перетащить его на объект File's Owner.

image

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

image

Нам нужен будет digitPressed.
Все что нам осталось это расплодить наш «шаблон кнопки» c зажатым Alt (options). Теперь можно задать нашим цифрам лэйблы. Сделать это можно либо через Attribute inspector либо двойным нажатием на кнопку. Так же не стоит забывать что у нас есть одна кнопка отделения целой части от дробной (и делается это символом "." а не "," как это делаем мы в россии).

image

По той же схеме мы сделаем кнопки операций. Нам понядобятся сложение, вычитание, деление, умножение и извлечение квадратного корня. И еще знак равно который будет выводить результат нашего действия. Все. Теперь у нас есть устройство ввода. Делем устройство вывода, тоесть дисплей. Необходимо создать объект Label, сделать ему выравнивание по левому краю, установить шрифт на 26 и default text «0». Ничего сложного.

image
image

Продолжаем кодить


Больше с интерфейсами мы дела иметь не будем. Остался чистый код.
Кстати если интересно, можно запустить приложение и посмотреть как оно будет выглядеть на эмуляторе.
Приступим к написанию наших мозгов. Они будут работать так:
1 Объект будет получать первый операнд;
2 Объект получает действие;
3 Если действие не извлечение квадратного корня готовимся получать следующий операнд;
4 Если это корень или равно- выводим результат.

Текст файла CalcBrain.h

#import <Foundation/Foundation.h>

@interface CalcBrain : NSObject {
    double operand;
}

-(void)setOperand:(double)aDouble;
-(double)performOperation:(NSString *)operation;

@end


С переменной operand надеюсь нет необходимости объяснять. Методы класса- тут все просто setOperand- это обычный сеттер для переменной operand. Можно было бы использовать и @syntesuze но тогда будет сгенерирован геттер который нам никчему. Так что обойдемся и таким костылем. Теперь опишем все наши методы.

- (void)setOperand:(double)aDouble{
    operand = aDouble;
}
- (double)performOperation:(NSString *)operation{
    if ([operation isEqual:@"sqrt"]) {
        operand = sqrt(operand);
    }
    return operand;
}


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

Дополним класс:

@interface CalcBrain : NSObject {
    double operand;
    NSString *waitingOperation;
    double waitingOperand;
}


И описание:

- (double)performOperation:(NSString *)operation{
    if ([operation isEqual:@"sqrt"]) {
        operand = sqrt(operand);
    } else {
        [self performWaitingOperation];
        waitingOperation = operation;
        waitingOperand = operand;
    }
    return operand;
}


Метод performWaitingOperation будет выполнять непосредственно само действие надо операндами в том случае елси у нас действие не на один операнд (sqrt).

-(void)performWaitingOperation {
    if ([@"+" isEqual:waitingOperation]) {
        operand = waitingOperand + operand;
    } else if ([@"*" isEqual:waitingOperation]) {
        operand = waitingOperand * operand;
    } else if ([@"-" isEqual:waitingOperation]) {
        operand = waitingOperand - operand;
    } else if ([@"/" isEqual:waitingOperation]) {
        if (operand) {
            operand = waitingOperand / operand;
        }
    }
}


Незабываем объявить этот метод в CalcBrain.h

Программируем контроллер


Как это гордо звучит. Но пода перейти к нашему контроллеру а конкретно к заголовочному файлу. Начнем описывать наши уже объявленные методы. Кстати все что создал Xcode в файле ViewController.m можно стереть. Пока. Для данного приложения они нам не понадобятся. Все что нам нужно:

#import "ViewController.h"

@implementation ViewController

-(IBAction)digitPressed:(UIButton *)sender {
    
}
-(IBAction)operationPressed:(UIButton *)sender {
    
}

@end


Теперь пойдем по методам. При нажатии на операции мы должны получить текст с кнопки. Делается это просто: в метод падает кнопка как объект со всеми параметрами:

-(IBAction)operationPressed:(UIButton *)sender {
    NSString *operation = [[sender titleLabel] text];
}


Теперь нам надо запросить наши мозги выполнить эту операцию. Но сначала, нам надо эти мозги создать. Переменную под них мы выделили но еще не объявили:

-(CalcBrain *)brain {
    if (!brain) brain = [[CalcBrain alloc] init];
    return brain;
}


И допишем метод нажатия на операцию, сделаем запрос опирации и вывод результата на дисплей:

-(IBAction)operationPressed:(UIButton *)sender {
    NSString *operation = [[sender titleLabel] text];
    double result = [[self brain] performOperation:operation];
    [display setText: [NSString stringWithFormat:@"%g", result]];
}


Теперь встает логическая проблема- когда пользователь нажимает «5» «5» то он подразумевает «55» а не «5» как поймут наши мозги сейчас. Тоесть нам необходима булевская переменная которая будет идентифицировать находится ли пользователь в процессе ввода или нет.

@interface ViewController : UIViewController {
    IBOutlet UILabel *display;
    CalcBrain *brain;
    BOOL userIsInTheMiddleOfTypingANumber;
}


Теперь можно описать объявление операнда при нажатии на клавишу операции:

-(IBAction)operationPressed:(UIButton *)sender {
    if (userIsInTheMiddleOfTypingANumber) {
        [[self brain] setOperand:[[display text] doubleValue]];
        userIsInTheMiddleOfTypingANumber = NO;
    }
    NSString *operation = [[sender titleLabel] text];
    double result = [[self brain] performOperation:operation];
    [display setText: [NSString stringWithFormat:@"%g", result]];
}


Теперь опишим нажатие на цифру:

-(IBAction)digitPressed:(UIButton *)sender {
    NSString *digit = [[sender titleLabel] text];
    
    if (userIsInTheMiddleOfTypingANumber) {
        [display setText:[[display text] stringByAppendingString:digit]];
    } else {
        [display setText:digit];
        userIsInTheMiddleOfTypingANumber = YES;
    }
}


Тут логика проста: получаем цифру с лэйбла сендера, если stringByAppendingString true значит нам надо присоединить введенную цифру на дисплей. Если нет- просто добавить цифру на дисплей и перещелкнуть параметр stringByAppendingString.

Теперь осталось только Build and Run.

image

Домашнее задание


Вы можете добавить три функции:
Функция ± которая будет переворачивать цисло с отрицательного на положительное;
Функцию сброса результата (С);
И функция повтора операции (когда сделал операцию и повторно нажал на = то повторяется последняя операция с последним операндом);
Поделиться публикацией
Похожие публикации
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 43
  • –5
    О боже. Ну сколько можно уже про хелловорды рассказывать? При чем на таких платформах, на которых они в три клика делаются.
    • +9
      После таких IDE как Qt Creator и Visual Studio немного непривычно работать с XCode. Неплохой пример, демонстрирующий процесс разработки с использованием XCode.
      • –1
        Автор проделал большой труд. Упорядочнено написал про iOS шаги…

        Пользовался QT ,Visual Studio… Когда начинал работать с xCode не хватало такого простого и понятного примера. Спасибо за статью
        • 0
          Не понимаю, что сложного в работе с InterfaceBuilder, все так же, как в той же студии, перетаскиваем объекты драгендропом, по клику на объекте можно править его свойства. Можно даже проще делать, включаем отображение в режиме Assistant, и перетягиваем кнопки прямо в код, IB сам создаст оутлеты или экшены, при этом не забудет подчистить память в деалоке и вьюВилДисапир. Кстати, про управление память в статье вообще ни слова, хоть бы мозги в авторелиз засунули, это же статья для новичков, зачем показывать как делать нельзя.
          • 0
            если статьи делаются по видеолекциям стендорда, то все правильно, так задумано.
            я тоже их сейчас смотрю, разделу управления памятью посвящена отдельная лекция и там в качестве задания было пофиксить утечки памяти
  • +2
    Ну нечитабельный же код. Используйте <source lang="...">...</source>
  • –1
    вообще, если бы внимтельно прелюдию написал я пишу статьи адаптированые с курса cs193p под sdk5. Это учебный стенфорда. Будут и другие статьи. А если хочешь оспорить учебную программу это не ко мне.
    • +4
      Да хоть Кембриджа. Неужели нет более интересных тем, чем рассусоливание хелловордов?
      • –2
        не нравится можешь не читать ) в чем проблема то?
        • –5
          просто чтобы появились интересные проекты — надо научится, и чтобы люди научились, а потом начали писать эти проекты. А если люди не будут уметь, откуда твои интереные темы возьмутся?
          • +4
            1. Если бы ты потрудился полистать темы блога «Разработка под Apple iOS», ты бы нашёл подобные хеловорды.

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

            3. Я конечно могу и не читать, но моё замечание было более конструктивным: может поищешь более интересные темы? Или будешь кропать хеловорды?
            • –2
              ок принял к сведению
              • +1
                Имхо, но чем больше «хеловордов», тем лучше. Когда я только начинал знакомиться с iPhone SDK (тогда он еще так назывался) — было мало хеловордов на русском. И я был бы очень рад находя такие вот хелло ворды.
                • –4
                  я тоде сталкивался с подобными проблемами, особенно с тем, что написатели статей опускали очень много важных вещей. Имеенно по этмоу я сделал подробные скрины для постоения интерфейса
                • +1
                  хеллоуворлды тогда уж…
            • +1
              Несколько дней назад вышли новые курсы с Xcode 4 и iOS5 (по крайней мере первая часть с калькулятором там доступна). Так что смысла этих адаптаций больше нет (разве что перевод).
              • 0
                как раз их ждал… значит буду заниматься просто переводом. Если конечно люди оценивать будут. Если будут встречать таким же негативом просто запихаю к себе в блог :)
                • +2
                  Да не переживайте, продолжайте писать для Хабра.
                  «Негатив» у многих хабраюзеров появляется от того, что «хелловорлд'ная» статья используется для «прохождения» песочницы. Такие статьи безусловно полезны, но «бывалым» хочется видеть среди «нубисов» каких-нибудь случайно откопанных гуру, Вассермана, к примеру.
                  • +1
                    ну, я не виноват что курс начинается с хелоуворда :)
            • +3
              А что будет, если каким-либо случайным образом view будет выгружено из памяти? (например если приложение свернуть и долго не открывать и в это время играть в мощные игрушки). В таком случае неплохо было бы иметь модели метод, который вернет то, что должно быть на «экране» калькулятора. И в viewDidAppear например присваевать это значение label'у.

              Мне кажется, модель у нас немного зависима от представления. Т.е. если мы на view заменим title кнопок с действиями на «сложение», «умножение» и т.д. то конструкции вида:

              if ([@"+" isEqual:waitingOperation]) {
                  operand = waitingOperand + operand;
              }


              Будут выдавать неправильный результат. Может быть лучше добавить для каждой кнопки tag и объявить enum с тагами кнопок-действий?

              enum {
                  kButtonAdd = 1,
                  kButtonDivide,
                  // и так далее
              };
              


              В модель передавать таги и проверять так (или заменить на switch):
              if (waitingOperation == kButtonSum) {
                      operand = waitingOperand + operand;
              }
              
              • –2
                Впринципе согласен с замечанием, так бы было даже правильнее сделать, потому что преобразование string в double всервно будет занимать ресурсы пускай и незначительны, а при написании больших проектов эта привычка даст о себе знать.
                • +2
                  Вообще предложенная модель принадлежит не автору статьи, а лектору Стенфордского университета. Честно говоря, там у них все немного странное, и слушаая лекции, нужно быть хорошо подготовленным, чтобы ко всему относится критично. Поэтому лично я считаю эти лекции плохим стартом для начинающего.
                  • 0
                    Я и не отрицаю что данная модель не моя. Я даже в начале писал что моя статья это всеголишь перевод и некоторая адаптация на sdk5… потому что лекции по sdk5 только только собираются выйти в свет
                • +3
                  Курс Стэнфорда осени 2011
                  • 0
                    О, спасибо за ссылку, никак ни мог найти.
                    • +2
                      Paul Hegartly или как его. Отлично все описал, я по не му учился. Но этот пример это копипаст его лекции, не по хабру.
                    • 0
                      Методы класса- тут все просто setOperand- это обычный сеттер для переменной operand. Можно было бы использовать и @syntesuze но тогда будет сгенерирован геттер который нам никчему.

                      Свойства не обязательно синтезировать. Геттер и сеттер можно реализовать вручную. При этом поле _operand можно использовать автоматически. И в контексте задачи, геттер операнда никому особо бы не помешал.
                      • +2
                        подчеркивания — противоречат code style, они зарезервированы приватными структурами Эппла.

                        Avoid the use of the underscore character as a prefix meaning private, especially in methods. Apple reserves the use of this convention. Use by third parties could result in name-space collisions; they might unwittingly override an existing private method with one of their own, with disastrous consequences. See “Private Methods” for suggestions on conventions to follow for private API.
                        • 0
                          ну, если идти путем минимализации использования данных то геттер будет занимать память- что впринципе ни к чему. На калькуляторе это не скажется, а вот мусор в больших проектах скажется.
                        • +1
                          Осмелюсь сделать замечание — для опытных разработчиков Ваш топик хорош наивностью.
                          Для новичков маловато разжеван — смело добавляйте подсказки что тако IB, и объясняйте в чем его преимущество перед программным созданием контролов.

                          По мельчайшим шагам и с комментариями — вспомните себя при создании первого проекта.
                          • 0
                            Я решил попробовать но до конца не дошел. И вправду для опытных это смех и только, а для первого раза многое непонятно.

                            Дошел до места:

                            «Теперь опишем все наши методы.

                            — (void)setOperand:(double)aDouble{
                            operand = aDouble;
                            }
                            — (double)performOperation:(NSString *)operation{
                            if ([operation isEqual:@»sqrt"]) {
                            operand = sqrt(operand);
                            }
                            return operand;
                            }
                            "

                            И куда это писать? Логично что в CalcBrain.h но когда это пишешь редактор выставляет восклицательные знаки мол ошибка, и все такое. Потому лично для меня это место пока Стоп.
                            • 0
                              Посмотрел один урок на Ютубе и стало все понятно. Думаю для начала нужно всем начинающим хотя бы поглядеть видео примеры чтоб понятно стало как это все работает. Потому как с нуля это непросто.

                              Сейчас допишу по вашему примеру.
                              • 0
                                Описания методов логично писать в @implementation в *.m файле. Начинающим полезно прочитать хотя бы одну книжку по Objective-C и Cocoa, тогда такие вопросы сразу отпадут. На туториалах далеко не уедешь. :)
                          • 0
                            От второй лекции cs139p отличается только небольшим изменением имен классов. Все это уже доступно в iTunesU как курс «Программирование доя iOS5». Качайте и смотрите оригинал…
                            • +1
                              Эх, хороший вроде туториал, а нажатие повешено на touchDown :(
                              touchUpInside же!
                              • 0
                                Верните картинки, пожалуйста. Или это только у меня не отображаются?
                                • 0
                                  Программа очень похожа на первую из стэнфордских лекций 2012 года, оттуда идея?
                                  • –1
                                    не хочу читать все, хочу читать по диагонали и задавать глупые вопросы. В первом абзаце написано что это и откуда.
                                  • 0
                                    Картинки… :(

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

                                    Интересные публикации