Асинхронная загрузка изображений в UITableView

Достаточно нередкая задача — сделать асинхронную загрузку изображений в UITableView. Для решения этой задачи есть несколько подходов. В этой статье я опишу один из них. Для полного понимания материала вам потребуется знание блоков в Objective-C и NSNotification, UITableView Если таких знаний нет — воспринимайте написанное как правду.

Мы создадим UITableView, который представляет собой список некоторых европейских стран и их флаги. Метод достаточно прост — создадим сущность (класс) Country (страна), который имеет три свойства: название, ссылка на картинку, и саму картинку. Картинка не загружается при создании класса, картинка загружается по вызову метода loadImage. После загрузки картинки создается уведомление, которое сообщает всем, что картинка у конкретной страны загружена.

Для начала подготовим список стран. Я сделал его в виде plist-файла, который можно загрузить отсюда. Файл представляет собой словарь с одним ключом — countries, значение которого — массив со словарями, который представляют страны.

Асинхронная загрузка изображений в UITableView

Далее необходимо создать какую-нибудь сущность, представляющую страну. Пускай это будет класс 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 для этой страны и обновляется соответствующая ячейка.

Вот такой простой метод. И вот что у нас получилось:

Асинхронная загрузка изображений в UITableView

Как всегда, можно скачать исходный проект.

Похожие статьи

  • [Swift] Урок 1 — Пишем программу «Hello, World» на Swift языке под iOS

  • Audio Unit в iOS. Часть 3, накладываем эффект Delay

  • Audio Unit в iOS. Часть 2, строим граф и проигрываем файлы

  • Audio Unit в iOS. Часть 1, введение.

  • Используем Emoji в своих приложениях

Рейтинг
( Пока оценок нет )
webnewsite.ru / автор статьи
Загрузка ...

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: