#import <Foundation/Foundation.h>
   #import "Car.h"
 
   @interface Jaguar: NSObject <Car>
   @
   end
 
   Если вы попробуете собрать ваш проект на данном этапе, то компилятор выдаст вам несколько предупреждений, например такое:
   Auto property synthesis will not synthesize property declared in a protocol
   Это означает, что ваш класс Jaguar пытается соответствовать протоколу Car, но на самом деле не реализует всех требуемых свойств и/или методов, описанных в этом протоколе. Теперь вы уже знаете, что в протоколе могут содержаться необходимые и факультативные (опциональные) элементы, которые вы помечаете ключевыми словами @optional или @required. По умолчанию действует квалификатор @required, и поскольку мы явно не указываем квалификатор для этого протокола, компилятор неявно выбирает @required за нас. Следовательно, класс Jaguar теперь обязан реализовывать все аспекты, требуемые протоколом Car, вот так:
 
   #import <Foundation/Foundation.h>
   #import "Car.h"
 
   @interface Jaguar: NSObject <Car>
 
   @property (nonatomic, copy) NSArray *wheels;
   @property (nonatomic, strong) UIColor *bodyColor;
   @property (nonatomic, copy) NSArray *doors;
 
   @end
 
   Отлично. Теперь мы понимаем основы работы с протоколами, то, как они работают и как их определить. Далее в этой книге мы подробнее поговорим о протоколах, а на данный момент вы получили довольно полное представление о них.

