2012-03-20 11 views
34

Sto sviluppando un'applicazione iOS 4 con iOS 5.0 SDK e XCode 4.2.Caricamento di un'immagine in UIImmagine in modo asincrono

Devo mostrare alcuni post blog in un UITableView. Quando avrò recuperato i tutti i dati di servizio web, io uso questo metodo per creare un 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; 
} 

Ma questa linea:

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

è così lento (entry.photo ha un URL http).

C'è un modo per caricare quell'immagine in modo asincrono? Penso che sia difficile perché tableView: cellForRowAtIndexPath viene chiamato molto spesso.

risposta

27

Date un'occhiata a SDWebImage:

https://github.com/rs/SDWebImage

Si tratta di una serie fantastica di classi che gestiscono tutto per voi.

Tim

+0

Grazie per la risposta. Funziona!! – VansFannel

+3

Se qualcuno ha bisogno di ridimensionare/ritagliare le funzionalità, ho integrato questa libreria con UIImage + Ridimensiona libreria. Controllalo in https://github.com/toptierlabs/ImageCacheResize – Tony

+0

questa libreria ha problemi se si scrivono informazioni in NSTemporaryDirectory parallelamente a questo. Qualche altra soluzione? – jose920405

0

Probabilmente dovrai sottoclassi il tuo UIImageView. Di recente ho realizzato un semplice progetto per spiegare questo particolare compito: caricamento di immagini asincrone in background: dai un'occhiata a at my project su GitHub. In particolare, guarda la classe KDImageView.

2

Sì, è relativamente facile. L'idea è qualcosa di simile:

  1. creare una matrice mutevole per contenere le immagini
  2. compilare la matrice con i segnaposto, se ti piace (o oggetti NSNull se non)
  3. Creare un metodo per andare a prendere la tua immagini in modo asincrono in background
  4. Quando un'immagine è arrivato, scambiare il tuo segnaposto con l'immagine reale e fare un [self.tableView reloadData]

ho testato questo approccio molte volte e dà grande resu è. Se hai bisogno di aiuto/esempi con la parte asincrona (ti consiglio di usare gcd per quello) fammi sapere.

+0

Se l'immagine è memorizzata su un server web, perché spendere le risorse per scaricare tutte le immagini quando hai solo bisogno delle immagini per le celle visualizzate? –

+0

Principalmente per motivi di memorizzazione nella cache, ma sì per un set di grandi dimensioni è possibile recuperare solo le immagini necessarie per la visualizzazione. Il mio punto era sottolineare il concetto generale. – Alladinian

+0

Vorrei aggiungere che 'reloadData' dovrebbe essere chiamato dal thread principale in modo che le viste vengano aggiornate quasi immediatamente. Grazie per il consiglio! –

81

ho scritto una classe personalizzata di fare proprio questo, utilizzando blocchi e 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 

E nel tuo

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

    }]; 
} 

È molto veloce e caricherà le immagini influenzando l'interfaccia utente o la velocità di scorrimento di TableView.

*** Nota: questo esempio presuppone che ARC venga utilizzato. In caso contrario, è necessario gestire le proprie uscite su oggetti)

+0

Come si impedisce il ri-download delle immagini in questo caso? (Voglio dire ogni volta che una cella deve essere riutilizzata, il metodo si attiva e scarica nuovamente i dati dal server giusto?) – Alladinian

+1

Basta aggiungere l'immagine di una matrice a un array e controllare l'esistenza dell'imageata nell'array prima di caricare è asincrono.Ti preparerò qualcosa, se ne avrai bisogno (ma lo farò più tardi oggi) –

+0

Oh no, nessun problema Non ho problemi con questo, volevo solo assicurarmi di non aver perso nulla nel tuo esempio :) evviva – Alladinian

1

si può facilmente farlo perfettamente se si utilizza il codice di esempio fornito da Apple per questo scopo: il codice di esempio: Lazy Image

Basta guardare la rowforcell delegato e aggiungere i file icondownloader a tu progetti.

L'unico cambiamento che devi fare è cambiare l'oggetto apprecord con il tuo oggetto.

51

In iOS 6 e versioni successive dispatch_get_current_queue fornisce avvisi di ritiro.

Ecco un'alternativa che è una sintesi della risposta @ElJay sopra e dell'articolo di @khanlou here.

Creare una categoria a 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 
+0

Una categoria è davvero la strada da percorrere. –

+0

come posso chiamare il metodo in una classe? UIImage * img = [UIImage loadFromURL: [NSURL URLWithString: @ ""] callback: <#^(UIImage * image) callback #>]; Cosa devo inserire nella richiamata? – Pinturikkio

+0

@bbrame per favore dammi una risposta! – Pinturikkio

6

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

considera la gestione del caso di errore – Mabedan

+0

mm confuso !! l'estensione aggiuntiva func è loadFromURL e la chiamata a loadAsync? – Alupotha

3

Considerando Caso Failure.

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

Mentre SDWebImage e altre forse grandi soluzioni terze parti, se non siete appassionati di utilizzando le API 3rd party, è possibile sviluppare la propria soluzione troppo.

Fare riferimento a questo tutorial about lazy loading che indica anche come modellare i dati nella vista tabella.

0

Swift 4 | caricamento asincrona dell'immagine

Fare una nuova classe denominata 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() 
    } 
} 

utilizzare nella classe 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 
       } 
      }) 
Problemi correlati