it-swarm.com.ru

Как асинхронно загрузить изображение в 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];
        });
    });
}

Изображение маленькое (150х100).

Однако пользовательский интерфейс по-прежнему блокируется при загрузке изображения. Что мне не хватает?

Вот небольшой пример кода, демонстрирующий это поведение:

Создайте новый класс на основе UIImageView, установите для его взаимодействия с пользователем значение YES, добавьте два экземпляра в UIView и реализуйте его метод touchesBegan следующим образом:

-(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 0step 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's 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...

Но изображение никогда не меняется.

24
alecail

Проблема в том, что 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];
    });
});

Правка: интерфейс не блокирует. Вы специально настроили его на использование UILongPressGestureRecognizer, который по умолчанию ждет полсекунды, прежде чем что-либо делать. Основной поток все еще обрабатывает события, но ничего не произойдет, пока не истечет время GR. Если вы делаете это:

    longpress.minimumPressDuration = 0.01;

... вы заметите, что это становится намного быстрее. Загрузка изображения не является проблемой здесь.

Правка 2: Я посмотрел на код, опубликованный на github, работает на iPad 2, и я просто не понимаю, какой сбой вы описываете. На самом деле, это довольно гладко. Вот снимок экрана с запуском кода в инструменте CoreAnimation:

enter image description here

Как вы можете видеть на верхнем графике, FPS увеличивается до ~ 60 FPS и остается там на протяжении всего жеста. На нижнем графике вы можете увидеть всплывающее окно примерно через 16 с, где изображение загружается впервые, но вы можете видеть, что частота кадров не падает. Просто из визуального осмотра я вижу, что слой выделения пересекается, и между первым пересечением и появлением изображения есть небольшая, но заметная задержка. Насколько я могу судить, код фоновой загрузки работает, как и ожидалось.

Я хотел бы помочь вам больше, но я просто не вижу проблемы.

52
ipmcc

Вы можете использовать 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];

надеюсь это поможет .

5
itechnician

У меня была очень похожая проблема с моим приложением, когда мне приходилось загружать много изображений, и наряду с этим мой интерфейс постоянно обновлялся. Ниже приведена простая учебная ссылка, которая разрешила мою проблему:

Учебник NSOperations & NSOperationQueues

4
Geekoder

это хороший способ:

-(void)updateImageViewContent {
    dispatch_async(dispatch_get_main_queue(), ^{

        UIImage * img = [UIImage imageNamed:@"background.jpg"];
        [[self imageView] setImage:img];
    });
}
3
Mirko Catalano

Почему вы не используете сторонние библиотеки, такие как AsyncImageView ? Используя это, все, что вам нужно сделать, это объявить свой объект AsyncImageView и передать URL или изображение, которое вы хотите загрузить. Индикатор активности будет отображаться во время загрузки изображения, и ничто не будет блокировать пользовательский интерфейс.

2
Luciano Rodríguez

- (void) touchchesBegan: вызывается в основном потоке. Вызывая dispatch_async (dispatch_get_main_queue), вы просто помещаете блок в очередь. Этот блок будет обработан GCD, когда очередь будет готова (то есть система закончила обработку ваших касаний). Вот почему вы не можете видеть, как загружен деревянный каркас и назначен для self.image, пока вы не отпустите палец и не разрешите GCD обрабатывать все блоки, которые были поставлены в очередь в основной очереди.

Замена:

    dispatch_async(dispatch_get_main_queue(), ^{
    [self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];
  });

от :

[self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];

должен решить вашу проблему ... по крайней мере, для кода, который ее демонстрирует.

1
Patrock

Когда вы используете AFNetwork в приложении, вам не нужно использовать какой-либо блок для загрузки изображения, потому что AFNetwork предоставляет решение для этого. Как показано ниже:

#import "UIImageView+AFNetworking.h"

А также 

   Use **setImageWithURL** function of AFNetwork....

Спасибо

0
Hindu

Попробуйте использовать SDWebImage : он не только загружает и кэширует изображение в фоновом режиме, но также загружает и отображает его. 

Я использовал его с хорошими результатами в виде таблицы, в которой были большие изображения, которые загружались медленно даже после загрузки.

0
Lescai Ionel

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 для вашего файла, вам может быть интересно следующее:

Получение ссылок/путей к пакетам при запуске приложения

0
Marc

Один из способов, которыми я это реализовал, заключается в следующем: (хотя я не знаю, является ли он лучшим)

Сначала я создаю очередь, используя Serial Asynchronous или Parallel Queue

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;
            });

        });
0
hyphestos