繁体   English   中英

如何在 UIImageView 中异步加载图像?

[英]How to asynchronously load an image in an UIImageView?

我有一个带有 UIImageView 子视图的 UIView。 我需要在不阻塞 UI 的情况下在 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)。

但是,加载图像时 UI 仍然被阻止。 我错过了什么?

这是一个展示此行为的小代码示例:

基于 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 分配给这些 imageView 之一。

当您从加载图像的视图开始几乎同时点击两个视图时,究竟会发生什么? UI 是否因为等待[self setImage:[UIImage imageNamed:@"woodenTile.jpg"]]; 返回 ? 如果是这样,我该如何异步执行此操作?

这是github上的一个项目,带有ipmcc代码

使用长按然后拖动以在黑色方块周围绘制一个矩形。 据我了解他的回答,理论上白色选择矩形不应该在第一次加载图像时被阻止,但实际上是这样。

项目中包含两张图片(一张小的:woodenTile.jpg,一张大的:bois.jpg)。 结果与两者相同。

图片格式

我真的不明白这与我在第一次加载图像时仍然遇到的 UI 被阻塞的问题有什么关系,但是 PNG 图像在不阻塞 UI 的情况下解码,而 JPG 图像确实阻塞了 UI。

事件年表

步骤 0步骤1

UI的阻塞从这里开始..

第2步

.. 到此结束。

第 3 步

AF网络解决方案

    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...

但图像从未改变。

问题是UIImage实际上并没有读取和解码图像,直到它第一次实际使用/绘制。 要在后台线程上强制执行此工作,您必须在执行主线程-setImage:之前在后台线程上使用/绘制图像-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工具中运行代码的屏幕截图:

在此输入图像描述

正如您在顶部图表中看到的那样,FPS最高可达~60FPS并在整个手势中保持不变。 在底部的图表中,您可以看到大约16s的blip,这是图像首次加载的位置,但您可以看到帧速率没有下降。 仅从视觉检查,我看到选择层相交,并且在第一个交叉点和图像外观之间存在一个小但可观察到的延迟。 据我所知,后台加载代码正在按预期执行。

我希望我能帮助你更多,但我只是没有看到问题。

您可以使用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];

希望这可以帮助 。

我的应用程序有一个非常类似的问题,我必须下载大量图像,并且我的UI不断更新。 以下是解决我的问题的简单教程链接:

NSOperations和NSOperationQueues教程

这是好方法:

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

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

为什么不使用像AsyncImageView这样的第三方库? 使用它,您所要做的就是声明您的AsyncImageView对象并传递您要加载的url或图像。 在图像加载期间将显示活动指示器,并且不会阻止UI。

- (void)touchesBegan:在主线程中调用。 通过调用dispatch_async(dispatch_get_main_queue),您只需将块放入队列中。 当队列准备好时(即系统结束并处理您的触摸),此块将由GCD处理。 这就是为什么你不能看到你的woodenTile被加载并分配给self.image,直到你释放你的手指并让GCD处理所有在主队列中排队的块。

更换:

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

通过:

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

应该解决你的问题...至少对于展示它的代码。

考虑使用SDWebImage :它不仅可以在后台下载和缓存图像,还可以加载和渲染它。

我已经在tableview中使用了它,结果很好,即使在下载之后也有很慢的加载速度。

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,您可能会发现以下内容:

在应用启动时获取捆绑文件引用/路径

在应用程序中使用AFNetwork时,您无需使用任何块来加载映像,因为AFNetwork为其提供了解决方案。 如下:

#import "UIImageView+AFNetworking.h"

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

谢谢

我实现它的一种方法是以下:(虽然我不知道它是否是最好的一个)

首先,我使用串行异步或并行队列创建队列

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

        });

iOS 15 中的UIImage引入了一组方法,用于在后台线程上异步解码图像和创建缩略图

func prepareForDisplay(completionHandler: (UIImage?) -> Void)

异步解码图像并提供新图像以在视图和动画中显示。

func prepareThumbnail(of: CGSize, completionHandler: (UIImage?) -> Void)

在后台线程上异步创建指定大小的缩略图。

如果您需要更多地控制您希望解码发生的位置,例如特定队列,您还可以使用一组类似的同步API:
func prepareForDisplay() -> UIImage?
func 准备Thumbnail(of: CGSize) -> UIImage?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM