Frage iOS herunterladen und speichern Sie das Bild in der App


Ist es mir möglich, ein Bild von der Website herunterzuladen und dauerhaft in meiner App zu speichern? Ich habe wirklich keine Ahnung, aber es wäre eine nette Funktion für meine App.


75
2018-06-04 17:01


Ursprung


Antworten:


Asynchron heruntergeladene Bilder mit Caching

Asynchron heruntergeladene Bilder mit Caching

Hier ist noch ein Repos, mit dem Bilder im Hintergrund heruntergeladen werden können


51
2018-06-04 17:11



Obwohl es stimmt, dass die anderen Antworten hier funktionieren werden, Sie sind wirklich keine Lösungen, die jemals in Produktionscode verwendet werden sollten. (zumindest nicht ohne Modifikation)

Probleme

Das Problem mit diesen Antworten besteht darin, dass sie den Hauptthread beim Herunterladen und Speichern des Bildes blockieren, wenn sie so implementiert werden, wie sie sind und nicht von einem Hintergrundthread aufgerufen werden. Das ist Schlecht.

Wenn der Hauptthread blockiert ist, werden UI-Aktualisierungen erst dann durchgeführt, wenn das Herunterladen / Speichern des Image abgeschlossen ist. Als Beispiel für das, was dies bedeutet, sagen Sie, dass Sie Ihrer App eine UIActivityIndicatorView hinzufügen, um dem Benutzer zu zeigen, dass der Download noch läuft (ich werde dies als Beispiel in dieser Antwort verwenden), mit dem folgenden Grobkontrollfluss:

  1. Das für den Start des Downloads verantwortliche Objekt wird geladen.
  2. Weisen Sie den Aktivitätsindikator an, mit der Animation zu beginnen.
  3. Starten Sie den synchronen Download-Prozess mit +[NSData dataWithContentsOfURL:]
  4. Speichern Sie die gerade heruntergeladenen Daten (Bild).
  5. Weisen Sie den Aktivitätsindikator an, die Animation zu stoppen.

Nun scheint dies ein vernünftiger Kontrollfluss zu sein, verschleiert aber ein kritisches Problem.

Wenn Sie die startAnimating-Methode des Aktivitätsindikators im Hauptthread (UI) aufrufen, werden die UI-Aktualisierungen für dieses Ereignis erst beim nächsten Mal ausgeführt Hauptlaufschleife Updates, und das ist, wo das erste große Problem ist.

Bevor dieses Update eine Chance hat, wird der Download ausgelöst, und da dies eine synchrone Operation ist, blockiert es den Hauptthread, bis der Download abgeschlossen ist (das Speichern hat das gleiche Problem). Dadurch wird verhindert, dass der Aktivitätsindikator seine Animation startet. Danach rufen Sie die Methode stopAnimating des Aktivitätsindikators auf und erwarten, dass alles gut ist, aber das ist es nicht.

An dieser Stelle werden Sie sich wahrscheinlich fragen, was das Folgende ist.

Warum wird mein Aktivitätsindikator nicht angezeigt?

Nun, denke darüber nach. Sie sagen dem Indikator zu starten, aber es wird keine Chance erhalten, bevor der Download startet. Nachdem der Download abgeschlossen ist, weisen Sie den Indikator an, die Animation zu beenden. Da der Hauptthread durch die gesamte Operation blockiert wurde, ist das Verhalten, das Sie tatsächlich sehen, eher entlang der Linien, die den Indikator anweisen, zu starten und dann sofort zu beenden, obwohl es eine (möglicherweise) große Download-Aufgabe dazwischen gab.

Jetzt, in der Best-Case-SzenarioAll dies verursacht eine schlechte Benutzererfahrung (immer noch sehr schlecht). Selbst wenn Sie denken, dass dies keine große Sache ist, weil Sie nur ein kleines Bild herunterladen und der Download fast sofort erfolgt, wird das nicht immer der Fall sein. Einige Ihrer Benutzer haben möglicherweise langsame Internetverbindungen, oder etwas könnte auf der Serverseite falsch sein, wenn der Download sofort / überhaupt nicht gestartet wird.

In beiden Fällen ist die App nicht in der Lage, UI-Updates zu verarbeiten oder sogar Ereignisse zu berühren, während sich der Download-Task um seine Daumen dreht und darauf wartet, dass der Download abgeschlossen wird oder der Server auf seine Anfrage reagiert.

Dies bedeutet, dass das synchrone Herunterladen aus dem Hauptthread verhindert, dass Sie möglicherweise etwas implementieren, um dem Benutzer anzuzeigen, dass gerade ein Download ausgeführt wird. Und da Touch-Events auch auf dem Haupt-Thread bearbeitet werden, besteht die Möglichkeit, dass auch jede Art von Cancel-Button hinzugefügt wird.

Dann in der Worst-Case-Szenario, erhalten Sie Absturzberichte mit folgenden Angaben.

Ausnahmetyp: 00000020 Ausnahmecodes: 0x8badf00d

Diese sind durch den Ausnahmecode leicht zu identifizieren 0x8badf00d, was als "aß schlechtes Essen" gelesen werden kann. Diese Ausnahme wird vom Watchdog-Timer ausgelöst, dessen Aufgabe es ist, lange laufende Aufgaben zu überwachen, die den Hauptthread blockieren, und die anstößige App zu beenden, wenn dies zu lange dauert. Dies ist zwar immer noch ein schlechtes Problem mit der Nutzererfahrung, aber wenn dies eintritt, hat die App die Grenze zwischen schlechter Nutzererfahrung und schrecklicher Nutzererfahrung überschritten.

Hier finden Sie weitere Informationen darüber, was dazu führen kann Apples technische Fragen und Antworten über synchrone Vernetzung (verkürzt für die Kürze).

Die häufigste Ursache für Watchdog-Timeout-Abstürze in einer Netzwerkanwendung ist das synchrone Netzwerk im Hauptthread. Hier sind vier Faktoren beteiligt:

  1. Synchrones Netzwerk - Hier stellen Sie eine Netzwerkanfrage und blockieren das Warten auf die Antwort.
  2. main thread - Synchrones Netzwerk ist im Allgemeinen weniger als ideal, verursacht jedoch bestimmte Probleme, wenn Sie es im Hauptthread tun. Denken Sie daran, dass der Hauptthread für die Ausführung der Benutzeroberfläche zuständig ist. Wenn Sie den Hauptthread für einen erheblichen Zeitraum blockieren, reagiert die Benutzeroberfläche nicht mehr inakzeptabel.
  3. lange Timeouts - Wenn das Netzwerk gerade verschwindet (zum Beispiel wenn der Benutzer in einem Zug ist, der in einen Tunnel fährt), wird jede ausstehende Netzwerkanforderung nicht fehlschlagen, bis ein Timeout abgelaufen ist ....

...

  1. Watchdog - Um die Benutzerschnittstelle ansprechend zu halten, enthält iOS einen Watchdog-Mechanismus. Wenn Ihre Anwendung nicht rechtzeitig auf bestimmte Benutzeroberflächenereignisse reagiert (Starten, Anhalten, Fortsetzen, Beenden), beendet der Watchdog Ihre Anwendung und generiert einen Watchdog-Timeout-Absturzbericht. Die Zeit, die der Watchdog Ihnen gibt, ist nicht offiziell dokumentiert, aber er ist immer kürzer als ein Netzwerk-Timeout.

Ein schwieriger Aspekt dieses Problems ist, dass es stark von der Netzwerkumgebung abhängig ist. Wenn Sie Ihre Anwendung immer in Ihrem Büro testen, wo die Netzwerkverbindung gut ist, werden Sie diese Art von Absturz nie sehen. Sobald Sie jedoch mit der Bereitstellung Ihrer Anwendung für Endbenutzer beginnen, die sie in allen möglichen Netzwerkumgebungen ausführen, werden solche Abstürze häufig auftreten.

Jetzt höre ich auf, warum die bereitgestellten Antworten problematisch sein könnten, und biete alternative Lösungen an. Denken Sie daran, dass ich in diesen Beispielen die URL eines kleinen Bildes verwendet habe, und Sie werden einen größeren Unterschied feststellen, wenn Sie ein Bild mit höherer Auflösung verwenden.


Lösungen

Ich werde damit beginnen, eine sichere Version der anderen Antworten zu zeigen, zusätzlich dazu, wie man mit UI-Updates umgeht. Dies wird das erste von mehreren Beispielen sein, von denen alle annehmen, dass die Klasse, in der sie implementiert sind, gültige Eigenschaften für eine UIImageView, eine UIActivityIndicatorView sowie die documentsDirectoryURL Methode, um auf das Dokumentenverzeichnis zuzugreifen. In Produktionscode möchten Sie möglicherweise eine eigene Methode implementieren, um auf das Dokumentenverzeichnis als eine Kategorie auf NSURL für eine bessere Wiederverwendbarkeit von Code zuzugreifen, aber für diese Beispiele wird das in Ordnung sein.

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

Diese Beispiele gehen auch davon aus, dass der Thread, an dem sie beginnen, der Hauptthread ist. Dies ist wahrscheinlich das Standardverhalten, es sei denn, Sie starten Ihren Download-Task von einer Stelle wie dem Callback-Block einer anderen asynchronen Task. Wenn Sie den Download an einem typischen Ort starten, z. B. eine Lebenszyklusmethode eines View-Controllers (z. B. viewDidLoad, viewWillAppear: usw.), wird das erwartete Verhalten erzeugt.

Dieses erste Beispiel verwendet die +[NSData dataWithContentsOfURL:] Methode, aber mit einigen wichtigen Unterschieden. Zum einen werden Sie feststellen, dass in diesem Beispiel der erste Aufruf, den wir machen, darin besteht, dem Aktivitätsindikator mitzuteilen, dass er mit der Animation beginnt. Dann besteht ein unmittelbarer Unterschied zwischen diesem und den synchronen Beispielen. Sofort verwenden wir dispatch_async () und übergeben die globale gleichzeitige Warteschlange, um die Ausführung in den Hintergrundthread zu verschieben.

An diesem Punkt haben Sie Ihre Download-Aufgabe bereits erheblich verbessert. Da alles innerhalb des dispatch_async () -Blocks nun außerhalb des Hauptthreads stattfindet, wird Ihre Benutzeroberfläche nicht mehr gesperrt und Ihre App kann auf Berührungsereignisse reagieren.

Was hier zu beachten ist, ist, dass der gesamte Code innerhalb dieses Blocks im Hintergrundthread ausgeführt wird, bis zu dem Punkt, an dem das Herunterladen / Speichern des Image erfolgreich war. Zu diesem Zeitpunkt sollten Sie den Aktivitätsindikator an stopAnimating erinnern , oder wenden Sie das neu gespeicherte Bild auf eine UIImageView an. In beiden Fällen handelt es sich um Aktualisierungen der Benutzeroberfläche, dh Sie müssen den Hauptthread mithilfe von dispatch_get_main_queue () zurücksenden, um sie auszuführen. Andernfalls kann es zu einem nicht definierten Verhalten kommen, wodurch die Benutzeroberfläche nach einer unerwarteten Zeit aktualisiert oder sogar zum Absturz gebracht werden kann. Vergewissern Sie sich immer, dass Sie zum Hauptthread zurückkehren, bevor Sie die UI-Aktualisierungen durchführen.

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

Jetzt bedenken Sie, dass die Methode oben ist immer noch keine ideale Lösung Wenn man bedenkt, dass es nicht vorzeitig abgebrochen werden kann, gibt es keinen Hinweis auf den Fortschritt des Downloads, es kann keine Authentifizierungsanfrage beantworten, es kann kein spezifisches Zeitüberschreitungsintervall gegeben werden, usw. (viele und viele Gründe dafür). Ich werde ein paar der besseren Optionen unten behandeln.

In diesen Beispielen werde ich nur Lösungen für Apps behandeln, die auf iOS 7 und höher abzielen (zum Zeitpunkt des Schreibens). IOS 8 ist die aktuelle Hauptversion und Apple schlägt vor, nur die Versionen N und N-1 zu unterstützen. Wenn Sie ältere iOS - Versionen unterstützen müssen, empfehle ich, in die NSURLConnection Klasse, sowie die 1.0 Version von AFNetworking. Wenn Sie sich den Überarbeitungsverlauf dieser Antwort ansehen, finden Sie grundlegende Beispiele mit NSURLConnection und ASIHTTPAnfrage, obwohl zu beachten ist, dass ASIHTTPRequest nicht mehr gepflegt wird und sollte nicht für neue Projekte verwendet werden.


NSURLSitzung

