Современная электронная библиотека ModernLib.Net

Бестселлеры O`Reilly - iOS. Приемы программирования

ModernLib.Net / Программирование / Вандад Нахавандипур / iOS. Приемы программирования - Чтение (Ознакомительный отрывок) (стр. 7)
Автор: Вандад Нахавандипур
Жанр: Программирование
Серия: Бестселлеры O`Reilly

 

 


Итак, предположим, что мы хотим создать функцию, способную обратить любое количество переданных ей строк. Как вы помните, когда ваше приложение инициализирует контроллер вида с функцией с помощью метода initWithActivityItems: applicationActivities:, он может передать в первом параметре этого метода массив объектов произвольных типов. Поэтому если в функции планируется просмотреть все объекты, находящиеся в этом произвольном массиве, и если все они окажутся строками, то функция обратит их и отобразит все полученные строки в окне (виде) с предупреждением.

1. Произведите подкласс от UIActivity следующим образом:


#import 

@interface StringReverserActivity: UIActivity

@end


2. Поскольку мы собираемся выводить в нашей функции вид с предупреждением и отображать его для пользователя, когда нам будет передан массив строк, мы должны гарантировать соответствие нашей функции протоколу UIAlertViewDelegate. Когда пользователь закроет окно с предупреждением, мы должны пометить нашу функцию как завершенную, вот так:


#import "StringReverserActivity.h"


@interface StringReverserActivity () 

@property (nonatomic, strong) NSArray *activityItems;

@end


@implementation StringReverserActivity


– (void) alertView:(UIAlertView *)alertView

didDismissWithButtonIndex:(NSInteger)buttonIndex{

[self activityDidFinish: YES];

}


3. Далее переопределим метод activityType нашей функции. Возвращаемое значение этого метода представляет собой объект типа NSString, являющийся уникальным идентификатором этой функции. Это значение не будет отображаться для пользователя – оно применяется только на уровне системы iOS для отслеживания идентификатора функции. Нет никаких особых значений, которые требовалось бы возвращать от этого метода, нет также никаких сопутствующих рекомендаций от Apple, но мы будем работать со строками в формате «обратное доменное имя», использовать идентификатор пакета приложения и прикреплять к нему имя нашего класса. Итак, если имеется идентификатор пакета com.pixolity.ios.cookbook.myapp и класс с именем StringReverserActivity, то мы возвратим от этого метода строку com.pixolity.ios.cookbook.myapp.StringReverserActivity, вот так:


– (NSString *) activityType{

return [[NSBundle mainBundle].bundleIdentifier

stringByAppendingFormat:@".%@", NSStringFromClass([self class])];

}


4. Следующий метод, который придется переопределить, называется activityTitle. В нем мы собираемся возвращать строку, которую будем отображать для пользователя в контроллере вида с функцией. Необходимо, чтобы эта строка получилась не слишком длинной и уместилась в нашем контроллере вида:


– (NSString *) activityTitle{

return @"Reverse String";

}


5. Переходим к методу activityImage, который должен возвращать нам экземпляр UIImage – то самое изображение, что будет выводиться в контроллере вида с функцией. Обязательно предоставляйте по два варианта изображения – для сетчаточного дисплея и для обычного – как для iPad, так и для iPhone/iPod. Разрешение сетчаточного изображения для iPad должно составлять 110 x 110 пикселов, а для iPhone – 86 x 86 пикселов. Неудивительно, что, разделив эти значения на 2, получим ширину и высоту обычных изображений. В этом изображении iOS использует только альфа-канал, поэтому убедитесь, что фон вашего изображения является прозрачным и что вы иллюстрируете его черным или белым цветом. Я уже создал изображение в разделе с ресурсами моего приложения и назвал его Reverse (Обратное). Вы можете ознакомиться с ним на рис. 1.29. А вот и код:


– (UIImage *) activityImage{

return [UIImage imageNamed:@"Reverse"];

}


Рис. 1.29. В категории Ресурсы содержатся изображения для создаваемой специальной функции


6. Реализуем метод canPerformWithActivityItems: нашей функции. Параметр этого метода содержит массив, который будет задан, когда метод-инициализатор контроллера вида с функцией получит массив компонентов функции. Не забывайте, что тип каждого из объектов данного массива является произвольным. Возвращаемое значение данного метода является логическим и указывает, можем ли мы произвести такую функцию над каждым конкретным элементом массива. Например, наша функция может обратить любое количество данных ей строк. То есть если мы найдем в массиве одну строку, это будет нам на руку, поскольку мы будем точно знать, что впоследствии сможем обратить эту строку. Но если мы получим массив из 1000 объектов, ни один из которых не будет относиться к приемлемому для нас типу, мы отклоним такой запрос, вернув NO от данного метода:


– (BOOL) canPerformWithActivityItems:(NSArray *)activityItems{


for (id object in activityItems){

if ([object isKindOfClass: [NSString class]]){

return YES;

}

}


return NO;

}


7. Теперь реализуем метод prepareWithActivityItems: нашей функции, чей параметр относится к типу NSArray. Этот метод вызывается, если вы возвращаете YES от метода canPerformWithActivityItems:. Придется сохранить данный массив для последующего использования. Но на самом деле можно сохранять не весь массив, а только часть его объектов – те, что относятся к интересующему вас типу. Например, строки:


– (void) prepareWithActivityItems:(NSArray *)activityItems{


NSMutableArray *stringObjects = [[NSMutableArray alloc] init];

for (id object in activityItems){

if ([object isKindOfClass: [NSString class]]){

[stringObjects addObject: object];

}

}


self.activityItems = [stringObjects copy];

}


8. Последнее, но немаловажное: потребуется реализовать метод performActivity нашей функции, который вызывается, если iOS требует от нас произвести выбранные действия над списком ранее предоставленных произвольных объектов. В функции мы собираемся перебрать массив строковых объектов, извлеченных из массива с произвольными типами, обратить их все и отобразить для пользователя в окне с предупреждением:


– (NSString *) reverseOfString:(NSString *)paramString{


NSMutableString *reversed = [[NSMutableString alloc]

initWithCapacity: paramString.length];


for (NSInteger counter = paramString.length – 1;

counter >= 0;

counter—){

[reversed appendFormat:@"%c", [paramString characterAtIndex: counter]];

}


return [reversed copy];


}


– (void) performActivity{


NSMutableString *reversedStrings = [[NSMutableString alloc] init];


for (NSString *string in self.activityItems){

[reversedStrings appendString: [self reverseOfString: string]];

[reversedStrings appendString:@"\n"];

}


UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Reversed"

message: reversedStrings

delegate: self

cancelButtonTitle:@"OK"

otherButtonTitles: nil];


[alertView show];

}


Итак, реализация класса нашей функции завершена. Перейдем к файлу реализации контроллера вида и отобразим контроллер вида функции в списке с нашей специальной функцией:


#import "ViewController.h"

#import "StringReverserActivity.h"


@implementation ViewController


– (void) viewDidAppear:(BOOL)animated{

[super viewDidAppear: animated];


NSArray *itemsToShare = @[

@"Item 1",

@"Item 2",

@"Item 3",

];


UIActivityViewController *activity =

[[UIActivityViewController alloc]

initWithActivityItems: itemsToShare

applicationActivities:@[[StringReverserActivity new]]];


[self presentViewController: activity animated: YES completion: nil];

}

@end


При первом запуске приложения на экране появится картинка, примерно такая, как на рис. 1.30.


Рис. 1.30. Специальная функция для обращения строк теперь находится в списке доступных функций


Если теперь вы нажмете в этом списке элемент Reverse String (Обращенная строка), то увидите нечто похожее на рис. 1.31.


Рис. 1.31. Наша функция для обращения строк в действии

<p>См. также</p>

Раздел 1.10.

1.12. Внедрение навигации с помощью UINavigationController

<p>Постановка задачи</p>

Необходимо дать пользователю возможность переходить от одного контроллера вида к другому, сопровождая этот процесс плавной анимацией, интегрированной в программу.

<p>Решение</p>

Используйте экземпляр класса UINavigationController.

<p>Обсуждение</p>

Если вам доводилось работать с iPhone, iPod touch или iPad, то вы, скорее всего, уже видели в действии навигационный инструмент управления. Например, если перейти в приложение Settings (Настройки) телефона, там можно выбрать команду Wallpaper (Обои) (рис. 1.32). В таком случае вы увидите, как основной экран программы Settings (Настройки) отодвигается влево, а на его место справа выходит экран Wallpaper (Обои). В этом и заключается самая интересная черта навигации iPhone. Вы можете складывать контроллеры видов в стек и поднимать их из стека. Контроллер вида, в данный момент находящийся на верхней позиции стека, виден пользователю. Итак, только самый верхний контроллер вида показывается зрителю, а чтобы отобразить другой контроллер, нужно либо удалить с верхней позиции контроллер, видимый в настоящий момент, либо поместить на верхнюю позицию в стеке новый контроллер вида.


Рис. 1.32. Контроллер вида настроек, отодвигающий вид с обоями для экрана


Теперь добавим в новый проект навигационный контроллер. Но сначала нужно создать проект. Выполните шаги, описанные в разделе 1.9, чтобы создать пустое приложение с простым контроллером вида. Данный раздел – расширенная версия работы, выполненной в разделе 1.9. Начнем с файла реализации (.m) делегата нашего приложения:


#import "AppDelegate.h"

#import "FirstViewController.h"


@interface AppDelegate ()

@property (nonatomic, strong) UINavigationController *navigationController;

@end


@implementation AppDelegate


Теперь следует инициализировать навигационный контроллер, воспользовавшись его методом initWithRootViewController:, и передать корневой контроллер нашего вида как параметр этого метода. Далее мы зададим навигационный контроллер в в качестве корневого контроллера вида в нашем окне. Здесь главное – не запутаться. UINavigationController – это фактически подкласс UIViewController, а свойство rootViewController, относящееся к нашему окну, принимает любой объект типа UIViewController. Таким образом, если мы хотим сделать навигационный контроллер корневым контроллером нашего вида, мы просто должны задать его в качестве корневого контроллера:


– (BOOL) application:(UIApplication *)application

didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{


FirstViewController *viewController = [[FirstViewController alloc]

initWithNibName: nil

bundle: nil];


self.navigationController = [[UINavigationController alloc]

initWithRootViewController: viewController];


self.window = [[UIWindow alloc]

initWithFrame: [[UIScreen mainScreen] bounds]];


self.window.rootViewController = self.navigationController;


self.window.backgroundColor = [UIColor whiteColor];

[self.window makeKeyAndVisible];

return YES;

}


После этого запустим приложение в эмуляторе (рис. 1.33).


Рис. 1.33. Пустой контроллер вида, отображаемый внутри навигационного контроллера

Файл реализации корневого контроллера вида создает кнопку в центре экрана (как показано на рис. 1.33). Чуть позже мы изучим этот файл реализации.

На рис. 1.33 мы в первую очередь замечаем полосу в верхней части экрана. Теперь экран уже не чисто-белый. Что это за новый виджет? Это навигационная панель. Мы будем активно пользоваться ею при навигации, например разместим на ней кнопки и сделаем кое-что еще. Кроме того, на этой панели удобно отображать заголовок. Каждый контроллер вида сам для себя указывает заголовок, а навигационный контроллер будет автоматически отображать заголовок того контроллера вида, который окажется на верхней позиции в стеке.

Переходим к файлу реализации корневого контроллера нашего вида в методе viewDidLoad. В качестве свойства контроллера вида укажем First Controller. Здесь же создадим кнопку. Когда пользователь нажмет эту кнопку, мы отобразим на экране второй контроллер вида:


#import "FirstViewController.h"

#import "SecondViewController.h"


@interface FirstViewController ()

@property (nonatomic, strong) UIButton *displaySecondViewController;

@end


@implementation FirstViewController


– (void) performDisplaySecondViewController:(id)paramSender{

SecondViewController *secondController = [[SecondViewController alloc]

initWithNibName: nil

bundle: NULL];

[self.navigationController pushViewController: secondController

animated: YES];

}


