Текст книги "iOS. Приемы программирования"
![](/books_files/covers/thumbs_240/ios-priemy-programmirovaniya-82643.jpg)
Автор книги: Вандад Нахавандипур
Жанр: Зарубежная компьютерная литература, Зарубежная литература
сообщить о неприемлемом содержимом
Текущая страница: 11 (всего у книги 59 страниц) [доступный отрывок для чтения: 17 страниц]
1.24. Загрузка веб-страниц с помощью UIWebView
Постановка задачиНеобходимо динамически загрузить веб-страницу прямо в ваше приложение для iOS.
РешениеВоспользуйтесь классом UIWebView.
ОбсуждениеВеб-вид (Web View) – это окно, которое браузер Safari использует для загрузки в систему iOS информации из Сети. Класс UIWebView позволяет использовать в приложениях для iOS всю мощь Safari. Все, что вам нужно сделать, – поместить веб-вид в вашем пользовательском интерфейсе и применить один из методов загрузки:
• loadData: MIMEType: textEncodingName: baseURL: – загружает в веб-вид экземпляр класса NSData;
• loadHTMLString: baseURL: – загружает в веб-вид экземпляр класса NSString. Строка должна содержать валидный HTML-код так, чтобы ее мог обработать браузер;
• loadRequest: – загружает экземпляр класса NSURLRequest. Этот метод пригодится в тех случаях, когда вы хотите загрузить в веб-вид, расположенный в вашем приложении, удаленное содержимое, на которое указывает URL.
Рассмотрим пример. Начнем с файла реализации контроллера нашего вида:
#import «ViewController.h»
@interface ViewController ()
@property(nonatomic, strong) UIWebView *myWebView;
@end
@implementation ViewController
Теперь я хочу загрузить в веб-вид строку iOS 7 Programming Cookbook. Чтобы убедиться в том, что все работает как надо и что наш веб-вид способен отображать насыщенный (форматированный) текст, я на этом не остановлюсь и выделю слово Cookbook полужирным шрифтом, а остальной текст оставлю без изменений (рис. 1.65):
– (void)viewDidLoad{
[super viewDidLoad];
self.myWebView = [[UIWebView alloc] initWithFrame: self.view.bounds];
[self.view addSubview: self.myWebView];
NSString *htmlString = @"iOS 7 Programming <strong>Cookbook</strong>";
[self.myWebView loadHTMLString: htmlString
baseURL: nil];
}
![](i_065.png)
Рис. 1.65. Загрузка форматированного текста в веб-вид
Еще один способ работы с веб-видом – загрузка в него удаленного контента, на который указывает URL. Для этого можно пользоваться методом loadRequest:. Перейдем к следующему примеру, в котором загрузим основную страницу сайта Apple в веб-вид, расположенный в нашей программе для iOS (рис. 1.66):
– (void)viewDidLoad{
[super viewDidLoad];
self.myWebView = [[UIWebView alloc] initWithFrame: self.view.bounds];
self.myWebView.scalesPageToFit = YES;
[self.view addSubview: self.myWebView];
NSURL *url = [NSURL URLWithString:@"http://www.apple.com"];
NSURLRequest *request = [NSURLRequest requestWithURL: url];
[self.myWebView loadRequest: request];
}
![](i_066.png)
Рис. 1.66. Веб-вид, в который загружена домашняя страница Apple
Может понадобиться какое-то время, прежде чем в веб-вид загрузится содержимое, которое вы туда передали. Наверное, вы заметили, что при загрузке информации в браузере Safari в левом верхнем углу экрана появляется тонкий индикатор процесса, показывающий, что ваше устройство занято загрузкой контента (рис. 1.67).
![](i_067.png)
Рис. 1.67. Индикатор процесса загрузки
В iOS эта задача решается с помощью делегирования. Мы сделаем подписку на делегат веб-вида, и веб-вид будет получать уведомление всякий раз, когда делегат станет загружать контент. Когда загрузка контента завершится, мы получим от веб-вида соответствующее сообщение. Все это мы сделаем, применив свойство delegate веб-вида. Делегат веб-вида должен соответствовать протоколу UIWebViewDelegate.
Идем дальше. Теперь реализуем в контроллере нашего вида небольшой индикатор процесса. Не забывайте, что индикатор протекающего процесса уже имеется в составе приложения и мы не должны создавать его сами. Управлять этим индикатором можем с помощью метода setNetworkActivityIndicatorVisible:, относящегося к UIApplication. Итак, начнем с файла реализации контроллера вида:
@interface ViewController () <UIWebViewDelegate>
@property(nonatomic, strong) UIWebView *myWebView;
@end
@implementation ViewController
Потом перейдем к реализации. Здесь мы будем использовать три метода из тех, которые объявляются в протоколе UIWebViewDelegate:
• webViewDidStartLoad: – вызывается, как только вид начинает загрузку содержимого;
• webViewDidFinishLoad: – вызывается, как только вид заканчивает загрузку содержимого;
• webView: didFailLoadWithError: – вызывается, как только вид останавливает загрузку содержимого, например, из-за возникшей ошибки или разрыва сетевого соединения:
– (void)webViewDidStartLoad:(UIWebView *)webView{
[[UIApplication sharedApplication]
setNetworkActivityIndicatorVisible: YES];
}
– (void)webViewDidFinishLoad:(UIWebView *)webView{
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible: NO];
}
– (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error{
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible: NO];
}
– (void)viewDidLoad{
[super viewDidLoad];
self.myWebView = [[UIWebView alloc] initWithFrame: self.view.bounds];
self.myWebView.delegate = self;
self.myWebView.scalesPageToFit = YES;
[self.view addSubview: self.myWebView];
NSURL *url = [NSURL URLWithString:@"http://www.apple.com"];
NSURLRequest *request = [NSURLRequest requestWithURL: url];
[self.myWebView loadRequest: request];
}
1.25. Отображение протекания процессов с помощью UIProgressView
Постановка задачиНеобходимо отображать на экране индикатор протекания процесса (Progress Bar), отражающий ход выполнения той или иной задачи, например индикатор загрузки файла, скачиваемого c определенного URL.
РешениеИнстанцируйте вид типа UIProgressView и разместите его в другом виде.
ОбсуждениеВид протекания процесса программисты обычно называют прогресс-баром. Образец такого вида показан на рис. 1.68.
![](i_068.png)
Рис. 1.68. Простой вид с индикатором протекания процесса
Виды, отображающие протекание процессов, обычно демонстрируются пользователю для показа выполнения задачи с четко определенными начальной и конечной точками. Примером такой задачи является, например, скачивание 30 файлов. Очевидно, что такая задача будет выполнена, когда все 30 файлов будут скопированы на устройство. Вид, отображающий протекание процесса, является экземпляром UIProgressView и инициализируется с помощью специального метода-инициализатора данного класса – initWithProgressViewStyle:. В качестве параметра данный метод принимает стиль (оформление) панели протекания, которую предполагается создать. Этот параметр относится к типу UIProgressViewStyle и, соответственно, может иметь одно из следующих значений:
• UIProgressViewStyleDefault – это стандартное оформление вида протекания процесса. Именно в этом стиле оформлен вид, показанный на рис. 1.68;
• UIProgressViewStyleBar – напоминает UIProgressViewStyleDefault, но предназначено для использования с видами отображения протекания процессов, добавляемыми на панель инструментов.
Экземпляр UIProgressView определяет свойство под названием progress (типа float). Это свойство сообщает системе iOS, как должна отображаться полоса в виде, отражающем протекание процесса. Значение этого свойства должно быть в диапазоне от 0 до 1.0. Если сообщается значение 0, то заполнение индикатора состояния еще не началось. Значение 1.0 соответствует 100 %-ной завершенности. Степень прогресса, показанная на рис. 1.68, составляет 0.5 (или 50 %).
Чтобы научиться создавать виды, отражающие протекание процессов, создадим вид, похожий на тот, что приведен на рис. 2.74. Начинаем с главного – определяем свойство для вида протекания процесса:
#import «ViewController.h»
@interface ViewController ()
@property (nonatomic, strong) UIProgressView *progressView;
@end
@implementation ViewController
Далее инстанцируем объект типа UIProgressView:
– (void)viewDidLoad{
[super viewDidLoad];
self.progressView = [[UIProgressView alloc]
initWithProgressViewStyle: UIProgressViewStyleBar];
self.progressView.center = self.view.center;
self.progressView.progress = 20.0f / 30.0f;
[self.view addSubview: self.progressView];
}
Итак, создать вид протекания процесса совсем не сложно. В сущности, нужно просто правильно отобразить ход процесса, так как свойство progress данного вида должно иметь значение в диапазоне от 0 до 1.0, то есть нормализованное значение. Итак, если вам предстоит решить 30 задач и вы уже выполнили 20 из них, то нужно присвоить свойству progress вида протекания процесса результат следующего равенства:
self.progressView.progress = 20.0f / 30.0f;
Значения 20 и 30 передаются данному равенству как значения с плавающей точкой, поскольку компилятору нужно сообщить, что операция деления будет производиться над числами с плавающей точкой и в результате деления получится десятичная дробь. Если приказать компилятору поместить в свойстве progress вида протекания процесса целочисленное деление 20/30, то вы получите целочисленный результат 0. Это происходит потому, что компилятор выполняет целочисленное деление, отсекая полученный результат до ближайшего предшествующего целого числа. Короче говоря, на индикаторе протекания действия прогресс все время будет оставаться нулевым, пока процесс не завершится и частное от деления 30/30 не станет равно 1. Пользователю такой индикатор загрузки будет ни к чему.
1.26. Создание и отображение текстов с оформлением
Постановка задачиТребуется возможность отображать в элементах вашего пользовательского интерфейса насыщенный форматированный текст, избегая при этом необходимости создавать отдельный компонент пользовательского интерфейса для каждого атрибута. Например, может потребоваться отобразить в UILabel предложение, в котором всего одно слово записано полужирным шрифтом.
РешениеСоздайте экземпляр класса NSAttributedString или его изменяемого варианта, NSMutableAttributedString, и либо задайте его как текст компонента пользовательского интерфейса (например, как текст подписи UILabel) с помощью специального строкового свойства, снабженного атрибутами, либо просто воспользуйтесь встроенными методами атрибутированной строки для отрисовки текста на холсте.
ОбсуждениеО насыщенном тексте слагают легенды. Многим из наших коллег-программистов приходилось сталкиваться с необходимостью отображения в пользовательском интерфейсе такой текстовой строки, в которой применяется сразу несколько видов форматирования. Например, в одной строке может понадобиться одновременно вывести и обычный текст, и курсив, причем курсивом будет записано всего одно слово. Возможно, одно из слов в предложении потребуется подчеркнуть. Для этого некоторые пытаются использовать веб-виды (Web Views), но это решение не является оптимальным, поскольку веб-виды довольно медленно отображают свой контент и неизбежно негативно воздействуют на производительность приложения. В iOS 7 можно приступать к применению атрибутированных строк. Не знаю, почему Apple решила внедрить такую возможность в iOS только сейчас, ведь Mac-разработчики пользуются атрибутированными строками уже довольно давно.
Прежде чем приступить к основной части раздела, я хотел бы четко пояснить, что понимается под термином «атрибутированная строка». Взгляните на рис. 1.69. Мы собираемся написать программу, которая будет достигать именно такого эффекта.
![](i_069.png)
Рис. 1.69. Атрибутированная строка отображена на экране в простой подписи
Необходимо отметить, что этот текст отображается в одном экземпляре класса UILabel.
Итак, что мы видим в этом примере? Перечислю.
• Текст iOS имеет следующие атрибуты:
• полужирный шрифт размером 60 точек;
• черный цвет фона;
• красный цвет шрифта.
• Текст SDK имеет следующие атрибуты:
• полужирный шрифт размером 60 точек;
• белый цвет шрифта;
• светло-серую тень;
• красный цвет фона.
Удобнее всего создавать атрибутированные строки с помощью метода initWithString:, относящегося к изменяемому классу NSMutableAttributedString, и передавать этому методу экземпляр NSString. Так создается атрибутированная строка без каких-либо атрибутов. Затем, чтобы присвоить атрибуты различным частям строки, мы воспользуемся методом setAttributes: range: класса NSMutableAttributedString. Этот метод принимает два параметра:
• setAttributes – словарь, ключи которого являются символьными атрибутами и значение каждого ключа зависит от самого ключа. Вот наиболее важные ключи, которые можно задать в этом словаре:
• NSFontAttributeName – значение этого ключа является экземпляром UIFont и определяет шрифт для того или иного фрагмента строки;
• NSForegroundColorAttributeName – значение этого ключа относится к типу UIColor и определяет цвет шрифта определенного фрагмента строки;
• NSBackgroundColorAttributeName – значение этого ключа относится к типу UIColor и определяет цвет фона, на котором будет отрисовываться определенный фрагмент строки;
• NSShadowAttributeName – значение этого ключа должно быть экземпляром NSShadow и задавать тень, которую будет отбрасывать определенный фрагмент строки;
• range – значение типа NSRange, определяющее начальную точку и длину группы символов, к которой вы хотите применить указанные атрибуты.
Чтобы просмотреть все ключи, которые можно передавать этому методу, просто изучите онлайновую документацию Apple по классу NSMutableAttributedString. Я не буду помещать здесь ссылку на документацию, так как Apple может рано или поздно изменить эту ссылку, а вот поиск вас точно не подведет.
Разобьем наш пример на два словаря с атрибутами. Словарь атрибутов для слова iOS создается в коде таким образом:
NSDictionary *attributesForFirstWord = @{
NSFontAttributeName: [UIFont boldSystemFontOfSize:60.0f],
NSForegroundColorAttributeName: [UIColor redColor],
NSBackgroundColorAttributeName: [UIColor blackColor]
};
А слово SDK создается с помощью следующих атрибутов:
NSShadow *shadow = [[NSShadow alloc] init];
shadow.shadowColor = [UIColor darkGrayColor];
shadow.shadowOffset = CGSizeMake(4.0f, 4.0f);
NSDictionary *attributesForSecondWord = @{
NSFontAttributeName: [UIFont boldSystemFontOfSize:60.0f],
NSForegroundColorAttributeName: [UIColor whiteColor],
NSBackgroundColorAttributeName: [UIColor redColor],
NSShadowAttributeName: shadow
};
Собрав все вместе, получаем следующий код, который не только создает нашу подпись, но и задает для нее атрибутированный текст:
#import «ViewController.h»
@interface ViewController ()
@property (nonatomic, strong) UILabel *label;
@end
@implementation ViewController
– (NSAttributedString *) attributedText{
NSString *string = @"iOS SDK";
NSMutableAttributedString *result = [[NSMutableAttributedString alloc]
initWithString: string];
NSDictionary *attributesForFirstWord = @{
NSFontAttributeName: [UIFont boldSystemFontOfSize:60.0f],
NSForegroundColorAttributeName: [UIColor redColor],
NSBackgroundColorAttributeName: [UIColor blackColor]
};
NSShadow *shadow = [[NSShadow alloc] init];
shadow.shadowColor = [UIColor darkGrayColor];
shadow.shadowOffset = CGSizeMake(4.0f, 4.0f);
NSDictionary *attributesForSecondWord = @{
NSFontAttributeName: [UIFont boldSystemFontOfSize:60.0f],
NSForegroundColorAttributeName: [UIColor whiteColor],
NSBackgroundColorAttributeName: [UIColor redColor],
NSShadowAttributeName: shadow
};
/* Находим фрагмент iOS в целой строке и задаем атрибуты для этого фрагмента */
[result setAttributes: attributesForFirstWord
range: [string rangeOfString:@"iOS"]];
/* Делаем то же самое со строкой SDK */
[result setAttributes: attributesForSecondWord
range: [string rangeOfString:@"SDK"]];
return [[NSAttributedString alloc] initWithAttributedString: result];
}
– (void)viewDidLoad{
[super viewDidLoad];
self.label = [[UILabel alloc] init];
self.label.backgroundColor = [UIColor clearColor];
self.label.attributedText = [self attributedText];
[self.label sizeToFit];
self.label.center = self.view.center;
[self.view addSubview: self.label];
}
@end
Разделы 1.17 и 1.18.
1.27. Представление видов «Основной – детали» с помощью UISplitViewController
Постановка задачиНеобходимо максимально эффективно использовать большой экран iPad, представив на нем два расположенных рядом контроллера видов.
РешениеВоспользуйтесь классом UISplitViewController.
ОбсуждениеКонтроллеры видов split view (будем называть эти виды разделенными экранами) есть только в iPad. Если вы работаете с iPad, то, вероятно, уже сталкивались с ними. Можно просто открыть приложение Settings (Настройки) в альбомном режиме и посмотреть. Видите, какой контроллер разделенного экрана показан на рис. 1.70?
У контроллера разделенного экрана есть левая и правая стороны. Слева отображаются основные настройки. При нажатии каждой из этих настроек открываются детали этого элемента, которые мы видим в правой части разделенного экрана.
Даже не пытайтесь инстанцировать объект типа UISplitViewController на каком-нибудь устройстве, кроме iPad. В результате вы получите исключение.
![](i_070.png)
Рис. 1.70. Контроллер с разделенным экраном в приложении Settings (Настройки) в iPad
Apple предельно упростила процесс создания приложений, в основе которых лежит работа с разделенными экранами. Чтобы создать собственное приложение такого рода, просто выполните следующие шаги.
1. В Xcode перейдите в меню File (Файл) и выполните NewNew Project (Новый Новый проект).
2. В окне New Project (Новый проект) выберите слева iOSApplication (iOSПриложение), а потом укажите вариант Master-Detail Application (Приложение «Основной – детали») (рис. 1.71) и нажмите Next (Далее).
![](i_071.png)
Рис. 1.71. Выбираем в Xcode шаблон приложения «Основной – детали»
3. На следующем экране выберите название вашего продукта и убедитесь в том, что для семейства устройств указан параметр Universal (Универсальное). Мы хотим, чтобы создаваемое приложение могло работать и на iPhone, и на iPad (рис. 1.72). Сделав это, нажмите Next (Далее).
![](i_072.png)
Рис. 1.72. Задаем в Xcode настройки проекта «Основной – детали»
4. Теперь выберем место для сохранения проекта. Сделав это, нажмите кнопку Create (Создать).
Итак, проект создан. На кнопке поэтапного выбора Scheme (Схема), расположенной в левом верхнем углу, должно быть указано, что приложение будет работать в эмуляторе iPad, а не в эмуляторе iPhone. Если в Xcode создается универсальное приложение «Основной – детали», то Xcode обеспечивает возможность работы с этим приложением и на iPhone, но при запуске приложения на iPhone структура его будет иной, нежели при запуске на iPad. В приложении окажется навигационный контроллер, внутри которого будет контроллер вида. Если то же самое приложение запустить на iPad, то мы увидим разделенный экран, в котором будут расположены два контроллера вида.
В шаблоне проекта с разделенным экраном есть два файла, о которых следует поговорить отдельно:
• MasterViewController – контроллер основного вида, располагающегося в левой части разделенного экрана в iPad. В iPhone это первый контроллер, который увидит пользователь;
• DetailViewController – контроллер вида с деталями, который отображается в правой части разделенного экрана на iPad. В iPhone это тот контроллер, который занимает верхнюю позицию в стеке, как только пользователь выбирает любой элемент в корневом (первом, основном) контроллере вида.
Теперь нужно подумать, как будет выглядеть обмен информацией между экраном основных параметров и экраном деталей. Хотите ли вы организовать такой обмен информацией через делегат приложения или желаете, чтобы основной вид посылал сообщения непосредственно виду с деталями? Это зависит от вас.
Если запустить такое приложение в эмуляторе iPad, то в альбомном режиме мы увидим контроллеры основного вида и вида с деталями в разделенном экране, но если изменить ориентацию на книжную, то вид с основными параметрами исчезнет и на его месте появится навигационная кнопка Master (Основной). Она будет располагаться в левой верхней части навигационной панели контроллера с детальной информацией. Хотя это и неплохой вариант, но мы ожидали иного, так как сравниваем наш проект с приложением Settings (Настройки) из iPad. Если в iPad повернуть экран с приложением Settings (Настройки) так, чтобы он приобрел книжную ориентацию, то на экране все равно останутся оба контроллера видов: и с основной информацией, и с деталями. Как нам добиться такого результата? Оказывается, Apple предлагает API (интерфейс программирования приложений), с помощью которого как раз и можно решить такую задачу. Просто переходим в файл DetailViewController.m и реализуем следующий метод:
– (BOOL) splitViewController:(UISplitViewController *)svc
shouldHideViewController:(UIViewController *)vc
inOrientation:(UIInterfaceOrientation)orientation{
return NO;
}
Если вернуть из этого метода значение NO, iOS не будет скрывать контроллер основного вида при любой ориентации и оба контроллера – как с основными опциями, так и с их деталями – будут отображаться и в альбомной, и в книжной ориентации. Теперь, реализовав упомянутый метод, мы сможем обойтись без двух следующих методов:
– (void)splitViewController:(UISplitViewController *)splitController
willHideViewController:(UIViewController *)viewController
withBarButtonItem:(UIBarButtonItem *)barButtonItem
forPopoverController:(UIPopoverController *)popoverController{
barButtonItem.title = NSLocalizedString(@"Master", @"Master");
[self.navigationItem setLeftBarButtonItem: barButtonItem animated: YES];
self.masterPopoverController = popoverController;
}
– (void)splitViewController:(UISplitViewController *)splitController
willShowViewController:(UIViewController *)viewController
invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem{
[self.navigationItem setLeftBarButtonItem: nil animated: YES];
self.masterPopoverController = nil;
}
Эти методы требовались нам просто для управления кнопкой из навигационной панели, но теперь мы больше не пользуемся ею и можем избавиться от этих методов. Их можно просто закомментировать или вообще удалить из файла DetailViewController.m.
Заглянув на заголовочный файл контроллера вашего основного вида, вы увидите там нечто подобное:
#import <UIKit/UIKit.h>
@class DetailViewController;
@interface MasterViewController: UITableViewController
@property (strong, nonatomic) DetailViewController *detailViewController;
@end
Как видите, в контроллере основного вида стоит ссылка на контроллер вида с деталями. С помощью этой связи мы можем сообщать контроллеру вида с деталями о сделанном выборе, а также передавать ему другие значения – об этом чуть позже.
По умолчанию если вы запустите приложение в эмуляторе iPad, то увидите пользовательский интерфейс, очень напоминающий тот, что показан на рис. 1.73. В стандартной реализации, которую Apple предоставляет нам с контроллером основного вида, содержится изменяемый массив. Этот массив заполняется экземплярами NSDate всякий раз, когда вы нажимаете кнопку «плюс» (+) на навигационной панели в этом контроллере вида. Стандартная реализация очень проста, и вы можете ее модифицировать, немного разобравшись в табличных видах. О том, что такое табличные виды и как они заполняются, подробно рассказано в главе 4.
![](i_073.png)
Рис. 1.73. Контроллер пустого вида с разделенным экраном, работающий в эмуляторе iPad
Правообладателям!
Данное произведение размещено по согласованию с ООО "ЛитРес" (20% исходного текста). Если размещение книги нарушает чьи-либо права, то сообщите об этом.Читателям!
Оплатили, но не знаете что делать дальше?