web-dev-qa-db-fra.com

iOS télécharger et enregistrer l'image dans l'application

Est-il possible pour moi de télécharger une image d'un site Web et de l'enregistrer en permanence dans mon application? Je n'en ai vraiment aucune idée, mais cela ferait une fonctionnalité intéressante pour mon application.

76
Samuli Lehtonen
51
Jhaliya

Bien qu'il soit vrai que les autres réponses ici fonctionneront, ce ne sont vraiment pas des solutions qui devraient être utilisées dans le code de production . (du moins pas sans modification)

Problèmes

Le problème avec ces réponses est que, si elles sont mises en œuvre telles quelles et ne sont pas appelées à partir d'un fil d'arrière-plan, elles bloqueront le fil principal lors du téléchargement et de l'enregistrement de l'image. C'est mauvais .

Si le fil principal est bloqué, les mises à jour de l'interface utilisateur ne se produiront pas avant la fin du téléchargement/de l'enregistrement de l'image. Pour illustrer ce que cela signifie, supposons que vous ajoutiez un UIActivityIndicatorView à votre application pour montrer à l'utilisateur que le téléchargement est toujours en cours (je vais l'utiliser comme exemple tout au long de cette réponse) avec le flux de contrôle approximatif suivant:

  1. L'objet responsable du démarrage du téléchargement est chargé.
  2. Dites à l'indicateur d'activité de commencer à animer.
  3. Démarrer le processus de téléchargement synchrone en utilisant +[NSData dataWithContentsOfURL:]
  4. Enregistrez les données (image) que vous venez de télécharger.
  5. Dites à l'indicateur d'activité d'arrêter l'animation.

Cela peut sembler un flux de contrôle raisonnable, mais cela dissimule un problème critique.

Lorsque vous appelez la méthode startAnimating de l'indicateur d'activité sur le thread principal (interface utilisateur), les mises à jour de l'interface utilisateur pour cet événement ne se produiront pas avant la prochaine mise à jour de la boucle d'exécution , et c'est là que réside le premier problème majeur.

Avant que cette mise à jour ait une chance de se produire, le téléchargement est déclenché et, puisqu'il s'agit d'une opération synchrone, il bloque le thread principal jusqu'à ce que le téléchargement soit terminé (l'enregistrement pose le même problème). Cela empêchera effectivement l'indicateur d'activité de démarrer son animation. Après cela, vous appelez la méthode stopAnimating de l'indicateur d'activité et vous vous attendez à ce que tout soit bon, mais ce n'est pas le cas.

À ce stade, vous vous demanderez probablement ce qui suit.

Pourquoi mon indicateur d'activité ne s'affiche-t-il jamais?

Eh bien, réfléchis comme ça. Vous indiquez à l’indicateur de démarrer mais il n’a aucune chance de le faire avant le téléchargement. Une fois le téléchargement terminé, vous indiquez à l’indicateur d’arrêter l’animation. Étant donné que le thread principal a été bloqué pendant toute l'opération, le comportement que vous observez est plutôt le long des lignes indiquant à l'indicateur de démarrer puis immédiatement à l'arrêt, même s'il existe une tâche de téléchargement volumineuse (éventuellement) importante.

Maintenant, dans le meilleur scénario , tout cela ne fait que créer une expérience utilisateur médiocre (toujours très mauvaise). Même si vous pensez que ce n'est pas grave car vous ne téléchargez qu'une petite image et que le téléchargement se fait presque instantanément, ce ne sera pas toujours le cas. Certains de vos utilisateurs peuvent avoir des connexions Internet lentes ou un problème avec le serveur empêchant le téléchargement de démarrer immédiatement.

Dans ces deux cas, l'application ne sera pas en mesure de traiter les mises à jour de l'interface utilisateur, ni même de toucher des événements, alors que votre tâche de téléchargement est en train de tourner en rond pour attendre que le téléchargement soit terminé ou que le serveur réponde à sa demande.

Cela signifie que le téléchargement synchrone à partir du thread principal vous empêche de tout implémenter pour indiquer à l'utilisateur qu'un téléchargement est en cours. Et comme les événements tactiles sont également traités sur le thread principal, cela élimine également la possibilité d'ajouter n'importe quel type de bouton d'annulation.

Puis, dans le pire des cas , vous commencerez à recevoir des rapports d'incident indiquant ce qui suit.

Type d'exception: 00000020 Codes d'exception: 0x8badf00d

Celles-ci sont faciles à identifier par le code d'exception 0x8badf00d, qui peut être lu comme "a mal mangé". Cette exception est levée par le chien de garde, dont le travail consiste à surveiller les tâches de longue durée qui bloquent le thread principal, et à tuer l'application incriminée si cela dure trop longtemps. On peut soutenir que cela reste un problème d’expérience utilisateur médiocre, mais si cela commence à se produire, l’application a franchi la frontière entre une mauvaise expérience utilisateur et une expérience utilisateur déplorable.

Voici quelques informations supplémentaires sur les causes possibles de ce problème: , questions techniques d'Apple sur la mise en réseau synchrone (abrégé par souci de concision).

La cause la plus fréquente de blocage du délai de surveillance dans une application réseau est la mise en réseau synchrone sur le thread principal. Il y a quatre facteurs contributifs ici:

  1. réseau synchrone - C’est ici que vous faites une demande réseau et bloquez l’attente de la réponse.
  2. thread principal - La mise en réseau synchrone est loin d'être idéale en général, mais elle pose des problèmes spécifiques si vous le faites sur le thread principal. N'oubliez pas que le thread principal est responsable de l'exécution de l'interface utilisateur. Si vous bloquez le thread principal pendant un laps de temps important, l'interface utilisateur ne répond plus de manière inacceptable.
  3. longs délais d'attente - Si le réseau disparaît simplement (par exemple, si l'utilisateur est dans un train qui passe dans un tunnel), toute demande de réseau en attente n'échouera pas avant l'expiration d'un délai d'attente donné ....

...

  1. chien de garde - Afin de garder l'interface utilisateur réactive, iOS inclut un mécanisme de chien de garde. Si votre application ne répond pas à temps à certains événements de l'interface utilisateur (lancement, suspension, reprise, arrêt), le chien de garde tue votre application et génère un rapport de blocage du délai d'attente du chien de garde. Le temps que le chien de garde vous donne n'est pas formellement documenté, mais il est toujours inférieur au délai d'attente du réseau.

Un aspect délicat de ce problème est qu’il dépend fortement de l’environnement réseau. Si vous testez toujours votre application dans votre bureau, où la connectivité réseau est bonne, vous ne verrez jamais ce type de panne. Cependant, une fois que vous aurez commencé à déployer votre application auprès des utilisateurs finaux (qui l'exécutera dans toutes sortes d'environnements réseau), les plantages de ce type deviendront courants.

Maintenant, je vais arrêter de parler des raisons pour lesquelles les réponses fournies pourraient être problématiques et je commencerai à proposer des solutions alternatives. Gardez à l'esprit que j'ai utilisé l'URL d'une petite image dans ces exemples et vous remarquerez une différence plus importante lorsque vous utilisez une image de résolution supérieure.


Solutions

Je vais commencer par montrer une version sécurisée des autres réponses, en ajoutant comment gérer les mises à jour de l'interface utilisateur. Ce sera le premier de plusieurs exemples, qui supposent tous que la classe dans laquelle elles sont implémentées possède des propriétés valides pour un UIImageView, un UIActivityIndicatorView, ainsi que la méthode documentsDirectoryURL pour accéder au répertoire de documents. Dans le code de production, vous souhaiterez peut-être implémenter votre propre méthode pour accéder au répertoire de documents en tant que catégorie sur NSURL pour une meilleure réutilisation du code, mais cela ne posera pas de problème pour ces exemples.

- (NSURL *)documentsDirectoryURL
{
    NSError *error = nil;
    NSURL *url = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory
                                                        inDomain:NSUserDomainMask
                                               appropriateForURL:nil
                                                          create:NO
                                                           error:&error];
    if (error) {
        // Figure out what went wrong and handle the error.
    }

    return url;
}

Ces exemples supposent également que le thread sur lequel ils ont démarré est le thread principal. Ce sera probablement le comportement par défaut à moins que vous ne commenciez votre tâche de téléchargement à partir d'un endroit comme le bloc de rappel d'une autre tâche asynchrone. Si vous démarrez votre téléchargement dans un endroit typique, comme une méthode du cycle de vie d'un contrôleur de vue (par exemple, viewDidLoad, viewWillAppear:, etc.), le comportement attendu sera généré.

Ce premier exemple utilisera la méthode +[NSData dataWithContentsOfURL:], mais avec quelques différences essentielles. En premier lieu, vous remarquerez que dans cet exemple, le tout premier appel que nous passons consiste à indiquer à l’indicateur d’activité de commencer l’animation, puis il existe une différence immédiate entre cet exemple et les exemples synchrones. Immédiatement, nous utilisons dispatch_async (), en passant dans la file d'attente simultanée globale pour déplacer l'exécution vers le thread en arrière-plan.

À ce stade, vous avez déjà grandement amélioré votre tâche de téléchargement. Étant donné que tout ce qui se trouve dans le bloc dispatch_async () aura désormais lieu hors du fil principal, votre interface ne sera plus bloquée et votre application sera libre de répondre aux événements tactiles.

Ce qui est important à noter ici, c’est que tout le code de ce bloc s’exécutera sur le fil d’arrière-plan, jusqu’au point où le téléchargement/l’enregistrement de l’image a réussi, point auquel vous voudrez peut-être indiquer à l’indicateur d’activité de s’arrêterAnimant ou appliquez la nouvelle image enregistrée à UIImageView. Quoi qu'il en soit, il s'agit de mises à jour de l'interface utilisateur, ce qui signifie que vous devez réacheminer le thread principal à l'aide de dispatch_get_main_queue () pour les effectuer. Ne pas le faire entraîne un comportement indéfini, ce qui peut entraîner la mise à jour de l'interface utilisateur après une période de temps inattendue, voire même provoquer un blocage. Assurez-vous toujours de revenir au thread principal avant d'effectuer les mises à jour de l'interface utilisateur.

// Start the activity indicator before moving off the main thread
[self.activityIndicator startAnimating];
// Move off the main thread to start our blocking tasks.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // Create the image URL from a known string.
    NSURL *imageURL = [NSURL URLWithString:@"http://www.google.com/images/srpr/logo3w.png"];

    NSError *downloadError = nil;
    // Create an NSData object from the contents of the given URL.
    NSData *imageData = [NSData dataWithContentsOfURL:imageURL
                                              options:kNilOptions
                                                error:&downloadError];
    // ALWAYS utilize the error parameter!
    if (downloadError) {
        // Something went wrong downloading the image. Figure out what went wrong and handle the error.
        // Don't forget to return to the main thread if you plan on doing UI updates here as well.
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.activityIndicator stopAnimating];
            NSLog(@"%@",[downloadError localizedDescription]);
        });
    } else {
        // Get the path of the application's documents directory.
        NSURL *documentsDirectoryURL = [self documentsDirectoryURL];
        // Append the desired file name to the documents directory path.
        NSURL *saveLocation = [documentsDirectoryURL URLByAppendingPathComponent:@"GCD.png"];

        NSError *saveError = nil;
        BOOL writeWasSuccessful = [imageData writeToURL:saveLocation
                                                options:kNilOptions
                                                  error:&saveError];
        // Successful or not we need to stop the activity indicator, so switch back the the main thread.
        dispatch_async(dispatch_get_main_queue(), ^{
            // Now that we're back on the main thread, you can make changes to the UI.
            // This is where you might display the saved image in some image view, or
            // stop the activity indicator.

            // Check if saving the file was successful, once again, utilizing the error parameter.
            if (writeWasSuccessful) {
                // Get the saved image data from the file.
                NSData *imageData = [NSData dataWithContentsOfURL:saveLocation];
                // Set the imageView's image to the image we just saved.
                self.imageView.image = [UIImage imageWithData:imageData];
            } else {
                NSLog(@"%@",[saveError localizedDescription]);
                // Something went wrong saving the file. Figure out what went wrong and handle the error.
            }

            [self.activityIndicator stopAnimating];
        });
    }
});

N'oubliez pas que la méthode présentée ci-dessus n'est toujours pas la solution idéale , car elle ne peut pas être annulée prématurément. Elle ne vous donne aucune indication sur la l’avancement du téléchargement, il ne peut gérer aucun type de défi d’authentification, il ne peut pas se voir attribuer un délai d’expiration précis, etc. (beaucoup de raisons). Je vais couvrir quelques-unes des meilleures options ci-dessous.

Dans ces exemples, je ne couvrirai que les solutions pour les applications ciblant iOS 7 et ultérieures, iOS 8 étant la version majeure actuelle (au moment de la rédaction de cet article), et , Apple suggère de ne prendre en charge que les versions N et N. -1 . Si vous devez prendre en charge des versions iOS plus anciennes, je vous recommande de consulter la classe NSURLConnection , ainsi que la version 1.0 de AFNetworking. Si vous examinez l'historique des révisions de cette réponse, vous trouverez des exemples de base utilisant NSURLConnection et ASIHTTPRequest , bien qu'il faille noter que ASIHTTPRequest n'est plus mis à jour et doit non être utilisé pour les nouveaux projets.


NSURLSession

Commençons par NSURLSession , qui a été introduit dans iOS 7, et améliore considérablement la facilité de mise en réseau dans iOS. Avec NSURLSession, vous pouvez facilement exécuter des requêtes HTTP asynchrones avec un blocage de rappel et gérer les problèmes d'authentification avec son délégué. Mais ce qui rend cette classe si spéciale, c’est qu’elle permet également aux tâches de téléchargement de continuer à s’exécuter même si l’application est envoyée en arrière-plan, s’arrête ou même se bloque. Voici un exemple de base de son utilisation.

// Start the activity indicator before starting the download task.
[self.activityIndicator startAnimating];

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// Use a session with a custom configuration
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
// Create the image URL from some known string.
NSURL *imageURL = [NSURL URLWithString:@"http://www.google.com/images/srpr/logo3w.png"];
// Create the download task passing in the URL of the image.
NSURLSessionDownloadTask *task = [session downloadTaskWithURL:imageURL completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
    // Get information about the response if neccessary.
    if (error) {
        NSLog(@"%@",[error localizedDescription]);
        // Something went wrong downloading the image. Figure out what went wrong and handle the error.
        // Don't forget to return to the main thread if you plan on doing UI updates here as well.
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.activityIndicator stopAnimating];
        });
    } else {
        NSError *openDataError = nil;
        NSData *downloadedData = [NSData dataWithContentsOfURL:location
                                                       options:kNilOptions
                                                         error:&openDataError];
        if (openDataError) {
            // Something went wrong opening the downloaded data. Figure out what went wrong and handle the error.
            // Don't forget to return to the main thread if you plan on doing UI updates here as well.
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"%@",[openDataError localizedDescription]);
                [self.activityIndicator stopAnimating];
            });
        } else {
            // Get the path of the application's documents directory.
            NSURL *documentsDirectoryURL = [self documentsDirectoryURL];
            // Append the desired file name to the documents directory path.
            NSURL *saveLocation = [documentsDirectoryURL URLByAppendingPathComponent:@"NSURLSession.png"];
            NSError *saveError = nil;

            BOOL writeWasSuccessful = [downloadedData writeToURL:saveLocation
                                                          options:kNilOptions
                                                            error:&saveError];
            // Successful or not we need to stop the activity indicator, so switch back the the main thread.
            dispatch_async(dispatch_get_main_queue(), ^{
                // Now that we're back on the main thread, you can make changes to the UI.
                // This is where you might display the saved image in some image view, or
                // stop the activity indicator.

                // Check if saving the file was successful, once again, utilizing the error parameter.
                if (writeWasSuccessful) {
                    // Get the saved image data from the file.
                    NSData *imageData = [NSData dataWithContentsOfURL:saveLocation];
                    // Set the imageView's image to the image we just saved.
                    self.imageView.image = [UIImage imageWithData:imageData];
                } else {
                    NSLog(@"%@",[saveError localizedDescription]);
                    // Something went wrong saving the file. Figure out what went wrong and handle the error.
                }

                [self.activityIndicator stopAnimating];
            });
        }
    }
}];

// Tell the download task to resume (start).
[task resume];

A partir de là, vous remarquerez que la méthode downloadTaskWithURL: completionHandler: renvoie une instance de NSURLSessionDownloadTask, sur laquelle une méthode d'instance -[NSURLSessionTask resume] est appelée. C'est la méthode qui indique en fait à la tâche de téléchargement de démarrer. Cela signifie que vous pouvez lancer votre tâche de téléchargement et, si vous le souhaitez, ne pas la démarrer (si nécessaire). Cela signifie également que tant que vous stockez une référence à la tâche, vous pouvez également utiliser ses méthodes cancel et suspend pour annuler ou suspendre la tâche si nécessaire.

Ce qui est vraiment génial à propos de NSURLSessionTasks, c’est qu’avec un peu de propriété KVO , vous pouvez surveiller les valeurs de ses propriétés countOfBytesExpectedToReceive et countOfBytesReceived NSByteCountFormatter , et créez facilement un indicateur de progression du téléchargement pour votre utilisateur avec des unités lisibles par l'homme (par exemple, 42 Ko sur 100 Ko).

Avant de quitter NSURLSession, j’aimerais souligner que la laideur de devoir renvoyer_sync aux tâches principales à différents moments du bloc de rappel du téléchargement peut être évitée. Si vous avez choisi cette route, vous pouvez initialiser la session avec son initialiseur qui vous permet de spécifier le délégué, ainsi que la file d'attente. Cela nécessitera que vous utilisiez le modèle de délégué au lieu des blocs de rappel, mais cela peut être avantageux car c'est le seul moyen de prendre en charge les téléchargements en arrière-plan.

NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration
                                                      delegate:self
                                                 delegateQueue:[NSOperationQueue mainQueue]];

AFNetworking 2.0

Si vous n'avez jamais entendu parler de AFNetworking , c'est à mon humble avis le plus complet des bibliothèques réseau. Il a été créé pour Objective-C, mais il fonctionne également dans Swift. Dans les mots de son auteur:

AFNetworking est une délicieuse bibliothèque de réseaux pour iOS et Mac OS X. Elle s’appuie sur le système Foundation URL Loading, complétant ainsi les puissantes abstractions réseau de haut niveau intégrées à Cocoa. Il présente une architecture modulaire avec des API bien conçues, riches en fonctionnalités et faciles à utiliser.

AFNetworking 2.0 prend en charge iOS 6 et les versions ultérieures, mais dans cet exemple, j'utiliserai sa classe AFHTTPSessionManager, qui requiert iOS 7 et plus en raison de son utilisation de toutes les nouvelles API autour de la classe NSURLSession. Cela deviendra évident lorsque vous lirez l'exemple ci-dessous, qui partage beaucoup de code avec l'exemple de NSURLSession ci-dessus.

Il y a cependant quelques différences que je voudrais souligner. Pour commencer, au lieu de créer votre propre session NSURLSession, vous allez créer une instance de AFURLSessionManager, qui gérera en interne une session NSURLSession. Cela vous permet de tirer parti de certaines de ses méthodes pratiques comme -[AFURLSessionManager downloadTaskWithRequest:progress:destination:completionHandler:]. L’intérêt de cette méthode est qu’elle vous permet de créer de manière assez concise une tâche de téléchargement avec un chemin de fichier de destination donné, un bloc d’achèvement et une entrée pour un pointeur NSProgress , sur lequel vous pouvez observer des informations sur la progression du téléchargement. Voici un exemple.

// Use the default session configuration for the manager (background downloads must use the delegate APIs)
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// Use AFNetworking's NSURLSessionManager to manage a NSURLSession.
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];

// Create the image URL from some known string.
NSURL *imageURL = [NSURL URLWithString:@"http://www.google.com/images/srpr/logo3w.png"];
// Create a request object for the given URL.
NSURLRequest *request = [NSURLRequest requestWithURL:imageURL];
// Create a pointer for a NSProgress object to be used to determining download progress.
NSProgress *progress = nil;

// Create the callback block responsible for determining the location to save the downloaded file to.
NSURL *(^destinationBlock)(NSURL *targetPath, NSURLResponse *response) = ^NSURL *(NSURL *targetPath, NSURLResponse *response) {
    // Get the path of the application's documents directory.
    NSURL *documentsDirectoryURL = [self documentsDirectoryURL];
    NSURL *saveLocation = nil;

    // Check if the response contains a suggested file name
    if (response.suggestedFilename) {
        // Append the suggested file name to the documents directory path.
        saveLocation = [documentsDirectoryURL URLByAppendingPathComponent:response.suggestedFilename];
    } else {
        // Append the desired file name to the documents directory path.
        saveLocation = [documentsDirectoryURL URLByAppendingPathComponent:@"AFNetworking.png"];
    }

    return saveLocation;
};

// Create the completion block that will be called when the image is done downloading/saving.
void (^completionBlock)(NSURLResponse *response, NSURL *filePath, NSError *error) = ^void (NSURLResponse *response, NSURL *filePath, NSError *error) {
    dispatch_async(dispatch_get_main_queue(), ^{
        // There is no longer any reason to observe progress, the download has finished or cancelled.
        [progress removeObserver:self
                      forKeyPath:NSStringFromSelector(@selector(fractionCompleted))];

        if (error) {
            NSLog(@"%@",error.localizedDescription);
            // Something went wrong downloading or saving the file. Figure out what went wrong and handle the error.
        } else {
            // Get the data for the image we just saved.
            NSData *imageData = [NSData dataWithContentsOfURL:filePath];
            // Get a UIImage object from the image data.
            self.imageView.image = [UIImage imageWithData:imageData];
        }
    });
};

// Create the download task for the image.
NSURLSessionDownloadTask *task = [manager downloadTaskWithRequest:request
                                                         progress:&progress
                                                      destination:destinationBlock
                                                completionHandler:completionBlock];
// Start the download task.
[task resume];

// Begin observing changes to the download task's progress to display to the user.
[progress addObserver:self
           forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
              options:NSKeyValueObservingOptionNew
              context:NULL];

Bien sûr, puisque nous avons ajouté la classe contenant ce code en tant qu'observateur à l'une des propriétés de l'instance de NSProgress, vous devez implémenter la méthode -[NSObject observeValueForKeyPath:ofObject:change:context:]. Dans ce cas, j'ai inclus un exemple de la manière dont vous pourriez mettre à jour une étiquette de progression pour afficher la progression du téléchargement. C'est vraiment facile. NSProgress a une méthode d'instance localizedDescription qui affiche les informations de progression dans un format lisible par l'homme et localisé.

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    // We only care about updates to fractionCompleted
    if ([keyPath isEqualToString:NSStringFromSelector(@selector(fractionCompleted))]) {
        NSProgress *progress = (NSProgress *)object;
        // localizedDescription gives a string appropriate for display to the user, i.e. "42% completed"
        self.progressLabel.text = progress.localizedDescription;
    } else {
        [super observeValueForKeyPath:keyPath
                             ofObject:object
                               change:change
                              context:context];
    }
}

N'oubliez pas que si vous souhaitez utiliser AFNetworking dans votre projet, vous devez suivre ses instructions d'installation et assurez-vous de bien utiliser #import <AFNetworking/AFNetworking.h>.

Alamofire

Enfin, je voudrais donner un dernier exemple en utilisant Alamofire . Ceci est une bibliothèque qui fait du réseautage dans Swift une promenade de gâteaux. Je suis à court de caractères pour entrer dans les détails du contenu de cet exemple, mais il fait à peu près la même chose que les derniers exemples, mais d'une manière plus belle sans doute.

// Create the destination closure to pass to the download request. I haven't done anything with them
// here but you can utilize the parameters to make adjustments to the file name if neccessary.
let destination = { (url: NSURL!, response: NSHTTPURLResponse!) -> NSURL in
    var error: NSError?
    // Get the documents directory
    let documentsDirectory = NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory,
        inDomain: .UserDomainMask,
        appropriateForURL: nil,
        create: false,
        error: &error
    )

    if let error = error {
        // This could be bad. Make sure you have a backup plan for where to save the image.
        println("\(error.localizedDescription)")
    }

    // Return a destination of .../Documents/Alamofire.png
    return documentsDirectory!.URLByAppendingPathComponent("Alamofire.png")
}

Alamofire.download(.GET, "http://www.google.com/images/srpr/logo3w.png", destination)
    .validate(statusCode: 200..<299) // Require the HTTP status code to be in the Successful range.
    .validate(contentType: ["image/png"]) // Require the content type to be image/png.
    .progress { (bytesRead, totalBytesRead, totalBytesExpectedToRead) in
        // Create an NSProgress object to represent the progress of the download for the user.
        let progress = NSProgress(totalUnitCount: totalBytesExpectedToRead)
        progress.completedUnitCount = totalBytesRead

        dispatch_async(dispatch_get_main_queue()) {
            // Move back to the main thread and update some progress label to show the user the download is in progress.
            self.progressLabel.text = progress.localizedDescription
        }
    }
    .response { (request, response, _, error) in
        if error != nil {
            // Something went wrong. Handle the error.
        } else {
            // Open the newly saved image data.
            if let imageData = NSData(contentsOfURL: destination(nil, nil)) {
                dispatch_async(dispatch_get_main_queue()) {
                    // Move back to the main thread and add the image to your image view.
                    self.imageView.image = UIImage(data: imageData)
                }
            }
        }
    }
92
Mick MacCallum

Vous ne pouvez rien enregistrer dans le bundle de l'application, mais vous pouvez utiliser +[NSData dataWithContentsOfURL:] pour stocker l'image dans le répertoire des documents de votre application, par exemple:

NSData *imageData = [NSData dataWithContentsOfURL:myImageURL];
NSString *imagePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"/myImage.png"];
[imageData writeToFile:imagePath atomically:YES];

Pas exactement permanent , mais il y reste au moins jusqu'à ce que l'utilisateur supprime l'application.

38
user142019

C'est le concept principal. S'amuser ;)

NSURL *url = [NSURL URLWithString:@"http://example.com/yourImage.png"];
NSData *data = [NSData dataWithContentsOfURL:url];
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
path = [path stringByAppendingString:@"/yourLocalImage.png"];
[data writeToFile:path atomically:YES];
13
cem

Depuis que nous sommes sur IO5, vous n’avez plus besoin d’écrire des images sur le disque de manière nécessaire.
Vous pouvez maintenant définir "autoriser le stockage externe" sur un attribut binaire coredata . Selon les notes de publication d'apples, cela signifie ce qui suit:

Les petites valeurs de données telles que les vignettes d’image peuvent être efficacement stockées dans un fichier base de données, mais les meilleures photos ou autres supports sont mieux gérés directement par le système de fichiers. Vous pouvez maintenant spécifier que la valeur d'un .__ géré. attribut object peut être stocké en tant qu’enregistrement externe - voir setAllowsExternalBinaryDataStorage: Lorsque cette option est activée, Core Data détermine de manière heuristique, valeur par valeur, si il devrait sauvegarder les données directement dans la base de données ou stocker un URI dans un fichier fichier séparé qu'il gère pour vous. Vous ne pouvez pas interroger sur la base du contenu d'une propriété de données binaires si vous utilisez cette option.

7
Alexander

Comme d'autres personnes l'ont dit, il existe de nombreux cas dans lesquels vous devriez télécharger une image dans le fil de discussion en arrière-plan sans bloquer l'interface utilisateur.

Dans ce cas, ma solution préférée consiste à utiliser une méthode pratique avec des blocs, comme celui-ci: (crédit -> iOS: Comment télécharger des images de manière asynchrone (et rendre votre UITableView Scroll Fast rapide )

- (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
{
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
                               if ( !error )
                               {
                                   UIImage *image = [[UIImage alloc] initWithData:data];
                                   completionBlock(YES,image);
                               } else{
                                   completionBlock(NO,nil);
                               }
                           }];
}

Et appelle ça comme

NSURL *imageUrl = //...

[[MyUtilManager sharedInstance] downloadImageWithURL:[NSURL URLWithString:imageURL] completionBlock:^(BOOL succeeded, UIImage *image) {
    //Here you can save the image permanently, update UI and do what you want...
}];
3
andreacipriani

Voici comment je télécharge une bannière publicitaire. Il est préférable de le faire en arrière-plan si vous téléchargez une image de grande taille ou un groupe d’images.

- (void)viewDidLoad {
    [super viewDidLoad];

    [self performSelectorInBackground:@selector(loadImageIntoMemory) withObject:nil];

}
- (void)loadImageIntoMemory {
    NSString *temp_Image_String = [[NSString alloc] initWithFormat:@"http://yourwebsite.com/MyImageName.jpg"];
    NSURL *url_For_Ad_Image = [[NSURL alloc] initWithString:temp_Image_String];
    NSData *data_For_Ad_Image = [[NSData alloc] initWithContentsOfURL:url_For_Ad_Image];
    UIImage *temp_Ad_Image = [[UIImage alloc] initWithData:data_For_Ad_Image];
    [self saveImage:temp_Ad_Image];
    UIImageView *imageViewForAdImages = [[UIImageView alloc] init];
    imageViewForAdImages.frame = CGRectMake(0, 0, 320, 50);
    imageViewForAdImages.image = [self loadImage];
    [self.view addSubview:imageViewForAdImages];
}
- (void)saveImage: (UIImage*)image {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString* path = [documentsDirectory stringByAppendingPathComponent: @"MyImageName.jpg" ];
    NSData* data = UIImagePNGRepresentation(image);
    [data writeToFile:path atomically:YES];
}
- (UIImage*)loadImage {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString* path = [documentsDirectory stringByAppendingPathComponent:@"MyImageName.jpg" ];
    UIImage* image = [UIImage imageWithContentsOfFile:path];
    return image;
}
1
Bobby

Voici le code pour télécharger une image de manière asynchrone à partir de l'URL et ensuite enregistrer où vous voulez dans objective-c: ->

    + (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
        {
            NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
            [NSURLConnection sendAsynchronousRequest:request
                                               queue:[NSOperationQueue mainQueue]
                                   completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
                                       if ( !error )
                                       {
                                           UIImage *image = [[UIImage alloc] initWithData:data];
                                           completionBlock(YES,image);
                                       } else{
                                           completionBlock(NO,nil);
                                       }
                                   }];
        }
1
Mohd. Asif

Vous pouvez télécharger une image sans bloquer l'interface utilisateur à l'aide de NSURLSessionDataTask.

+(void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
 {
 NSURLSessionDataTask*  _sessionTask = [[NSURLSession sharedSession] dataTaskWithRequest:[NSURLRequest requestWithURL:url]
                                            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    if (error != nil)
        {
          if ([error code] == NSURLErrorAppTransportSecurityRequiresSecureConnection)
                {
                    completionBlock(NO,nil);
                }
         }
    else
     {
      [[NSOperationQueue mainQueue] addOperationWithBlock: ^{
                        dispatch_async(dispatch_get_main_queue(), ^{
                        UIImage *image = [[UIImage alloc] initWithData:data];
                        completionBlock(YES,image);

                        });

      }];

     }

                                            }];
    [_sessionTask resume];
}
0
Mohd. Asif

Si vous utilisez la bibliothèque AFNetworking pour télécharger une image et que les images utilisent dans UITableview, vous pouvez utiliser le code ci-dessous dans cellForRowAtIndexPath

 [self setImageWithURL:user.user_ProfilePicturePath toControl:cell.imgView]; 
 
-(void)setImageWithURL:(NSURL*)url toControl:(id)ctrl
{
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    AFImageRequestOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:request imageProcessingBlock:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
        if (image) {
            if([ctrl isKindOfClass:[UIButton class]])
            {
                UIButton btn =(UIButton)ctrl;
                [btn setBackgroundImage:image forState:UIControlStateNormal];
            }
            else
            {
                UIImageView imgView = (UIImageView)ctrl;
                imgView.image = image;
            }

} } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) { NSLog(@"No Image"); }]; [operation start];}
0
ASHISHT

Voici une solution Swift 5 permettant de télécharger et d’enregistrer une image ou en général un fichier dans le répertoire des documents à l’aide de Alamofire:

func dowloadAndSaveFile(from url: URL) {
    let destination: DownloadRequest.DownloadFileDestination = { _, _ in
        var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
        documentsURL.appendPathComponent(url.lastPathComponent)
        return (documentsURL, [.removePreviousFile])
    }
    let request = SessionManager.default.download(url, method: .get, to: destination)
    request.validate().responseData { response in
        switch response.result {
        case .success:
            if let destinationURL = response.destinationURL {
                print(destinationURL)
            }
        case .failure(let error):
            print(error.localizedDescription)
        }
    }
}
0
tanaschita