– (void)viewDidLoad{

[super viewDidLoad];

self.title = @"First Controller";


self.displaySecondViewController = [UIButton

buttonWithType: UIButtonTypeSystem];


[self.displaySecondViewController

setTitle:@"Display Second View Controller"

forState: UIControlStateNormal];


[self.displaySecondViewController sizeToFit];

self.displaySecondViewController.center = self.view.center;

[self.displaySecondViewController

addTarget: self

action:@selector(performDisplaySecondViewController:)

forControlEvents: UIControlEventTouchUpInside];


[self.view addSubview: self.displaySecondViewController];

}


@end


А теперь создадим второй контроллер вида, уже без файла XIB, и назовем его SecondViewController. Проделайте тот же процесс, что был показан в разделе 1.9. Когда создадите этот контроллер вида, назовите его Second Controller:


#import "SecondViewController.h"


@implementation SecondViewController


– (void)viewDidLoad{

[super viewDidLoad];

self.title = @"Second Controller";

}


Теперь мы собираемся всплыть из второго контроллера вида обратно в первый контроллер вида через 5 секунд после того, как первый контроллер вида окажется на экране. Для этого используем метод performSelector: withObject: afterDelay: объекта NSObject, чтобы вызвать новый метод goBack. Второй метод будет вызван через 5 секунд после того, как контроллер первого вида успешно отобразит на экране этот первый вид. В методе goBack просто используем свойство navigationController контроллера вида (а оно встроено в UIViewController, и нам самим не приходится его писать), чтобы вернуться к экземпляру FirstViewController. Для этого воспользуемся методом popViewControllerAnimated: навигационного контроллера, который принимает в качестве параметра логическое значение. Если этот параметр имеет значение YES, то переход к предыдущему контроллеру вида будет анимироваться, если NO – не будет. В результате мы увидим примерно такую картинку, как на рис. 1.34.


Рис. 1.34. Контроллер вида размещается поверх другого контроллера вида


#import "SecondViewController.h"


@implementation SecondViewController


– (void)viewDidLoad{

[super viewDidLoad];

self.title = @"Second Controller";

}


– (void) goBack{

[self.navigationController popViewControllerAnimated: YES];

}


– (void) viewDidAppear:(BOOL)paramAnimated{

[super viewDidAppear: paramAnimated];

[self performSelector:@selector(goBack)

withObject: nil

afterDelay:5.0f];


}


@end


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

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

<p>См. также</p>

Раздел 1.9.

1.13. Управление массивом контроллеров видов, относящихся к навигационному контроллеру

<p>Постановка задачи</p>

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

<p>Решение</p>

Воспользуйтесь свойством viewControllers из класса UINavigationController для доступа к массиву контроллеров видов, связанных с навигационным контроллером, а также для изменения этого массива:


– (void) goBack{

/* Получаем актуальный массив контроллеров видов. */

NSArray *currentControllers = self.navigationController.viewControllers;


/* Создаем на основе этого массива изменяемый массив. */

NSMutableArray *newControllers = [NSMutableArray

arrayWithArray: currentControllers];


/* Удаляем последний объект из массива. */

[newControllers removeLastObject];


/* Присваиваем этот массив навигационному контроллеру. */

self.navigationController.viewControllers = newControllers

}


Этот метод можно вызвать внутри любого контроллера вида, чтобы поднять последний контроллер вида из иерархии навигационного контроллера, связанного с контроллером вида, который отображается в настоящий момент.

<p>Обсуждение</p>

Экземпляр класса UINavigationController содержит массив объектов UIViewController. Получив этот массив, вы можете оперировать им как угодно. Например, можно удалить контроллер вида из произвольного места в массиве.

Если мы напрямую управляем контроллерами видов, связанными с навигационным контроллером, то есть путем присвоения массива свойству viewControllers навигационного контроллера, то весь процесс будет протекать без явного перехода между контроллерами и без анимации. Если вы хотите, чтобы эти действия анимировались, используйте метод setViewControllers: animated:, относящийся к классу UINavigationController, как показано в следующем фрагменте кода:


