Уроки iPhone SDK: (Часть 3) Локализация приложений iPhone: интернационализация. TowerMadness - новый уровень в жанре Tower Defence
Май 28

CocoaTouch с самого начала создавалась с прицелом на парадигму MVC. Практически все шаблоны, представления и их контроллеры для пользователя уже готовы. Ключевые классы — “UIView” и “UIViewController“. Во многих случаях метод “UIView” применим сам по себе — с добавлением элементов пользовательского интерфейса в общий “UIView” в редакторе IB. Для создания собственных функций добавляем подклассы к “UIViewController“. Спецификаторы “IBOutlet” позволяют связывать элементы пользовательского интерфейса с представлением, обеспечивая к ним доступ.

А как быть с понятием “Model”? О нем информации я практически не нашел. В уроках по программированию с моделью предпочитают не работать, набирая код непосредственно в контроллерах.

Добившись, как мне показалось, неплохих результатов с реализацией, я предлагаю их здесь для обсуждения и оценки. Изложу вкратце. Я создаю класс “Singleton“, расширяющий “NSObject” для моей модели. Потом посредством наблюдения за ключами/переменными узнаю об обновлениях. Это во многом напоминает “ModelLocator” из “Cairngorm“, если кому-то приходилось работать с ним во “Flex“.

Для начала создадим проект с парой представлений. Одно из них позволит пользователю менять значение. Это значение задается для модели, которая, в свою очередь, запускает изменение в другом представлении. Для этой цели вполне подойдет шаблон “Utility Application“. Итак, создаем на его основе проект и присваиваем ему имя “MVC“. (В принципе, имя может быть любым, но для удобства работы с уроком лучше его продублировать.)

Результат должен быть примерно таким:

mvc_01

Как видите, перед нами объекты “Main View” и “Flipside View“, причем для обоих есть контроллеры и nib-файлы. Запустите проект и предварительно ознакомьтесь с кодом. Я буду концентрироваться в основном на моментах, касающихся модели, полагая, что остальное и так ясно.

Теперь добавим к представлениям метку и текстовое поле. Метка — для “Main View“, поле, соответственно, для “Flipside View“. Для начала займемся переменными типа outlet.

Файл “MainViewController.h” должен выглядеть так, как показано ниже.

1
2
3
4
5
6
7
8
#import

@interface MainViewController : UIViewController {
UILabel *label;
}

@property (nonatomic, retain) IBOutlet UILabel *label;
@end

а файл “MainViewController.m” — так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#import "MainViewController.h"
#import "MainView.h"

@implementation MainViewController
@synthesize label;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
// Пользовательская инициализация
}
return self;
}

/*
// Внедряем viewDidLoad для дополнительной настройки после загрузки изображения, как правило, из nib-файла.
- (void)viewDidLoad {
[super viewDidLoad];
}
*/


/*
// Отменяем, разрешая другие ориентации помимо заданной по умолчанию книжной.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/


- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning]; // Освобождаем представление, если у него нет superview
// Освобождаем все лишнее, например данные из кэша
}

- (void)dealloc {
[label release];
[super dealloc];
}

@end

Точно так же в файле “FlipsideViewController.h” добавляем переменную “textField“, а также “IBAction“, который даст нам знать об изменении текста в поле

1
2
3
4
5
6
7
8
9
10
#import

@interface FlipsideViewController : UIViewController {
UITextField *textField;
}

@property (nonatomic, retain) IBOutlet UITextField *textField;

- (IBAction)textChanged:(id)sender;
@end

и FlipsideViewController.m:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#import "FlipsideViewController.h"

@implementation FlipsideViewController
@synthesize textField;

- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor viewFlipsideBackgroundColor];
}

/*
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/


- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
// Release anything that's not essential, such as cached data
}

- (IBAction)textChanged:(id)sender
{

}

- (void)dealloc {
[textField release];
[super dealloc];
}

@end

Теперь можно открыть “MainView.xib” и добавить к представлению “UILabel“. Перетащите от объекта “File’s Owner” (класс “MainViewController“) соединительную линию к метке, связав ее с соответствующей переменной.

mvc_02mvc_03

То же самое проделайте в файле “FlipsideView.xib“, добавив элемент “UITextField” и связав его с переменной “textField” для “File’s Owner” (”FlipsideViewController“). Подключите IBActiontextChanged” к событию “editingChanged” текстового поля.

mvc_04mvc_05

К этому времени, наверное, уже стало понятно, что наша цель — ввод пользователем некоего текста в поле, который затем появляется на метке. Но ведь это два отдельных представления со своими контроллерами? Как наладить взаимодействие между ними? С помощью модели, конечно. Сохранив оба nib-файла, закройте редактор IB и вернитесь в XCode.

Модель.

Настало время создать модель. Добавьте к проекту новый файл — подкласс для “NSObject“. Дайте классу имя “Model“. Не то, чтобы я был ярым поклонником шаблонов “Singleton“. Я прекрасно осведомлен об их недостатках и считаю, что порой их задействуют неоправданно, но как прагматик, уверен, что в данном случае это приемлемый вариант.

Те, кому невыносима сама мысль о синглетонах, могут создать модель в “RootViewController” и передать по экземпляру каждому из контроллеров представления. Это должно сработать. Я же выберу другой способ, воспользовавшись остроумным файлом “SynthesizeSingleton” с [CocoaWithLove.com]. Кстати, там же можно высказать свое отношение к синглетонам.

Теперь добавьте к проекту файл “SynthesizeSingleton.h” и вызовите макрос “SYNTHESIZE_SINGLETON_FOR_CLASS” в реализации класса, который хотите сделать синглетоном.

Я заметил, что работа с данным макросом всегда вызывает предупреждение, если только не объявить статичный метод “sharedModel” в файле интерфейса.

Нашей модели также понадобятся одно единственное свойство, текст и пользовательские средства доступа. Вот файл “Model.h“:

1
2
3
4
5
6
7
8
9
10
#import <Foundation/Foundation.h>

@interface Model : NSObject {
NSString *text;
}

@property (nonatomic, retain) NSString *text;

+ (Model *)sharedModel;
@end

А вот и “Model.m“:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#import "Model.h"
#import "SynthesizeSingleton.h"

@implementation Model

SYNTHESIZE_SINGLETON_FOR_CLASS(Model);

@synthesize text;

- (id) init
{
self = [super init];
if (self != nil) {
text = @"";
}
return self;
}

- (void) dealloc
{
[text release];
[super dealloc];
}

@end

Настройка данных для модели.

При изменении текста в текстовом поле будет вызываться метод “textChanged” в контроллере “FlipsideViewController“. Здесь можно присвоить новое значение свойству текста модели.

1
2
3
4
5
- (IBAction)textChanged:(id)sender
{
Model *model = [Model sharedModel];
model.text = textField.text;
}

Обязательно импортируйте “Model.h” в “FlipsideViewController.m“.

Фиксация изменений.

Основному представлению достаточно сообщить, когда будет меняться модель. Это можно сделать путем наблюдения за ключом/значением. Соответственно, нужно настроить наблюдателя на определенное свойство объекта, чтобы он сразу же вызывал метод в случае его изменения. Здесь нам нужно узнать, когда поменяется текстовое свойство для класса модели синглетона. Воспользуемся для этой цели методом “initWithNibName” класса “MainViewController“. Вот как он выглядит:

1
2
3
4
5
6
7
8
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
// Пользовательская инициализация
Model *model = [Model sharedModel];
[model addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew context:nil];
}
return self;
}

Для начала получаем ссылку на синглетон “Model“, после чего вызываем метод “addObserver:forKeyPath:options:context“. Обозревателем является “self“, класс “MainViewController”. “keyPath” — свойство, за которым будет вестись наблюдение, текстовая строка символов. Эта опции позволяют нам указать, какие именно данные будут меняться. Здесь это новое значение для свойства. При желании можно запросить начальное, устаревшее или предыдущее значение, а также любую их комбинацию. В качестве контекста можно добавить ноль.

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

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

keyPath” — имя свойства как строки, “object” — объект, являющийся владельцем данного свойства (в данном случае, модели), “change” — словарь, хранящий указанные старое, новое, предыдущее и начальное значения, “context” — любой соответствующий случаю контекст (у нас — ноль).

Если стоит цель наблюдать сразу за несколькими свойствами модели, потребуется оператор выбора с “keyPath“, сообщающий о том, какое именно свойство поменялось. Пока мы просто просматриваем текст, поэтому и так это знаем. Можно получить новое значение свойства, отправив запрос на изменение параметров словаря. Еще один вариант — напрямую добраться к свойству текста модели, но мы воспользуемся приведенными ниже данными:

1
2
3
4
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
label.text = [change valueForKey:@"new"];
}

Здесь мы спрашиваем у объекта изменения его “новое” значение (которым будет все его содержимое, поскольку именно такой вариант нами задан). Присваиваем результат “label.text” — и все готово.

Ну вот и все. У нас есть модель, хранящая данные и сообщающая о событиях при их изменении. Обратите внимание: в парадигме Cocoa MVC представления обычно не следят непосредственно за моделью. Этим занимаются контроллеры представлений, и они же сообщают представлениям, что делать. Такой подход не имеет статуса закона, но является общепринятым.

Я хотел разобраться еще с одной концепцией — с использованием “NSNotification” вместо наблюдения за ключом/значением. Не уверен, что это действительно достойная альтернатива, и какие у нее могут быть плюсы и минусы.

Рассмотренный выше пример практической ценности не имеет — это всего лишь иллюстрация. В реальном приложении многое можно было бы сделать по-другому, например, не фиксировать каждое событие редактирования, переходя к нему только после закрытия представления “Flipside“. Но цель нашего урока была совсем не в этом, а в наглядном внедрении модели (по крайней мере, ОДНИМ из способов).

Спасибо и успехов!

Исходный код скачать можно [здесь]

Текст оригинальной статьи на английском языке [здесь]

Уважаемые читатели, данный материал был переведен и подготовлен к публикации проектом LookApp.ru, при публикации на другом сайте ссылка на LookApp.ru обязательна.

1 звезда2 звезд3 звезд4 звезд5 звезд (Оцените приложение)
Загрузка ... Загрузка ...

One Ping to “Уроки iPhone SDK: (Часть 1) MVC на iPhone: “The Model””

  1. Уроки iPhone SDK: (Часть 2) И еще об MVC на iPhone | LookApp.ru - обзоры программ и игр для iPhone Says:

    [...] В предыдущей статье я рассказал о настройке модели, узнать об изменениях свойств которой можно с помощью наблюдения за ключом/значением. С предложенным методом связана одна теоретическая проблема, на которую уже указали читатели: за все изменения модели отвечает всего один метод — “observeValueForKeyPath:ofObject:change:context:“. Как следствие, наблюдая за несколькими свойствами, получаем скопление операторов “if/else” или посредством оператора выбора выясняем, какие же именно изменения только что произошли. Поразмышляв об этом, я вспомнил о предложении передавать селекторы в качестве контекста при обращении к “addObserver“. Рассмотрим, как именно это делается. [...]


Оставьте комментарий