简体   繁体   English

将图像异步加载到 UIImage 中

[英]Loading an image into UIImage asynchronously

I'm developing an iOS 4 application with iOS 5.0 SDK and XCode 4.2.我正在使用 iOS 5.0 SDK 和 XCode 4.2 开发 iOS 4 应用程序。

I have to show some post blogs into a UITableView.我必须在 UITableView 中显示一些帖子博客。 When I have retreived all web service data, I use this method to create an UITableViewCell:当我检索到所有 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;
}

But this line:但是这一行:

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

it is so slow (entry.photo has a http url).它太慢了(entry.photo 有一个 http url)。

Is there any way to load that image asynchronously?有没有办法异步加载该图像? I think it is difficult because tableView:cellForRowAtIndexPath is called very often.我认为这很困难,因为 tableView:cellForRowAtIndexPath 经常被调用。

I wrote a custom class to do just this, using blocks and GCD:我编写了一个自定义类来做到这一点,使用块和 GCD:

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

And in your ViewController在你的 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;
    }

    }];
}

It is very fast and will load the images without affecting the UI or scrolling speed of the TableView.它非常快,并且可以在不影响 TableView 的 UI 或滚动速度的情况下加载图像。

*** Note - This example assumes ARC is being used. *** 注意 - 此示例假设正在使用 ARC。 If not, you will need to manage your own releases on objects)如果没有,您将需要管理您自己的对象发布)

In iOS 6 and later dispatch_get_current_queue gives deprecation warnings.在 iOS 6 及更高版本中 dispatch_get_current_queue 给出弃用警告。

Here is an alternative that is a synthesis of the @ElJay answer above and the article by @khanlou here .这里是作为@ElJay答案上述和由@khanlou制品的合成的替代这里

Create a category on UIImage:在 UIImage 上创建一个类别:

UIImage+Helpers.h UIImage+Helpers.h

@interface UIImage (Helpers)

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

@end

UIImage+Helpers.m 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

Take a look at SDWebImage:看看 SDWebImage:

https://github.com/rs/SDWebImage https://github.com/rs/SDWebImage

It's a fantastic set of classes that handle everything for you.这是一组很棒的类,可以为您处理所有事情。

Tim蒂姆

Swift:迅速:

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

Usage:用法:

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

Considering Failure Case.考虑失败案例。

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

Swift 4 |斯威夫特 4 | Async loading of image图像的异步加载

Make a new class named ImageLoader.swift创建一个名为 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()
    }
}

To Use in your ViewController class:要在您的 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
                }
            })

Yes it's relatively easy.是的,这相对容易。 The idea is something like:这个想法是这样的:

  1. Create a mutable array to hold your images创建一个可变数组来保存您的图像
  2. Populate the array with placeholders if you like (or NSNull objects if you don't)如果您愿意,可以使用占位符填充数组(如果您不喜欢,则使用 NSNull 对象)
  3. Create a method to fetch your images asynchronously in the background创建一种在后台异步获取图像的方法
  4. When an image has arrived, swap your placeholder with the real image and do a [self.tableView reloadData]当图像到达时,将占位符与真实图像交换并执行[self.tableView reloadData]

I have tested this approach many times and gives great results.我已经多次测试了这种方法并给出了很好的结果。 If you need any help / examples with the async part (I recommend to use gcd for that) let me know.如果您需要有关异步部分的任何帮助/示例(我建议为此使用 gcd),请告诉我。

you can easily do it perfectly if you use the sample code provided by Apple for this purpose: the sample code : Lazy Image如果您为此目的使用Apple提供的示例代码,您可以轻松地做到这一点:示例代码: Lazy Image

Just look at the delegate rowforcell and add icondownloader files to you project.只需查看委托 rowforcell 并将 icondownloader 文件添加到您的项目即可。

The only change you have to do is to change apprecord object with your object.您唯一需要做的更改是使用您的对象更改 apprecord 对象。

While SDWebImage and other 3rd party maybe great solutions, if you are not keen on using 3rd party APIs, you can develop your own solution too.虽然 SDWebImage 和其他 3rd 方可能是不错的解决方案,但如果您不热衷于使用 3rd 方 API,您也可以开发自己的解决方案。

Refer to this tutorial about lazy loading which also talks about how should you model your data inside table view.请参阅有关延迟加载的教程,其中还讨论了如何在表视图中对数据进行建模。

AsyncImage can synchronously load and display an image. AsyncImage可以同步加载和显示图像。 is officially introduced after iOS 15在 iOS 15 之后正式推出

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

It also supports:它还支持:

See more in doc文档中查看更多

Use below code snippet to loading image into imageview使用下面的代码片段将图像加载到 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)
    }
}
}

You'll probably have to subclass your UIImageView.您可能必须对 UIImageView 进行子类化。 I've recently made a simple project to explain this particular task - background asynchronous image loading - take a look at my project at GitHub.我最近做了一个简单的项目来解释这个特定的任务——后台异步图像加载——看看在 GitHub 上的项目 Specifically, look at KDImageView class.具体来说,看看KDImageView类。

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

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