Lass uns beginnen mit NSURLSitzung, die in iOS 7 eingeführt wurde, und verbessert erheblich die Leichtigkeit, mit der die Vernetzung in iOS erfolgen kann. Mit NSURLSession können Sie problemlos asynchrone HTTP-Anforderungen mit einem Callback-Block ausführen und Authentifizierungsherausforderungen mit seinem Delegaten bearbeiten. Was diese Klasse zu etwas Besonderem macht, ist, dass Download-Aufgaben auch dann ausgeführt werden können, wenn die Anwendung an den Hintergrund gesendet wird, beendet wird oder sogar abstürzt. Hier ist ein grundlegendes Beispiel für seine Verwendung.

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

Von diesem werden Sie bemerken, dass das downloadTaskWithURL: completionHandler: Methode gibt eine Instanz von NSURLSessionDownloadTask zurück, auf der sich eine Instanzmethode befindet -[NSURLSessionTask resume] wird genannt. Dies ist die Methode, mit der der Download-Task tatsächlich gestartet wird. Dies bedeutet, dass Sie Ihre Download-Aufgabe hochfahren können, und falls gewünscht, halten Sie sie beim Starten an (falls erforderlich). Dies bedeutet auch, dass Sie, solange Sie einen Verweis auf die Aufgabe speichern, auch dessen verwenden können cancel und suspend Methoden zum Abbrechen oder Pausieren der Aufgabe, falls erforderlich.

Was an NSURLSessionTasks wirklich cool ist, ist das mit ein bisschen KVOSie können die Werte der Eigenschaften countOfBytesExpectedToReceive und countOfBytesReceived überwachen und diese Werte in ein NSByteCountFormatterund erstellen Sie einfach einen Download-Fortschrittsanzeiger für Ihren Benutzer mit visuell lesbaren Einheiten (z. B. 42 KB von 100 KB).

Bevor ich jedoch von NSURLSession abfahre, möchte ich darauf hinweisen, dass die Hässlichkeit, an mehreren verschiedenen Stellen im Callback-Block des Downloads zu den Hauptthreads dispatch_async zu senden, vermieden werden kann. Wenn Sie sich für diese Route entschieden haben, können Sie die Sitzung mit ihrem Initialisierer initialisieren, mit dem Sie sowohl den Delegaten als auch die Stellvertreterwarteschlange angeben können. Dies erfordert, dass Sie das Delegatenmuster anstelle der Rückrufblöcke verwenden, aber dies kann von Vorteil sein, da es die einzige Möglichkeit ist, Hintergrunddownloads zu unterstützen.

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

AFNetwork 2.0

Wenn du noch nie davon gehört hast AFNetworkingEs ist IMHO das Ende aller Netzwerkbibliotheken. Es wurde für Objective-C erstellt, funktioniert aber auch in Swift. In den Worten seines Autors:

AFNetworking ist eine reizvolle Netzwerkbibliothek für iOS und Mac OS X. Es basiert auf dem Foundation URL Loading System und erweitert die leistungsstarken High-Level-Netzwerk-Abstraktionen in Cocoa. Es hat eine modulare Architektur mit gut gestalteten, funktionsreichen APIs, die eine Freude zu verwenden sind.

AFNetworking 2.0 unterstützt iOS 6 und höher, aber in diesem Beispiel verwende ich die Klasse AFTHTPSessionManager, die aufgrund der Verwendung aller neuen APIs um die Klasse NSURLSession iOS 7 und höher erfordert. Dies wird offensichtlich, wenn Sie das folgende Beispiel lesen, das viel Code mit dem obigen Beispiel von NSURLSession teilt.

Es gibt ein paar Unterschiede, auf die ich hinweisen möchte. Anstatt eine eigene NSURLSession zu erstellen, erstellen Sie zunächst eine Instanz von AFURLSessionManager, die NSURLSession intern verwaltet. Auf diese Weise können Sie einige seiner Komfortmethoden wie -[AFURLSessionManager downloadTaskWithRequest:progress:destination:completionHandler:]. Interessant an dieser Methode ist, dass Sie eine Download - Aufgabe mit einem bestimmten Pfad für Zieldateien, einem Abschlussblock und einer Eingabe für eine NSProgress Zeiger, auf dem Sie Informationen über den Fortschritt des Downloads beobachten können. Hier ist ein Beispiel.

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