– (void) goBack{

/* Получаем актуальный массив контроллеров видов. */

NSArray *currentControllers = self.navigationController.viewControllers;


/* Создаем на основе этого массива изменяемый массив. */

NSMutableArray *newControllers = [NSMutableArray

arrayWithArray: currentControllers];


/* Удаляем последний объект из массива. */

[newControllers removeLastObject];


/* Присваиваем этот массив навигационному контроллеру. */

[self.navigationController setViewControllers: newControllers

animated: YES];

}

1.14. Демонстрация изображения на навигационной панели

<p>Постановка задачи</p>

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

<p>Решение</p>

Воспользуйтесь свойством titleView навигационного элемента контроллера вида:


– (void)viewDidLoad{

[super viewDidLoad];


/* Создаем вид с изображением, заменяя им вид с заголовком. */

UIImageView *imageView =

[[UIImageView alloc]

initWithFrame: CGRectMake(0.0f, 0.0f, 100.0f, 40.0f)];


imageView.contentMode = UIViewContentModeScaleAspectFit;


/* Загружаем изображение. Внимание! Оно будет кэшироваться. */

UIImage *image = [UIImage imageNamed@"Logo"];


/* Задаем картинку для вида с изображением. */

[imageView setImage: image];


/* Задаем вид с заголовком. */

self.navigationItem.titleView = imageView;


}

Предыдущий код должен выполняться в контроллере вида, находящемся внутри навигационного контроллера.

Я уже загрузил изображение в группу ресурсов моего проекта и назвал это изображение Logo. Как только вы запустите это приложение с приведенным фрагментом кода, увидите результат, напоминающий рис. 1.35.

Рис. 1.35. Вид с изображением на нашей навигационной панели

<p>Обсуждение</p>

Навигационный элемент каждого конкретного контроллера вида может отображать два различных вида контента в той области контроллера вида, которой этот элемент присвоен:

• обычный текст;

• вид.


Если вы собираетесь работать с текстом, можете использовать свойство title навигационного элемента. Тем не менее, если вам требуется более полный контроль над заголовком или вы просто хотите вывести над навигационной панелью изображение или любой другой вид, можете использовать свойство titleView навигационного элемента контроллера вида. Ему можно присваивать любой объект, являющийся подклассом класса UIView. В примере мы создали вид для изображения, а затем присвоили ему изображение. Потом вывели это изображение в качестве заголовка вида, в настоящий момент находящегося на навигационном контроллере.

Свойство titleView навигационной панели – это самый обычный вид, но Apple рекомендует, чтобы его высота не превышала 128 точек. Поэтому считайте его изображением. Если бы вы загружали изображение, имеющее высоту 128 пикселов, то на сетчатом дисплее это соответствовало бы 64 точкам и все было бы нормально. Но если бы вы загружали изображение высотой 300 пикселов на сетчатом дисплее, то по высоте оно заняло бы 150 точек, то есть заметно превысило бы те 128 точек, которые Apple рекомендует для видов, расположенных в строке заголовка. Для исправления этой ситуации необходимо гарантировать, что вид в строке заголовка по высоте ни в коем случае не окажется больше 128 точек, а также задать для контента режим заполнения вида целиком, а не подгонки вида под содержимое. Для этого можно установить свойство contentMode вашей строки заголовка в UIViewContentModeScaleAspectFit.

1.15. Добавление кнопок на навигационные панели с помощью UIBsrButtonItem

<p>Постановка задачи</p>

Необходимо добавить кнопки на навигационную панель.

<p>Решение</p>

Используйте класс UIBarButtonItem.

<p>Обсуждение</p>

На навигационной панели могут содержаться различные элементы. Кнопки часто отображаются в ее левой и правой частях. Такие кнопки относятся к классу UIBarButtonItem и могут принимать самые разнообразные формы и очертания. Рассмотрим пример, показанный на рис. 1.36.


Рис. 1.36. Различные кнопки, отображаемые на навигационной панели


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


  • Страницы:
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15