异步加载图像到UIImage

时间:2012-03-20 11:37:01

标签: ios cocoa-touch uitableview asynchronous uiimageview

我正在使用iOS 5.0 SDK和XCode 4.2开发iOS 4应用程序。

我必须在UITableView中显示一些帖子博客。当我检索所有Web服务数据时,我使用此方法创建UITableViewCell:

- (BlogTableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString* cellIdentifier = @"BlogCell";

    BlogTableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];

    if (cell == nil)
    {
        NSArray* topLevelObjects =  [[NSBundle mainBundle] loadNibNamed:@"BlogTableViewCell" owner:nil options:nil];

        for(id currentObject in topLevelObjects)
        {
            if ([currentObject isKindOfClass:[BlogTableViewCell class]])
            {
                cell = (BlogTableViewCell *)currentObject;
                break;
            }
        }
    }

    BlogEntry* entry = [blogEntries objectAtIndex:indexPath.row];

    cell.title.text = entry.title;
    cell.text.text = entry.text;
    cell.photo.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:entry.photo]]];

    return cell;
}

但这一行:

cell.photo.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:entry.photo]]];

它太慢了(entry.photo有一个http网址)。

有没有办法异步加载该图像?我认为很难,因为tableView:cellForRowAtIndexPath经常被调用。

12 个答案:

答案 0 :(得分:81)

我使用块和GCD编写了一个自定义类来执行此操作:

WebImageOperations.h

#import <Foundation/Foundation.h>

@interface WebImageOperations : NSObject {
}

// This takes in a string and imagedata object and returns imagedata processed on a background thread
+ (void)processImageDataWithURLString:(NSString *)urlString andBlock:(void (^)(NSData *imageData))processImage;
@end

WebImageOperations.m

#import "WebImageOperations.h"
#import <QuartzCore/QuartzCore.h>

@implementation WebImageOperations


+ (void)processImageDataWithURLString:(NSString *)urlString andBlock:(void (^)(NSData *imageData))processImage
{
    NSURL *url = [NSURL URLWithString:urlString];

    dispatch_queue_t callerQueue = dispatch_get_current_queue();
    dispatch_queue_t downloadQueue = dispatch_queue_create("com.myapp.processsmagequeue", NULL);
    dispatch_async(downloadQueue, ^{
        NSData * imageData = [NSData dataWithContentsOfURL:url];

        dispatch_async(callerQueue, ^{
            processImage(imageData);
        });
    });
    dispatch_release(downloadQueue);
}

@end

在你的ViewController中

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

    // Pass along the URL to the image (or change it if you are loading there locally)
    [WebImageOperations processImageDataWithURLString:entry.photo andBlock:^(NSData *imageData) {
    if (self.view.window) {
        UIImage *image = [UIImage imageWithData:imageData];

        cell.photo.image = image;
    }

    }];
}

速度非常快,可以在不影响TableView的UI或滚动速度的情况下加载图像。

***注 - 此示例假定正在使用ARC。如果没有,您将需要在对象上管理自己的版本)

答案 1 :(得分:52)

在iOS 6及更高版本中,dispatch_get_current_queue提供弃用警告。

这是一个替代方案,它是上述@ElJay答案和@khanlou here的文章的综合。

在UIImage上创建一个类别:

<强>的UIImage + Helpers.h

@interface UIImage (Helpers)

+ (void) loadFromURL: (NSURL*) url callback:(void (^)(UIImage *image))callback;

@end

<强>的UIImage + Helpers.m

#import "UIImage+Helpers.h"

@implementation UIImage (Helpers)

+ (void) loadFromURL: (NSURL*) url callback:(void (^)(UIImage *image))callback {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
    dispatch_async(queue, ^{
        NSData * imageData = [NSData dataWithContentsOfURL:url];
        dispatch_async(dispatch_get_main_queue(), ^{
            UIImage *image = [UIImage imageWithData:imageData];
            callback(image);
        });
    });
}

@end

答案 2 :(得分:28)

看看SDWebImage:

https://github.com/rs/SDWebImage

这是一套很棒的课程,可以为你处理一切。

答案 3 :(得分:6)

斯威夫特:

extension UIImage {

    // Loads image asynchronously
    class func loadFromURL(url: NSURL, callback: (UIImage)->()) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), {

            let imageData = NSData(contentsOfURL: url)
            if let data = imageData {
                dispatch_async(dispatch_get_main_queue(), {
                    if let image = UIImage(data: data) {
                        callback(image)
                    }
                })
            }
        })
    }
}

用法:

// First of all remove the old image (required for images in cells)
imageView.image = nil 

// Load image and apply to the view
UIImage.loadFromURL("http://...", callback: { (image: UIImage) -> () in
     self.imageView.image = image
})

答案 4 :(得分:3)

考虑失败案例。

- (void) loadFromURL: (NSURL*) url callback:(void (^)(UIImage *image))callback {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
    dispatch_async(queue, ^{
        NSError * error = nil;
        NSData * imageData = [NSData dataWithContentsOfURL:url options:0 error:&error];
        if (error)
            callback(nil);

        dispatch_async(dispatch_get_main_queue(), ^{
            UIImage *image = [UIImage imageWithData:imageData];
            callback(image);
        });
    });
}

答案 5 :(得分:3)

Swift 4 |图像的异步加载

创建一个名为ImageLoader.swift的新类

import UIKit

class ImageLoader {

var cache = NSCache<AnyObject, AnyObject>()

class var sharedInstance : ImageLoader {
    struct Static {
        static let instance : ImageLoader = ImageLoader()
    }
    return Static.instance
}

func imageForUrl(urlString: String, completionHandler:@escaping (_ image: UIImage?, _ url: String) -> ()) {
        let data: NSData? = self.cache.object(forKey: urlString as AnyObject) as? NSData

        if let imageData = data {
            let image = UIImage(data: imageData as Data)
            DispatchQueue.main.async {
                completionHandler(image, urlString)
            }
            return
        }

    let downloadTask: URLSessionDataTask = URLSession.shared.dataTask(with: URL.init(string: urlString)!) { (data, response, error) in
        if error == nil {
            if data != nil {
                let image = UIImage.init(data: data!)
                self.cache.setObject(data! as AnyObject, forKey: urlString as AnyObject)
                DispatchQueue.main.async {
                    completionHandler(image, urlString)
                }
            }
        } else {
            completionHandler(nil, urlString)
        }
    }
    downloadTask.resume()
    }
}

在ViewController类中使用:

ImageLoader.sharedInstance.imageForUrl(urlString: "https://www.logodesignlove.com/images/classic/apple-logo-rob-janoff-01.jpg", completionHandler: { (image, url) in
                if image != nil {
                    self.imageView.image = image
                }
            })

答案 6 :(得分:2)

是的,它相对容易。这个想法是这样的:

  1. 创建一个可变数组来保存图像
  2. 如果您愿意,可以使用占位符填充数组(如果不喜欢,则填充NSNull对象)
  3. 创建一种在后台异步获取图像的方法
  4. 当图像到达时,将占位符与实际图像交换并执行[self.tableView reloadData]
  5. 我已经多次测试过这种方法并且效果很好。如果您需要有异步部分的任何帮助/示例(我建议使用gcd)请告诉我。

答案 7 :(得分:1)

如果您使用Apple提供的示例代码,则可以轻松完成此操作: 示例代码: Lazy Image

只需查看委托rowforcell并将icondownloader文件添加到项目中即可。

您唯一需要做的改变就是用您的对象更改欣赏对象。

答案 8 :(得分:1)

虽然SDWebImage和其他第三方可能是很好的解决方案,但如果您不热衷于使用第三方API,您也可以开发自己的解决方案。

请参阅此tutorial about lazy loading,其中还讨论了如何在表视图中建模数据。

答案 9 :(得分:1)

AsyncImage 可以同步加载和显示图片。在 iOS 15 之后正式引入

cell.photo = AsyncImage(url: URL(string: entry.photo))
    .frame(width: 200, height: 200)

它还支持:

doc中查看更多

答案 10 :(得分:0)

您可能必须继承您的UIImageView。我最近做了一个简单的项目来解释这个特定的任务 - 后台异步图像加载 - 看看GitHub上的at my project。具体来说,请查看KDImageView类。

答案 11 :(得分:0)

使用以下代码片段将图像加载到imageview中

func imageDownloading() {

DispatchQueue.global().async {

    let url = URL(string: "http://verona-api.municipiumstaging.it/system/images/image/image/22/app_1920_1280_4.jpg")!

    do {

        let data = try Data(contentsOf: url)

        DispatchQueue.main.async {

        self.imageView.image = UIImage(data: data)

        }

    } catch {
        print(error.localizedDescription)
    }
}
}