Da wir die Klasse, die diesen Code enthält, als Beobachter zu einer der Eigenschaften der NSProgress-Instanz hinzugefügt haben, müssen Sie natürlich die -[NSObject observeValueForKeyPath:ofObject:change:context:] Methode. In diesem Fall habe ich ein Beispiel aufgeführt, wie Sie eine Fortschrittsbeschriftung aktualisieren können, um den Fortschritt des Downloads anzuzeigen. Es ist wirklich einfach. NSProgress hat eine Instanzmethode localizedDescription Die Fortschrittsinformationen werden in einem lokalisierten, von Menschen lesbaren Format angezeigt.

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

Vergessen Sie nicht, wenn Sie AFNetworking in Ihrem Projekt verwenden möchten, müssen Sie dessen folgen Installationsanleitung und sei dir sicher #import <AFNetworking/AFNetworking.h>.

Alamofire

Zum Schluss möchte ich noch ein letztes Beispiel geben Alamofire. Dies ist eine Bibliothek, die das Networking in Swift zu einem Kuchenspaziergang macht. Ich habe keine Charaktere, um sehr detailliert über den Inhalt dieses Samples zu sprechen, aber es ist ziemlich genau dasselbe wie die letzten Beispiele, nur auf eine wohl schönere Art und Weise.

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

88
2017-10-27 12:06



Sie können nichts im Paket der App speichern, aber Sie können es verwenden +[NSData dataWithContentsOfURL:] um das Bild im Dokumentenverzeichnis Ihrer App zu speichern, z. B .:

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

Nicht genau permanent, aber es bleibt dort mindestens so lange, bis der Benutzer die App löscht.


38
2018-06-04 17:12



Das ist das Hauptkonzept. Habe Spaß ;)

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
2018-06-04 17:14



Da wir jetzt auf IO5 sind, müssen Sie keine Bilder mehr auf die Festplatte schreiben.
Sie können jetzt "Erlaube externen Speicher" auf einem Coreedata-Binärattribut festlegen. Laut Apples Release Notes bedeutet dies Folgendes:

Kleine Datenwerte wie Bildminiaturen können effizient in einem gespeichert werden   Datenbank, aber große Fotos oder andere Medien werden am besten direkt behandelt   das Dateisystem. Sie können jetzt den Wert eines verwalteten angeben   Objektattribut kann als externer Datensatz gespeichert werden - siehe setAlowsExternalBinaryDataStorage:   Wenn aktiviert, entscheidet Core Data heuristisch auf einer pro-Wert-Basis, wenn   Es sollte die Daten direkt in der Datenbank speichern oder einen URI auf einem speichern   separate Datei, die es für Sie verwaltet. Sie können nicht basierend auf der Abfrage abfragen   Inhalt einer binären Dateneigenschaft, wenn Sie diese Option verwenden.


7
2018-06-02 08:58



Wie andere Leute sagten, gibt es viele Fälle, in denen Sie ein Bild im Hintergrund-Thread herunterladen sollten, ohne die Benutzeroberfläche zu blockieren

In diesen Fällen ist meine bevorzugte Lösung, eine bequeme Methode mit Blöcken zu verwenden, wie diese: (credit -> iOS: So laden Sie Bilder asynchron herunter (und machen Ihre UITableView schnell scrollen))

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

Und nenne es wie

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
2018-05-13 10:27



So lade ich ein Werbebanner herunter. Es ist am besten, es im Hintergrund zu tun, wenn Sie ein großes Bild oder eine Reihe von Bildern herunterladen.

- (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
2018-04-12 08:40



Hier ist Code, um ein Bild asynchron von der URL herunterzuladen und dann in objective-c zu speichern: ->

    + (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
2018-05-03 17:44



Wenn Sie die AFNetworking-Bibliothek zum Herunterladen von Bildern verwenden und diese Bilder in UITableview verwendet werden, können Sie den folgenden Code in cellForRowAtIndexPath verwenden

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

    } }                                                                                        Fehler: ^ (NSURLRequest * Anfrage, NSHTTPURLResponse * Antwort, NSError * Fehler) {                                                                                            NSLog (@ "Kein Bild");                                                                                        }]; [Betriebsstart];}

0
2018-04-23 12:33



Sie können ein Image herunterladen, ohne die Benutzeroberfläche zu blockieren, indem Sie NSURLSessionDataTask verwenden.

+(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
2017-07-05 10:30