Хранение элементов в коллекциях и получение элементов из коллекций

   Коллекции – это такие объекты, в экземплярах которых могут храниться другие объекты. Одна из самых распространенных разновидностей коллекций – это массив, который инстанцирует NSArray или NSMutableArray. В массиве можно хранить любой объект, причем массив может содержать несколько экземпляров одного и того же объекта. В следующем примере мы создаем массив из трех строк:
 
   NSArray *stringsArray = @[
   @"String 1",
   @"String 2",
   @"String 3"
   ];
 
   __unused NSString *firstString = stringsArray[0];
   __unused NSString *secondString = stringsArray[1];
   __unused NSString *thirdString = stringsArray[2];
   Макрос __unused приказывает компилятору «не жаловаться», когда переменная – в нашем случае переменная firstString – объявлена, но ни разу не использовалась. По умолчанию в такой ситуации компилятор выдает в консоль предупреждение, сообщающее, что переменная не используется. В нашем кратком примере мы объявили переменные, но не задействовали их. Поэтому, если добавить вышеупомянутый макрос в начале объявления переменной, это вполне устроит и нас, и компилятор.
   Изменяемый массив – это такой массив, в который можно вносить изменения уже после того, как он был создан. Как мы видели ранее, неизменяемый массив не может быть дополнен новой информацией уже после создания. Вот пример неизменяемого массива:
 
   NSString *string1 = @"String 1";
   NSString *string2 = @"String 2";
   NSString *string3 = @"String 3";
 
   NSArray *immutableArray = @[string1, string2, string3];
 
   NSMutableArray *mutableArray = [[NSMutableArray alloc]
   initWithArray: immutableArray];
 
   [mutableArray exchangeObjectAtIndex:0 withObjectAtIndex:1];
   [mutableArray removeObjectAtIndex:1];
   [mutableArray setObject: string1 atIndexedSubscript:0];
   NSLog(@"Immutable array = %@", immutableArray);
   NSLog(@"Mutable Array = %@", mutableArray);
   Вывод этой программы таков:
   Immutable array = (
   "String 1",
   "String 2",
   "String 3"
   )
   Mutable Array = (
   "String 1",
   "String 3"
   )
 
   Еще одна распространенная коллекция, которая часто встречается в программах для iOS, – это словарь. Словари похожи на массивы, но каждому объекту в словаре присваивается ключ, и по этому ключу вы можете позже получить интересующий вас объект. Рассмотрим пример:
 
   NSDictionary *personInformation =
   @{
   @"firstName": @"Mark",
   @"lastName": @"Tremonti",
   @"age": @30,
   @"sex": @"Male"
   };
 
   NSString *firstName = personInformation[@"firstName"];
   NSString *lastName = personInformation[@"lastName"];
   NSNumber *age = personInformation[@"age"];
   NSString *sex = personInformation[@"sex"];
 
   NSLog(@"Full name = %@ %@", firstName, lastName);
   NSLog(@"Age = %@, Sex = %@", age, sex);
   А вот и вывод этой программы:
   Full name = Mark Tremonti
   Age = 30, Sex = Male
 
   Можно также использовать изменяемые словари, которые довольно сильно похожи на изменяемые массивы. Содержимое изменяемого словаря можно изменить после того, как словарь инстанцирован. Пример:
 
   NSDictionary *personInformation =
   @{
   @"firstName": @"Mark",
   @"lastName": @"Tremonti",
   @"age": @30,
   @"sex": @"Male"
   };
 
   NSMutableDictionary *mutablePersonInformation =
   [[NSMutableDictionary alloc] initWithDictionary: personInformation];
   mutablePersonInformation[@"age"] = @32;
 
   NSLog(@"Information = %@", mutablePersonInformation);
   Вывод этой программы таков:
   Information = {
   age = 32;
   firstName = Mark;
   lastName = Tremonti;
   sex = Male;
   }
 
   Еще можно работать с множествами. Множества похожи на массивы, но любой объект, входящий в состав множества, должен встречаться в нем только один раз. Иными словами, в одном множестве не может быть двух экземпляров одного и того же объекта. Пример множества:
 
   NSSet *shoppingList = [[NSSet alloc] initWithObjects:
   @"Milk",
   @"Bananas",
   @"Bread",
   @"Milk", nil];
 
   NSLog(@"Shopping list = %@", shoppingList);
   Запустив эту программу, вы получите следующий вывод:
   Shopping list = {(
   Milk,
   Bananas,
   Bread
   )}
 
   Обратите внимание: элемент Milk упомянут в программе дважды, а в множество добавлен всего один раз. Эта черта множеств – настоящее волшебство. Изменяемые множества можно использовать и вот так:
 
   NSSet *shoppingList = [[NSSet alloc] initWithObjects:
   @"Milk",
   @"Bananas",
   @"Bread",
   @"Milk", nil];
 
   NSMutableSet *mutableList = [NSMutableSet setWithSet: shoppingList];
 
   [mutableList addObject:@"Yogurt"];
   [mutableList removeObject:@"Bread"];
   NSLog(@"Original list = %@", shoppingList);
   NSLog(@"Mutable list = %@", mutableList);
   А вывод будет таким:
   Original list = {(
   Milk,
   Bananas,
   Bread
   )}
   Mutable list = {(
   Milk,
   Bananas,
   Yogurt
   )}
 
   Обсуждая множества и коллекции, следует упомянуть еще два важных класса, о которых вам необходимо знать:
    NSOrderedSet – неизменяемое множество, учитывающее, в каком порядке в него добавлялись объекты;
   • NSMutableOrderedSet – изменяемый вариант вышеупомянутого изменяемого множества.
   По умолчанию множества не учитывают, в каком порядке объекты в них добавлялись. Рассмотрим пример:
 
   NSSet *setOfNumbers = [NSSet setWithArray:@[@3, @4, @1, @5, @10]];
   NSLog(@"Set of numbers = %@", setOfNumbers);
   Запустив эту программу, получим на экране следующий вывод:
   Set of numbers = {(
   5,
   10,
   3,
   4,
   1
   )}
 
   Но на самом деле мы наполняли множество элементами в другом порядке. Если вы хотите сохранить правильный порядок, просто воспользуйтесь классом NSOrderedSet:
 
   NSOrderedSet *setOfNumbers = [NSOrderedSet orderedSetWithArray
   :@[@3, @4, @1, @5, @10]];
 
   NSLog(@"Ordered set of numbers = %@", setOfNumbers);
   Разумеется, вы можете воспользоваться и изменяемой версией упорядоченного множества:
   NSMutableOrderedSet *setOfNumbers =
   [NSMutableOrderedSet orderedSetWithArray:@[@3, @4, @1, @5, @10]];
 
   [setOfNumbers removeObject:@5];
   [setOfNumbers addObject:@0];
   [setOfNumbers exchangeObjectAtIndex:1 withObjectAtIndex:2];
 
   NSLog(@"Set of numbers = %@", setOfNumbers);
   А вот и результаты:
   Set of numbers = {(
   3,
   1,
   4,
   10,
   0
   )}
 
   Прежде чем завершить разговор о множествах, упомяну еще об одном удобном классе, который может вам пригодиться. Класс NSCountedSet может несколько раз содержать уникальный экземпляр объекта. Правда, в нем эта задача решается иначе, нежели в массивах. В массиве может несколько раз присутствовать один и тот же объект. А в рассматриваемом здесь «подсчитываемом множестве» каждый объект появляется в множестве как будто заново, но множество ведет подсчет того, сколько раз объект был добавлен в множество, и снижает значение этого счетчика на единицу, как только вы удалите из этого множества экземпляр данного объекта. Вот пример:
 
   NSCountedSet *setOfNumbers = [NSCountedSet setWithObjects:
   @10, @20, @10, @10, @30, nil];
 
   [setOfNumbers addObject:@20];
   [setOfNumbers removeObject:@10];
 
   NSLog(@"Count for object @10 = %lu",
   (unsigned long)[setOfNumbers countForObject:@10]);
 
   NSLog(@"Count for object @20 = %lu",
   (unsigned long)[setOfNumbers countForObject:@20]);
   Вывод программы:
   Count for object @10 = 2
   Count for object @20 = 2
   Класс NSCountedSet является изменяемым, хотя из его названия это и не следует.

Обеспечение поддержки подписывания объектов в ваших классах

   Традиционно при необходимости доступа к объектам, содержащимся в коллекциях – например, массивах и словарях, – программисту требовалось получить доступ к методу в словаре или массиве, чтобы получить или установить желаемый объект. Например, создавая изменяемый словарь, мы добавляем в него два ключа и значения, получая эти значения обратно:
 
   NSString *const kFirstNameKey = @"firstName";
   NSString *const kLastNameKey = @"lastName";
 
   NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
   [dictionary setValue:@"Tim" forKey: kFirstNameKey];
   [dictionary setValue:@"Cook" forKey: kLastNameKey];
 
   __unused NSString *firstName = [dictionary valueForKey: kFirstNameKey];
   __unused NSString *lastName = [dictionary valueForKey: kLastNameKey];
   Но с развитием компилятора LLVM этот код можно сократить, придав ему следующий вид:
   NSString *const kFirstNameKey = @"firstName";
   NSString *const kLastNameKey = @"lastName";
 
   NSDictionary *dictionary = @{
   kFirstNameKey: @"Tim",
   kLastNameKey: @"Cook",
   };
 
   __unused NSString *firstName = dictionary[kFirstNameKey];
   __unused NSString *lastName = dictionary[kLastNameKey];
   Как видите, мы инициализируем словарь, давая ключи в фигурных скобках. Точно так же можно поступать и с массивами. Вот как мы обычно создаем и используем массивы:
   NSArray *array = [[NSArray alloc] initWithObjects:@"Tim", @"Cook", nil];
   __unused NSString *firstItem = [array objectAtIndex:0];
   __unused NSString *secondObject = [array objectAtIndex:1];
   А теперь, имея возможность подписывать объекты, мы можем сократить этот код следующим образом:
   NSArray *array = @[@"Tim", @"Cook"];
   __unused NSString *firstItem = array[0];
   __unused NSString *secondObject = array[0];
 
   Компилятор LLVM не останавливается и на этом. Вы можете также добавлять подписывание и к собственным классам. Существует два типа подписывания:
    подписывание по ключу – действуя таким образом, вы можете задавать внутри объекта значение для того или иного ключа точно так же, как вы делали бы это в словаре. Указывая ключ, вы также можете получать доступ к значениям внутри объекта и считывать их;
   • подписывание по индексу – как и при работе с массивами, вы можете устанавливать/получать значения внутри объекта, предоставив для этого объекта индекс. Это целесообразно делать в массивоподобных классах, где элементы естественным образом располагаются в порядке, удобном для индексирования.
   Сначала рассмотрим пример подписывания по ключу. Для этого создадим класс под названием Person, имеющий свойства firstName и lastName. Далее мы позволим программисту менять значения этих свойств (имя и фамилию), просто предоставив ключи для этих свойств.
   Вам может понадобиться добавить к классу подобный механизм подписывания по ключу, например, по такой причине: имена ваших свойств могут изменяться и вы хотите предоставить программисту возможность устанавливать значения таких свойств, не учитывая, будут ли имена этих свойств впоследствии изменяться. В противном случае программисту лучше будет использовать свойства напрямую. Другая причина реализации подписывания по ключу – стремление скрыть точную реализацию/объявление ваших свойств от программиста и закрыть программисту прямой доступ к этим свойствам.
   Чтобы обеспечить поддержку подписывания по ключу в ваших собственных классах, вы должны реализовать в вашем классе два следующих метода и записать сигнатуры методов в файле заголовков этого класса. В противном случае компилятор не узнает, что в вашем классе поддерживается подписывание по ключу.
 
   #import <Foundation/Foundation.h>
 
   /* Мы будем использовать их как ключи для наших свойств firstName
   и lastName, так что если имена наших свойств firstName и lastName
   в будущем изменятся в реализации, нам не придется ничего переделывать
   и наш класс останется работоспособным, поскольку мы сможем просто
   изменить значения этих констант в нашем файле реализации */
   extern NSString *const kFirstNameKey;
   extern NSString *const kLastNameKey;
 
   @interface Person: NSObject
 
   @property (nonatomic, copy) NSString *firstName;
   @property (nonatomic, copy) NSString *lastName;
 
   – (id) objectForKeyedSubscript:(id<NSCopying>)paramKey;
   – (void) setObject:(id)paramObject forKeyedSubscript:(id<NSCopying>)paramKey;
 
   @end
 
   Метод objectForKeyedSubscript: будет вызываться в вашем классе всякий раз, когда программист предоставит ключ и захочет прочитать в вашем классе значение, соответствующее данному ключу. Очевидно, тот параметр, который будет вам передан, будет представлять собой ключ, по которому программист хочет считать интересующее его значение. Дополнительно к этому методу мы будем вызывать в нашем классе метод setObject: forKeyedSubscript: всякий раз, когда программист захочет задать значение для конкретного ключа. Итак, в данной реализации мы хотим проверить, ассоциированы ли заданные ключи с именами и фамилиями. Если это так, то собираемся установить/получить в нашем классе значения имени и фамилии:
 
   #import "Person.h"
 
   NSString *const kFirstNameKey = @"firstName";
   NSString *const kLastNameKey = @"lastName";
 
   @implementation Person
 
   – (id) objectForKeyedSubscript:(id<NSCopying>)paramKey{
 
   NSObject<NSCopying> *keyAsObject = (NSObject<NSCopying> *)paramKey;
   if ([keyAsObject isKindOfClass: [NSString class]]){
   NSString *keyAsString = (NSString *)keyAsObject;
   if ([keyAsString isEqualToString: kFirstNameKey] ||
   [keyAsString isEqualToString: kLastNameKey]){
   return [self valueForKey: keyAsString];
   }
   }
 
   return nil;
   }
 
   – (void) setObject:(id)paramObject forKeyedSubscript:(id<NSCopying>)paramKey{
   NSObject<NSCopying> *keyAsObject = (NSObject<NSCopying> *)paramKey;
   if ([keyAsObject isKindOfClass: [NSString class]]){
   NSString *keyAsString = (NSString *)keyAsObject;
   if ([keyAsString isEqualToString: kFirstNameKey] ||
   [keyAsString isEqualToString: kLastNameKey]){
   [self setValue: paramObject forKey: keyAsString];
   }
   }
   }
 
   @end
 
   Итак, в этом коде мы получаем ключ в методе objectForKeyedSubscript:, а в ответ должны вернуть объект, который ассоциирован в нашем экземпляре с этим ключом. Ключ, который получаем, – это объект, соответствующий протоколу NSCopying. Это означает, что при желании мы можем сделать копию такого объекта. Рассчитываем на то, что ключ будет представлять собой строку, чтобы мы могли сравнить его с готовыми ключами, которые были заранее объявлены в начале класса. В случае совпадения зададим значение данного свойства в этом классе. После этого воспользуемся методом valueForKey:, относящимся к объекту NSObject, чтобы вернуть значение, ассоциированное с заданным ключом. Но, разумеется, прежде, чем так поступить, мы должны гарантировать, что данный ключ – один из тех, которые мы ожидаем. В методе setObject: forKeyedSubscript: мы делаем совершенно противоположное – устанавливаем значения для заданного ключа, а не возвращаем их.
   Теперь в любой части вашего приложения вы можете инстанцировать объект типа Person и использовать заранее определенные ключи kFirstNameKey и kLastNameKey, чтобы изменить значения свойств firstName и lastName, вот так:
 
   Person *person = [Person new];
   person[kFirstNameKey] = @"Tim";
   person[kLastNameKey] = @"Cook";
   __unused NSString *firstName = person[kFirstNameKey];
   __unused NSString *lastName = person[kLastNameKey];
   Этот код позволяет достичь точно того же результата, что и при более лобовом подходе, когда мы устанавливаем свойства класса:
   Person *person = [Person new];
   person.firstName = @"Tim";
   person.lastName = @"Cook";
   __unused NSString *firstName = person.firstName;
   __unused NSString *lastName = person.lastName;
 
   Вы также можете поддерживать и подписывание по индексу – точно как при работе с массивами. Как было указано ранее, это полезно делать, чтобы обеспечивать программисту доступ к объектам, выстраиваемым в классе в некоем естественном порядке. Но, кроме массивов, существует не так уж много структур данных, где целесообразно упорядочивать и нумеровать элементы, чего не скажешь о подписывании по ключу, которое применяется в самых разных структурах данных. Поэтому пример, которым иллюстрируется подписывание по индексу, немного надуман. В предыдущем примере у нас существовал класс Person с именем и фамилией. Теперь мы хотим предоставить программистам возможность считывать имя, указывая индекс 0, а фамилию – указывая индекс 1. Все, что требуется сделать для этого, – объявить методы objectAtIndexedSubscript: и setObject: atIndexedSubscript: в заголовочном файле класса, а затем написать реализацию. Вот как мы объявляем два этих метода в заголовочном файле класса Person:
   – (id) objectAtIndexedSubscript:(NSUInteger)paramIndex;
   – (void) setObject:(id)paramObject atIndexedSubscript:(NSUInteger)paramIndex;
   Реализация также довольно проста. Мы берем индекс и оперируем им так, как это требуется в нашем классе. Ранее мы решили, что у имени должен быть индекс 0, а у фамилии – индекс 1. Итак, получаем индекс 0 для задания значения, присваиваем значение имени первому входящему объекту и т. д.:
 
   – (id) objectAtIndexedSubscript:(NSUInteger)paramIndex{
 
   switch (paramIndex){
   case 0:{
   return self.firstName;
   break;
   }
   case 1:{
   return self.lastName;
   break;
   }
   default:{
   [NSException raise:@"Invalid index" format: nil];
   }
   }
 
   return nil;
   }
 
   – (void) setObject:(id)paramObject atIndexedSubscript:(NSUInteger)paramIndex{
   switch (paramIndex){
   case 0:{
   self.firstName = paramObject;
   break;
   }
   case 1:{
   self.lastName = paramObject;
   break;
   }
   default:{
   [NSException raise:@"Invalid index" format: nil];
   }
   }
   }
   Теперь можно протестировать весь написанный ранее код вот так:
   Person *person = [Person new];
   person[kFirstNameKey] = @"Tim";
   person[kLastNameKey] = @"Cook";
   NSString *firstNameByKey = person[kFirstNameKey];
   NSString *lastNameByKey = person[kLastNameKey];
 
   NSString *firstNameByIndex = person[0];
   NSString *lastNameByIndex = person[1];
 
   if ([firstNameByKey isEqualToString: firstNameByIndex] &&
   [lastNameByKey isEqualToString: lastNameByIndex]){
   NSLog(@"Success");
   } else {
   NSLog(@"Something is not right");
   }
 
   Если вы правильно выполнили все шаги, описанные в этом разделе, то на консоли должно появиться значение Success.

1.1. Отображение предупреждений с помощью UIAlertView

Постановка задачи

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

Решение

   Воспользуйтесь UIAlertView.

Обсуждение

   Если вы сами пользуетесь iOS, то вам определенно попадались виды-предупреждения. Пример такого вида показан на рис. 1.1.
 
   Рис. 1.1. Вид-предупреждение, сообщающий пользователю, что для работы требуется активное соединение с Интернетом
 
   Наилучший способ инициализации вида-предупреждения заключается, разумеется, в использовании его базового конструктора-инициализатора:
 
   – (void) viewDidAppear:(BOOL)paramAnimated{
 
   [super viewDidAppear: paramAnimated];
 
   UIAlertView *alertView = [[UIAlertView alloc]
   initWithTitle:@"Alert"
   message:@"You've been delivered an alert"
   delegate: nil
   cancelButtonTitle:@"Cancel"
   otherButtonTitles:@"OK", nil];
   [alertView show];
   }
 
   Когда этот вид-предупреждение отобразится у пользователя, он увидит экран, подобный показанному на рис. 1.2.
 
   Рис. 1.2. Простой вид-предупреждение, отображаемый у пользователя
 
   Чтобы показать пользователю вид-предупреждение, мы используем метод предупреждения show. Рассмотрим описания всех параметров, которые могут быть переданы базовому конструктору-инициализатору вида-предупреждения:
   • title – строка, которую пользователь увидит в верхней части вида-предупрежения. На рис. 1.2 эта строка – Title;
   • message – сообщение, которое отображается у пользователя. На рис. 1.2 для этого сообщения задано значение Message;
   • delegate – опциональный объект-делегат, который мы передаем виду-предупреждению. Затем этот объект будет получать уведомление при каждом изменении состояния предупреждения, например, когда пользователь нажмет на экранную кнопку, изображенную в этом виде. Объект, передаваемый данному параметру, должен соответствовать протоколу UIAlertViewDelegate;
   • cancelButtonTitle – строка, которая будет присваиваться кнопке отмены (Cancel Button) в виде-предупреждении. Если в виде-предупреждении есть кнопка отмены, то такой вид обычно побуждает пользователя к действию. Если пользователь не хочет совершать предложенное действие, то он нажимает кнопку отмены. Причем на этой кнопке не обязательно должна быть строка-надпись Cancel (Отменить). Надпись для этой кнопки определяете вы сами, и этот параметр опциональный – можно сделать диалоговое окно и без кнопки Отмена;
   • otherButtonTitles – надписи на других кнопках, тех, которые вы хотите отобразить в виде-предупреждении. Разделяйте такие надписи запятыми. Нужно убедиться, что в конце списка названий стоит значение nil, называемое сигнальной меткой. Этот параметр не является обязательным.
   Можно создать предупреждение вообще без кнопок. Но такое окно пользователь никак не сможет убрать с экрана. Создавая такой вид, вы как программист должны позаботиться о том, чтобы он убирался автоматически, например, через 3 секунды после того, как появится. Вид-предупреждение без кнопок, который не убирается автоматически, – это настоящее бедствие, с точки зрения пользователя. Ваше приложение не только получит низкие оценки на App Store за то, что вид-предупреждение блокирует пользовательский интерфейс. Велика вероятность, что вашу программу вообще удалят с рынка.
   Виды-предупреждения можно оформлять с применением различных стилей. В классе UIAlertView есть свойство alertViewStyle типа UIAlertViewStyle:
 
   typedef NS_ENUM(NSInteger, UIAlertViewStyle) {
   UIAlertViewStyleDefault = 0,
   UIAlertViewStyleSecureTextInput,
   UIAlertViewStylePlainTextInput,
   UIAlertViewStyleLoginAndPasswordInput
   };
 
   Вот что делает каждый из этих стилей:
   • UIAlertViewStyleDefault – стандартный стиль вида-предупреждения, подобное оформление мы видели на рис. 1.2;
   • UIAlertViewStyleSecureTextInput – при таком стиле в виде-предупреждении будет содержаться защищенное текстовое поле, которое станет скрывать от зрителя символы, вводимые пользователем. Такой вариант предупреждения вам подойдет, например, если вы запрашиваете у пользователя его учетные данные для дистанционного банковского обслуживания;
   • UIAlertViewStylePlainTextInput – при таком стиле у пользователя будет отображаться незащищенное текстовое поле. Этот стиль отлично подходит для случаев, когда вы просите пользователя ввести несекретную последовательность символов, например номер его телефона;
   • UIAlertViewStyleLoginAndPasswordInput – при таком стиле в виде-предупреждении будет два текстовых поля: незащищенное – для имени пользователя и защищенное – для пароля.
   Если вам необходимо получать уведомление, когда пользователь начинает работать с видом-предупреждением, укажите объект-делегат для вашего предупреждения. Этот делегат должен подчиняться протоколу UIAlertViewDelegate. Самый важный метод, определяемый в этом протоколе, – alertView: clickedButtonAtIndex:, который вызывается сразу же, как только пользователь нажимает на одну из кнопок в виде-предупреждении. Индекс нажатой кнопки передается вам через параметр clickedButtonAtIndex.
   В качестве примера отобразим предупреждение пользователю и спросим, хочет ли он перейти на сайт в браузере Safari после того, как нажмет ссылку на этот сайт, присутствующую в нашем пользовательском интерфейсе. В предупреждении будут отображаться две кнопки: Yes (Да) и No (Нет). В делегате вида-предупреждения мы увидим, какая кнопка была нажата, и предпримем соответствующие действия.
   Сначала реализуем два очень простых метода, которые возвращают надпись на той или иной из двух кнопок:
 
   – (NSString *) yesButtonTitle{
   return @"Yes";
   }
 
   – (NSString *) noButtonTitle{
   return @"No";
   }
   Теперь нужно убедиться, что контроллер нашего вида подчиняется протоколу UIAlertViewDelegate:
   #import <UIKit/UIKit.h>
 
   #import "ViewController.h"
 
   @interface ViewController () <UIAlertViewDelegate>
 
   @end
 
   @implementation ViewController
 
   …
 
   Следующий шаг – создать и отобразить для пользователя окно с предупреждением:
 
   – (void)viewDidAppear:(BOOL)animated{
   [super viewDidAppear: animated];
 
   self.view.backgroundColor = [UIColor whiteColor];
 
   NSString *message = @"Are you sure you want to open this link in Safari?";
   UIAlertView *alertView = [[UIAlertView alloc]
   initWithTitle:@"Open Link"
   message: message
   delegate: self
   cancelButtonTitle: [self noButtonTitle]
   otherButtonTitles: [self yesButtonTitle], nil];
   [alertView show];
 
   }
 
   Вид-предупреждение будет выглядеть примерно как на рис. 1.3.
 
   Рис. 1.3. Вид-предупреждение с кнопками No (Нет) и Yes (Да)
 
   Далее нужно узнать, какой вариант пользователь выбрал в нашем окне – No (Нет) или Yes (Да). Для этого потребуется реализовать метод alertView: clickedButtonAtIndex:, относящийся к делегату нашего вида-предупреждения:
 
   – (void) alertView:(UIAlertView *)alertView
   clickedButtonAtIndex:(NSInteger)buttonIndex{
 
   NSString *buttonTitle = [alertView buttonTitleAtIndex: buttonIndex];
 
   if ([buttonTitle isEqualToString: [self yesButtonTitle]]){
   NSLog(@"User pressed the Yes button.");
   }
   else if ([buttonTitle isEqualToString: [self noButtonTitle]]){
   NSLog(@"User pressed the No button.");
   }
 
   }
   Стоит учитывать, что в больших проектах, когда несколько специалистов разрабатывают один и тот же исходный код, обычно удобнее сравнивать надписи с кнопок из вида-предупреждения с соответствующими строками, а не проверять, какая кнопка была нажата, ориентируясь на индекс этой кнопки. Чтобы решение с индексом работало, программисту придется найти код, в котором был сконструирован вид с предупреждением, и уже в этом коде посмотреть, у какой кнопки какой индекс. В рассмотренном же нами решении любой разработчик, даже не знающий, как именно был создан вид с предупреждением, может понять, какой оператор if что именно делает.
   Как видите, мы пользуемся методом buttonTitleAtIndex: класса UIAlertView. Мы передаем этому методу индекс кнопки, отсчитываемый с нуля (кнопка находится в нашем виде), и получаем строку, которая представляет собой надпись на этой кнопке – если такая надпись вообще имеется. С помощью этого метода можно определить, какую кнопку нажал пользователь. Индекс этой кнопки будет передан нам как параметр buttonIndex метода alertView: clickedButtonAtIndex:. Если вас интересует надпись на этой кнопке, то нужно будет использовать метод buttonTitleAtIndex: класса UIAlertView. Все готово!