Как асинхронно загружать изображение в UIImageView?
У меня есть UIView с подвью UIImageView. Мне нужно загрузить изображение в UIImageView без блокировки пользовательского интерфейса. Блокирующий вызов выглядит следующим образом: UIImage imageNamed:
. Вот что я решил решить эту проблему:
-(void)updateImageViewContent {
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
UIImage * img = [UIImage imageNamed:@"background.jpg"];
dispatch_sync(dispatch_get_main_queue(), ^{
[[self imageView] setImage:img];
});
});
}
Изображение маленькое (150x100).
Однако пользовательский интерфейс по-прежнему блокируется при загрузке изображения. Что мне не хватает?
Вот небольшой пример кода, который проявляет это поведение:
Создайте новый класс на основе UIImageView, установите его взаимодействие с пользователем YES, добавьте два экземпляра в UIView и реализуйте его метод touchhesBegan следующим образом:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.tag == 1) {
self.backgroundColor= [UIColor redColor];
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
[self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];
});
[UIView animateWithDuration:0.25 animations:
^(){[self setFrame:CGRectInset(self.frame, 50, 50)];}];
}
}
Назначьте тег 1 одному из этих изображений.
Что происходит, когда вы одновременно используете два вида просмотра, начиная с представления, загружающего изображение? Заблокирован ли пользовательский интерфейс, потому что он ждет возврата [self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];
? Если да, то как я могу сделать это асинхронно?
Вот проект на github с кодом ipmcc
Используйте длинное нажатие, затем перетащите, чтобы нарисовать прямоугольник вокруг черных квадратов. Насколько я понимаю его ответ, теоретически белый прямоугольник выбора не должен блокироваться при первом загрузке изображения, но на самом деле это.
В проект включены два изображения (один маленький: woodenTile.jpg и еще один: bois.jpg). Результат одинаковый с обоими.
Формат изображения
Я действительно не понимаю, как это связано с проблемой, которую я все еще имею, когда пользовательский интерфейс блокируется во время загрузки изображения в первый раз, но изображения PNG декодируются без блокировки пользовательского интерфейса, в то время как изображения JPG блокируют пользовательский интерфейс.
Хронология событий
![step 0]()
![step 1]()
Здесь начинается блокировка пользовательского интерфейса.
![step 2]()
.. и заканчивается здесь.
![step 3]()
Решение AFNetworking
NSURL * url = [ [NSBundle mainBundle]URLForResource:@"bois" withExtension:@"jpg"];
NSURLRequest * request = [NSURLRequest requestWithURL:url];
[self.imageView setImageWithURLRequest:request
placeholderImage:[UIImage imageNamed:@"placeholder.png"]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
NSLog(@"success: %@", NSStringFromCGSize([image size]));
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(@"failure: %@", response);
}];
// this code works. Used to test that url is valid. But it blocking the UI as expected.
if (false)
if (url) {
[self.imageView setImage: [UIImage imageWithData:[NSData dataWithContentsOfURL:url]]]; }
В большинстве случаев он регистрируется: success: {512, 512}
Он также может использовать журналы: success: {0, 0}
И иногда: failure: <NSURLResponse: 0x146c0000> { URL: file:///var/mobile/Appl...
Но изображение никогда не изменяется.
Ответы
Ответ 1
Проблема в том, что UIImage
фактически не читает и декодирует изображение до первого использования/рисования. Чтобы заставить эту работу произойти в фоновом потоке, вы должны использовать/нарисовать изображение в фоновом потоке перед тем, как сделать основной поток -setImage:
. Это сработало для меня:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
UIImage * img = [UIImage imageNamed:@"background.jpg"];
// Make a trivial (1x1) graphics context, and draw the image into it
UIGraphicsBeginImageContext(CGSizeMake(1,1));
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), [img CGImage]);
UIGraphicsEndImageContext();
// Now the image will have been loaded and decoded and is ready to rock for the main thread
dispatch_sync(dispatch_get_main_queue(), ^{
[[self imageView] setImage: img];
});
});
EDIT: пользовательский интерфейс не блокирует. Вы специально настроили его, чтобы использовать UILongPressGestureRecognizer
, который ждет, по умолчанию, полсекунды, прежде чем что-либо делать. Основной поток по-прежнему обрабатывает события, но ничего не произойдет до тех пор, пока не истечет время GR. Если вы это сделаете:
longpress.minimumPressDuration = 0.01;
... вы заметите, что он становится намного более быстрым. Загрузка изображения здесь не проблема.
РЕДАКТИРОВАТЬ 2: Я просмотрел код, который был отправлен в github, работает на iPad 2, и я просто не получаю икоту, которую вы описываете. На самом деле это довольно гладко. Вот скриншот от запуска кода в инструменте CoreAnimation:
![enter image description here]()
Как вы можете видеть на верхнем графике, FPS идет до ~ 60FPS и остается там по всему жесту. На нижнем графике вы можете увидеть разброс примерно в 16 с, где сначала загружается изображение, но вы можете видеть, что нет снижения частоты кадров. Как раз из визуального осмотра, я вижу, что слой выбора пересекается, и есть небольшая, но наблюдаемая задержка между первым пересечением и внешним видом изображения. Насколько я могу судить, код загрузки фона выполняет свою работу, как ожидалось.
Хотел бы я помочь вам больше, но я просто не вижу проблемы.
Ответ 2
Вы можете использовать AFNetworking библиотеку, в которой путем импорта категории
"UIImageView + AFNetworking.m"
и используя следующий метод:
[YourImageView setImageWithURL:[NSURL URLWithString:@"http://image_to_download_from_serrver.jpg"]
placeholderImage:[UIImage imageNamed:@"static_local_image.png"]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
//ON success perform
}
failure:NULL];
надеюсь, что это поможет.
Ответ 3
У меня была очень похожая проблема с моим приложением, где мне пришлось загружать много изображений и вместе с тем мой пользовательский интерфейс постоянно обновлялся. Ниже приведена простая ссылка для учебника, в которой была решена моя проблема:
NSOperations и учебное пособие NSOperationQueues
Ответ 4
это хороший способ:
-(void)updateImageViewContent {
dispatch_async(dispatch_get_main_queue(), ^{
UIImage * img = [UIImage imageNamed:@"background.jpg"];
[[self imageView] setImage:img];
});
}
Ответ 5
Почему бы вам не использовать стороннюю библиотеку, например AsyncImageView? Используя это, все, что вам нужно сделать, это объявить объект AsyncImageView и передать URL-адрес или изображение, которое вы хотите загрузить. Во время загрузки изображения будет отображаться индикатор активности, и ничто не блокирует пользовательский интерфейс.
Ответ 6
- (void) touchesBegan: вызывается в основном потоке. Вызывая dispatch_async (dispatch_get_main_queue), вы просто кладете блок в очередь. Этот блок будет обработан GCD, когда очередь будет готова (т.е. Система закончила обработку ваших касаний). Поэтому вы не можете видеть, что ваш woodTile загружен и назначен для self.image, пока вы не отпустите палец и не разрешите GCD обрабатывать все блоки, которые были поставлены в очередь в основной очереди.
Замена:
dispatch_async(dispatch_get_main_queue(), ^{
[self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];
});
:
[self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];
должен решить вашу проблему... по крайней мере для кода, который ее показывает.
Ответ 7
Рассмотрите возможность использования SDWebImage: он не только загружает и кэширует изображение в фоновом режиме, но также загружает и отображает его.
Я использовал его с хорошими результатами в представлении таблицы, в котором были загружены большие изображения, которые медленно загружались даже после загрузки.
Ответ 8
https://github.com/nicklockwood/FXImageView
Это изображение, которое может обрабатывать загрузку фона.
Использование
FXImageView *imageView = [[FXImageView alloc] initWithFrame:CGRectMake(0, 0, 100.0f, 150.0f)];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.asynchronous = YES;
//show placeholder
imageView.processedImage = [UIImage imageNamed:@"placeholder.png"];
//set image with URL. FXImageView will then download and process the image
[imageView setImageWithContentsOfURL:url];
Чтобы получить URL-адрес для вашего файла, вы можете найти следующее:
Получение ссылок/путей файла связки при запуске приложения
Ответ 9
Когда вы используете AFNetwork в приложении, вам не нужно использовать какой-либо блок для изображения загрузки, потому что AFNetwork предоставляет для него решение. Как показано ниже:
#import "UIImageView+AFNetworking.h"
И
Use **setImageWithURL** function of AFNetwork....
Спасибо
Ответ 10
Один из способов, который я реализовал, это следующее: (Хотя я не знаю, лучший ли он)
Сначала я создаю очередь с помощью последовательной асинхронной или параллельной очереди
queue = dispatch_queue_create("com.myapp.imageProcessingQueue", DISPATCH_QUEUE_SERIAL);**
or
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
**
Что бы вы ни находили лучше для своих нужд.
Далее:
dispatch_async( queue, ^{
// Load UImage from URL
// by using ImageWithContentsOfUrl or
UIImage *imagename = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
// Then to set the image it must be done on the main thread
dispatch_sync( dispatch_get_main_queue(), ^{
[page_cover setImage: imagename];
imagename = nil;
});
});