webentwicklung-frage-antwort-db.com.de

Wie lädt man ein Bild asynchron in eine UIImageView?

Ich habe eine UIView mit einer UIImageView-Subansicht. Ich muss ein Bild in die UIImageView laden, ohne die Benutzeroberfläche zu blockieren. Der blockierende Aufruf scheint zu sein: UIImage imageNamed:. Ich dachte, ich habe dieses Problem gelöst:

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

Das Bild ist klein (150x100).

Die Benutzeroberfläche ist jedoch beim Laden des Bildes immer noch blockiert. Was vermisse ich ?

Hier ist ein kleines Codebeispiel, das dieses Verhalten zeigt:

Erstellen Sie eine neue Klasse basierend auf UIImageView, setzen Sie die Benutzerinteraktion auf YES, fügen Sie zwei Instanzen in einer UIView hinzu und implementieren Sie die touchesBegan-Methode wie folgt:

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

Weisen Sie das Tag 1 einer dieser imageViews zu. 

Was passiert genau, wenn Sie fast gleichzeitig auf die beiden Ansichten tippen, beginnend mit der Ansicht, die ein Bild lädt? Wird die Benutzeroberfläche gesperrt, weil auf die Rückkehr von [self setImage:[UIImage imageNamed:@"woodenTile.jpg"]]; gewartet wird? Wenn ja, wie kann ich das asynchron machen?

Hier ist ein Projekt auf github mit ipmcc-Code 

Drücken Sie lange und ziehen Sie dann, um ein Rechteck um die schwarzen Quadrate zu zeichnen. Soweit ich seine Antwort verstehe, sollte das weiße Auswahlrechteck beim ersten Laden des Bildes theoretisch nicht blockiert werden, ist es aber tatsächlich.

Das Projekt enthält zwei Bilder (eine kleine: woodenTile.jpg und eine größere: bois.jpg). Das Ergebnis ist bei beiden das gleiche.

Bildformat

Ich verstehe nicht wirklich, wie das mit dem Problem zusammenhängt, das ich immer noch habe, wenn die UI beim ersten Laden des Bildes blockiert wird, aber PNG-Bilder dekodieren ohne die UI zu blockieren, während JPG-Bilder die UI blockieren.

Chronologie der Ereignisse 

step 0step 1

Die Sperrung der Benutzeroberfläche beginnt hier ..

step 2

.. und endet hier.

step 3

AFNetworking-Lösung 

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

Die meiste Zeit protokolliert es: success: {512, 512}

Es gibt auch gelegentlich Protokolle: success: {0, 0}

