web-dev-qa-db-ja.com

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に2つのインスタンスを追加して、次のように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をこれらのimageViewsのいずれかに割り当てます。

画像を読み込むビューから始めて、2つのビューをほぼ同時にタップすると正確に何が起こりますか? [self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];が戻るのを待っているため、UIはブロックされますか?もしそうなら、どうすれば非同期でこれを行うことができますか?

githubのプロジェクト ipmccコード

長押ししてからドラッグして、黒い正方形の周りに長方形を描きます。彼の答えを理解しているように、理論的には、画像が最初にロードされるときに白い選択矩形がブロックされるべきではありませんが、実際にはブロックされます。

2つの画像がプロジェクトに含まれています(1つは小さい:woodenTile.jpgともう1つはbois.jpg)。結果は両方で同じです。

画像フォーマット

画像が初めて読み込まれているときにUIがブロックされるという問題とこれがどのように関係するのかはよくわかりませんが、PNG画像はUIをブロックせずにデコードしますが、JPG画像はUIをブロックします。

イベントの年表

step 0step 1

UIのブロックはここから始まります。

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

ただし、イメージは変更されません。

25
alecail

問題は、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];
    });
});

編集:UIはブロックしていません。特に、UILongPressGestureRecognizerを使用するように設定しました。これは、デフォルトでは、何もする前に0.5秒待機します。メインスレッドはまだイベントを処理していますが、そのGRがタイムアウトするまで何も起こりません。これを行う場合:

    longpress.minimumPressDuration = 0.01;

...あなたはそれがより速くなることに気付くでしょう。画像の読み込みはここでは問題ではありません。

編集2:私はiPad 2で実行されているgithubに投稿されたコードを見ましたが、あなたが説明しているしゃっくりを取得できません。実際、非常にスムーズです。以下は、CoreAnimationインストゥルメントでコードを実行したときのスクリーンショットです。

enter image description here

上のグラフを見るとわかるように、FPSは最大60FPSに達し、ジェスチャーの間中ずっとそこに留まります。一番下のグラフでは、画像が最初に読み込まれる約16秒でブリップが表示されますが、フレームレートの低下がないことがわかります。目視で確認すると、選択レイヤーが交差していることがわかります。最初の交差点と画像の外観の間には、わずかではありますが目に見える遅延があります。私が知る限り、バックグラウンドでロードするコードは期待どおりに機能しています。

もっとお役に立てればと思いますが、問題が発生していません。

53
ipmcc

AFNetworking libraryを使用できます。このライブラリでは、カテゴリをインポートすることにより

「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];

お役に立てれば 。

5
itechnician

多くの画像をダウンロードする必要があり、UIが絶えず更新されていたアプリケーションで、同様の問題が発生しました。以下は、私の問題を解決した簡単なチュートリアルリンクです。

NSOperationsおよびNSOperationQueuesチュートリアル

4
Geekoder

これは良い方法です:

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

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

AsyncImageView のようなサードパーティのライブラリを使用しないのはなぜですか?これを使用して、AsyncImageViewオブジェクトを宣言し、ロードするURLまたは画像を渡すだけです。画像の読み込み中にアクティビティインジケータが表示され、UIをブロックするものはありません。

2

-(void)touchesBegan:はメインスレッドで呼び出されます。 dispatch_async(dispatch_get_main_queue)を呼び出すことで、ブロックをキューに入れるだけです。このブロックは、キューの準備が整うとGCDによって処理されます(つまり、システムはタッチの処理を終了します)。そのため、指を離し、GCDがメインキューにキューイングされているすべてのブロックを処理するまで、woodenTileがロードされ、self.imageに割り当てられているのを確認できません。

交換:

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

沿って :

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

少なくとも問題を示すコードについては、問題を解決する必要があります。

1
Patrock

アプリケーションでAFNetworkを使用している場合、AFNetworkがソリューションを提供するため、イメージのロードにブロックを使用する必要はありません。以下のように:

#import "UIImageView+AFNetworking.h"

そして

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

ありがとう

0
Hindu

SDWebImage の使用を検討してください。バックグラウンドでイメージをダウンロードおよびキャッシュするだけでなく、ロードおよびレンダリングも行います。

ダウンロードした後でも読み込みに時間がかかる大きな画像が含まれるテーブルビューで、良い結果で使用しました。

0
Lescai Ionel

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を取得するには、次のことがおもしろいかもしれません。

アプリ起動時のバンドルファイル参照/パスの取得

0
Marc

私がそれを実装した1つの方法は次のとおりです:(それが最良のものかどうかはわかりませんが)

最初に、シリアル非同期または並列キューを使用してキューを作成します

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

        });
0
hyphestos