web-dev-qa-db-ja.com

画像をUIImageに非同期的にロードする

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 URLがあります)。

そのイメージを非同期にロードする方法はありますか? tableView:cellForRowAtIndexPathが非常に頻繁に呼び出されるため、難しいと思います。

36
VansFannel

SDWebImageを見てください:

https://github.com/rs/SDWebImage

それはあなたのためにすべてを処理する素晴らしいクラスのセットです。

ティム

28
tarmes

ブロックと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が使用されていることを前提としています。そうでない場合は、オブジェクトの独自のリリースを管理する必要があります)

81
LJ Wilson

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
52
bbrame

迅速:

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

失敗のケースを考慮します。

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

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
                }
            })
3
Akshay Sarda

はい、比較的簡単です。アイデアは次のようなものです:

  1. 画像を保持する可変配列を作成します
  2. 必要に応じて、プレースホルダーを配列に入力します(そうでない場合はNSNullオブジェクト)
  3. バックグラウンドで非同期に画像を取得するメソッドを作成します
  4. 画像が到着したら、プレースホルダーを実際の画像と交換し、[self.tableView reloadData]

私はこのアプローチを何度もテストしてきましたが、すばらしい結果が得られました。非同期部分のヘルプ/例が必要な場合(そのためにgcdを使用することをお勧めします)、教えてください。

2
Alladinian

Appleこの目的のために提供されるサンプルコードを使用する場合、完全に簡単に行うことができます:サンプルコード: Lazy Image

委任rowforcellを見て、icondownloaderファイルをプロジェクトに追加するだけです。

必要な変更は、オブジェクトでapprecordオブジェクトを変更することだけです。

1
Firas KADHUM

SDWebImageと他のサードパーティは素晴らしいソリューションかもしれませんが、サードパーティのAPIを使用したくない場合は、独自のソリューションを開発することもできます。

これを参照してください 遅延読み込みに関するチュートリアル これは、テーブルビュー内でデータをモデル化する方法についても説明しています。

1
Nirav Bhatt

おそらく、UIImageViewをサブクラス化する必要があります。最近、この特定のタスクを説明する単純なプロジェクトを作成しました-バックグラウンドの非同期イメージの読み込み-GitHubで 私のプロジェクトで を見てください。具体的には、 KDImageView クラスを見てください。

0
Kyr Dunenkoff