Und manchmal: failure: <NSURLResponse: 0x146c0000> { URL: file:///var/mobile/Appl...

Das Bild wird jedoch nie verändert.

24
alecail

Das Problem ist, dass UIImage das Bild erst liest und dekodiert, wenn es zum ersten Mal tatsächlich verwendet/gezeichnet wird. Um zu erzwingen, dass diese Arbeit in einem Hintergrundthread ausgeführt wird, müssen Sie das Bild im Hintergrundthread verwenden/zeichnen, bevor Sie den Hauptthread -setImage: ausführen. Das hat für mich funktioniert:

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

BEARBEITEN: Die Benutzeroberfläche blockiert nicht. Sie haben es speziell für die Verwendung von UILongPressGestureRecognizer eingerichtet, die standardmäßig eine halbe Sekunde wartet, bevor Sie etwas tun. Der Haupt-Thread verarbeitet zwar immer noch Ereignisse, aber bis zum Erreichen des GR-Zeitlimits wird nichts passieren. Wenn du das tust:

    longpress.minimumPressDuration = 0.01;

... Sie werden feststellen, dass es viel knackiger wird. Das Laden der Bilder ist hier nicht das Problem.

EDIT 2: Ich habe mir den Code angesehen, wie er in github auf einem iPad 2 läuft, und ich bekomme einfach nicht den Schluckauf, den Sie beschreiben. In der Tat ist es ziemlich glatt. Hier ist ein Screenshot vom Ausführen des Codes im CoreAnimation-Instrument:

enter image description here

Wie Sie in der oberen Grafik sehen können, geht der FPS bis zu ~ 60 FPS und bleibt während der gesamten Geste dort. In der unteren Grafik sehen Sie den Blip bei etwa 16s, wo das Bild zuerst geladen wird. Sie sehen jedoch, dass die Bildrate nicht abnimmt. Nur durch visuelle Inspektion sehe ich, wie sich die Auswahlebene überschneidet, und es gibt eine kleine, aber beobachtbare Verzögerung zwischen der ersten Schnittmenge und dem Erscheinungsbild des Bildes. Soweit ich das beurteilen kann, erledigt der Hintergrundladecode seine Arbeit wie erwartet.

Ich wünschte, ich könnte Ihnen mehr helfen, aber ich sehe das Problem einfach nicht.

52
ipmcc

Sie können AFNetworking library verwenden, indem Sie die Kategorie importieren

"UIImageView + AFNetworking.m" Und verwenden Sie die Methode wie folgt:

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

hoffe das hilft .

5
itechnician

Ich hatte ein sehr ähnliches Problem mit meiner Anwendung, bei der ich viele Bilder herunterladen musste, und gleichzeitig wurde meine Benutzeroberfläche ständig aktualisiert. Hier ist der einfache Tutorial-Link, der mein Problem gelöst hat:

NSOperations & NSOperationQueues Tutorial

4
Geekoder

das ist der gute Weg:

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

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

Warum verwenden Sie keine Drittanbieter-Bibliothek wie AsyncImageView ? Wenn Sie dies verwenden, müssen Sie lediglich Ihr AsyncImageView-Objekt deklarieren und die URL oder das Bild übergeben, die Sie laden möchten. Während des Ladevorgangs wird eine Aktivitätsanzeige angezeigt, und die Benutzeroberfläche wird nicht blockiert.

2

- (void) touchesBegan: wird im Hauptthread aufgerufen. Durch Aufruf von dispatch_async (dispatch_get_main_queue) setzen Sie den Block einfach in die Warteschlange. Dieser Block wird von GCD verarbeitet, wenn die Warteschlange bereit ist (d. H. Das System ist mit der Bearbeitung Ihrer Berührungen fertig). Aus diesem Grund können Sie Ihre woodenTile-Datei nicht geladen und zu self.image geladen anzeigen, bis Sie Ihren Finger loslassen und GCD alle Blöcke verarbeiten lassen, die sich in der Warteschlange in der Warteschlange befinden.

Ersetzen:

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

durch :

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

sollte Ihr Problem lösen… zumindest für den Code, der es anzeigt.

1
Patrock

Wenn Sie AFNetwork in einer Anwendung verwenden, müssen Sie keinen Block für das Lade-Image verwenden, da AFNetwork eine Lösung dafür bietet. Wie nachstehend:

#import "UIImageView+AFNetworking.h"

Und 

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

Vielen Dank

0
Hindu

Verwenden Sie SDWebImage : Es wird nicht nur das Bild im Hintergrund heruntergeladen und zwischengespeichert, sondern auch geladen und dargestellt. 

Ich habe es mit guten Ergebnissen in einer Tableview verwendet, die große Bilder hatte, die selbst nach dem Herunterladen nur langsam geladen wurden.

0
Lescai Ionel

https://github.com/nicklockwood/FXImageView

Dies ist eine Bildansicht, die das Laden des Hintergrunds unterstützt.

Verwendungszweck

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

Um eine URL für Ihre Datei zu erhalten, könnte Folgendes interessant sein:

Bündel-Dateiverweise/-pfade beim Start der App abrufen

0
Marc

Eine Möglichkeit, die ich implementiert habe, ist folgende: (Obwohl ich nicht weiß, ob es das Beste ist)

Zuerst erstelle ich eine Warteschlange mit seriell asynchron oder auf paralleler Warteschlange

queue = dispatch_queue_create("com.myapp.imageProcessingQueue", DISPATCH_QUEUE_SERIAL);**

or

queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);

**

Was auch immer Sie für Ihre Bedürfnisse besser finden können.

Danach:

 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