簡體   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