Подтвердить что ты не робот

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

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

4b9b3361

Ответ 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;
            });

        });