Содержание
Достаточно нередкая задача — сделать асинхронную загрузку изображений в UITableView. Для решения этой задачи есть несколько подходов. В этой статье я опишу один из них. Для полного понимания материала вам потребуется знание блоков в Objective-C и NSNotification, UITableView Если таких знаний нет — воспринимайте написанное как правду.
Мы создадим UITableView, который представляет собой список некоторых европейских стран и их флаги. Метод достаточно прост — создадим сущность (класс) Country (страна), который имеет три свойства: название, ссылка на картинку, и саму картинку. Картинка не загружается при создании класса, картинка загружается по вызову метода loadImage. После загрузки картинки создается уведомление, которое сообщает всем, что картинка у конкретной страны загружена.
Для начала подготовим список стран. Я сделал его в виде plist-файла, который можно загрузить отсюда. Файл представляет собой словарь с одним ключом — countries, значение которого — массив со словарями, который представляют страны.
Далее необходимо создать какую-нибудь сущность, представляющую страну. Пускай это будет класс Country. Приведу полный код класса.
Country.h
// название уведомления о загрузке
#define IMAGE_LOADED_NOTIFICATION @"imageLoaded"
@interface Country : NSObject {
UIImage* m_image;
}
// свойства
@property (nonatomic, copy) NSString* name;
@property (nonatomic, copy) NSURL* iconURL;
@property (nonatomic, readonly) UIImage* image;
// конструкторы
+(Country*) countryWithName:(NSString*)country;
+(Country*) countryWithName:(NSString *)country iconURL:(NSURL*)url;
-(id) initWithName:(NSString*)country;
-(id) initWithName:(NSString *)country iconURL:(NSURL*)iconUrl;
// запускает загрузку изображения
-(void) loadImage;
@end
Country.m
#import "Country.h"
@implementation Country
// Cвойства
@synthesize name;
@synthesize iconURL;
-(UIImage*) image {
return m_image;
}
// Конструкторы и деструктор
-(id) initWithName:(NSString*)country {
return [self initWithName:country iconURL:nil];
}
-(id) initWithName:(NSString *)country iconURL:(NSURL*)iconUrl {
self = [super init];
self.name = country;
self.iconURL = iconURL;
return self;
}
+(Country*) countryWithName:(NSString*)country {
return [[[Country alloc] initWithName:country] autorelease];
}
+(Country*) countryWithName:(NSString *)country iconURL:(NSURL*)url {
return [[[Country alloc]
initWithName:country iconURL:url] autorelease];
}
-(void) dealloc {
[m_image release];
self.iconURL = nil;
self.name = nil;
[super dealloc];
}
// Методы загрузки картинки
-(void) loadImage {
// запускам загружаться изображение асинхронно
[self performSelectorInBackground:@selector(loadImageInBackground)
withObject:nil];
}
-(void) loadImageInBackground {
NSAutoreleasePool* pool = [NSAutoreleasePool new];
// готовим и выполняем запрос
NSURLRequest* request = [NSURLRequest requestWithURL:self.iconURL];
NSError* error = nil;
NSData* data =
[NSURLConnection sendSynchronousRequest:request
returningResponse:nil error:&error];
if ( error == nil ) {
//изображение загрузилось
[m_image release];
m_image = [[UIImage imageWithData:data] retain];
// сообщаем, что изображение загрузилось
[[NSNotificationCenter defaultCenter]
postNotificationName:IMAGE_LOADED_NOTIFICATION
object:self];
}
[pool release];
}
@end
У класса три свойства — имя, URL-картинки и сама картинка. Как видим, в h-файле есть объявление название уведомления о загрузке. Разберем код подробнее:
Вот эта строчка вызывает асинхронное выполнение метода loadImageInBackground:
[self performSelectorInBackground:@selector(loadImageInBackground) withObject:nil];
Метод загружает изображение и уведомляет нас. Строчка которая генерирует уведомление:
[[NSNotificationCenter defaultCenter]
postNotificationName:IMAGE_LOADED_NOTIFICATION
object:self];
Когда сущность создана и умеет загружать свою картинку — переходим к созданию UITableView.
Создайте UIViewController. Поместите на нем UITableView, назначьте delegate и dataSource для UITableView на наш контроллер. Объявление UIViewController должно выглядеть так:
@interface RootViewController
: UIViewController<UITableViewDelegate,
UITableViewDataSource> {
// ссылка на UITableView
IBOutlet UITableView* m_tableView;
// список стран
NSMutableArray* m_countries;
}
@end
В конструкторе UIViewController поместите код:
// подготовим массив стран
m_countries = [[NSMutableArray array] retain];
// "преобразуем" файл из ресурсов в словарь
NSDictionary* countries =
[NSDictionary
dictionaryWithContentsOfFile:[[NSBundle mainBundle]
pathForResource:@"Countries"
ofType:@"plist"]];
// достанем массив со странами-словарями
NSArray* countriesArray = [countries objectForKey:@"countries"];
// заполним массив стран сущностями Country
for( NSDictionary* obj in countriesArray ) {
Country* country
= [Country countryWithName:[obj objectForKey:@"name"]];
country.iconURL
= [NSURL URLWithString:[obj objectForKey:@"url"]];
[m_countries addObject:country];
}
Этот код «достает» из plist-файла список стран и создает из него массив, состоящий из классов Country. Этот массив мы будем использовать для заполнения данными UITableView. Не забудьте создать как минимум два метода для UITableView, как того требует UITableViewDataSource. В методе:
-(NSInteger) tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
Необходимо вернуть количество стран в массиве m_countries:
-(NSInteger) tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [m_countries count];
}
Далее, в методах viewDidLoad и viewDidUnload нужно подписаться и отписаться от уведомлений:
- (void)viewDidLoad
{
[super viewDidLoad];
// начнем "слушать" notification
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(imageLoadedNotification:)
name:IMAGE_LOADED_NOTIFICATION object:nil];
}
- (void)viewDidUnload
{
// отменяем "прослушку"
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super viewDidUnload];
}
В методе:
-(UITableViewCell*) tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
будем вызывать загрузку картинки, если она еще не загружена. Полный код этого метода:
-(UITableViewCell*) tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString* cellID = @"imagedCell";
UITableViewCell* cell;
cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if ( !cell ) {
cell =
[[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:cellID];
}
// страна, которая должна быть помещена сейчас на ячейку
Country* country = [m_countries objectAtIndex:indexPath.row];
// назначим имя
cell.textLabel.text = country.name;
// если картинка для страны не загружена
// дамдим команду загружать
if ( country.image == nil )
[country loadImage];
// назначим картинку
cell.imageView.image = country.image;
return cell;
}
Теперь осталось написать метод, который будет принимать уведомления о загрузке картинок:
// "слушаем" notification
-(void) imageLoadedNotification:(NSNotification*)notification {
// если не пришла страна,
// для которой загрузился флаг - ничего не делаем
if ( notification.object == nil )
return;
// достаем страну, для которой загрузился флаг
Country* country = (Country*)notification.object;
// помечаем переменную, чтобы мы ее могли изменить в блоке
__block NSIndexPath* indexPath = nil;
// перебираем массив со странами и ищем ту,
// для которой загрузился флаг
[m_countries
enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
// здесь мы сравниваем адрес объекта!
// т.к. они берутся из одной коллекции - этого достаточно
if ( obj == country ) {
// нашли! запомним положение в tableView
indexPath
= [[NSIndexPath indexPathForRow:idx inSection:0] retain];
// остановим "перебор"
*stop = YES;
}
}];
// если нашли страну - обновим ячейку, в которой она показывается
if ( indexPath != nil ) {
dispatch_sync(dispatch_get_main_queue(), ^{
[m_tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
});
[indexPath release];
}
}
Метод принимает уведомление и страну, для которой загрузилась картинка. Далее определяется место в UITableView для этой страны и обновляется соответствующая ячейка.
Вот такой простой метод. И вот что у нас получилось:
Как всегда, можно скачать исходный проект.
Похожие статьи
-
[Swift] Урок 1 — Пишем программу «Hello, World» на Swift языке под iOS
-
Audio Unit в iOS. Часть 3, накладываем эффект Delay
-
Audio Unit в iOS. Часть 2, строим граф и проигрываем файлы
-
Audio Unit в iOS. Часть 1, введение.
-
Используем Emoji в своих приложениях
Сообщить об опечатке
Текст, который будет отправлен нашим редакторам: