Kann ich ein Bild von der Website herunterladen und dauerhaft in meiner App speichern? Ich habe wirklich keine Ahnung, aber es wäre eine nette Funktion für meine App.
iphone
cocoa-touch
ios
ipad
Samuli Lehtonen
quelle
quelle
Obwohl es stimmt, dass die anderen Antworten hier funktionieren, sind sie wirklich keine Lösungen, die jemals im Produktionscode verwendet werden sollten . (zumindest nicht ohne Änderung)
Probleme
Das Problem bei diesen Antworten besteht darin, dass sie den Hauptthread beim Herunterladen und Speichern des Bildes blockieren, wenn sie unverändert implementiert werden und nicht von einem Hintergrundthread aufgerufen werden. Das ist schlecht .
Wenn der Hauptthread blockiert ist, werden UI-Updates erst durchgeführt, wenn das Herunterladen / Speichern des Bildes abgeschlossen ist. Angenommen, Sie fügen Ihrer App eine UIActivityIndicatorView hinzu, um dem Benutzer anzuzeigen, dass der Download noch läuft (ich werde dies in dieser Antwort als Beispiel verwenden), mit dem folgenden groben Kontrollfluss:
+[NSData dataWithContentsOfURL:]
Dies scheint ein vernünftiger Kontrollfluss zu sein, verschleiert jedoch 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 Aktualisieren der Hauptlaufschleife durchgeführt. Hier liegt das erste große Problem.
Bevor dieses Update durchgeführt werden kann, wird der Download ausgelöst. Da es sich um einen synchronen Vorgang handelt, wird der Hauptthread blockiert, bis der Download abgeschlossen ist (das Speichern hat das gleiche Problem). Dadurch wird tatsächlich verhindert, dass die Aktivitätsanzeige ihre Animation startet. Danach rufen Sie die stopAnimating-Methode des Aktivitätsindikators auf und erwarten, dass alle gut sind, dies ist jedoch nicht der Fall.
An diesem Punkt werden Sie sich wahrscheinlich Folgendes fragen.
Nun, denken Sie so darüber nach. Sie weisen den Indikator an, zu starten, aber er erhält keine Chance, bevor der Download beginnt. Nach Abschluss des Downloads weisen Sie die Anzeige an, die Animation zu beenden. Da der Haupt-Thread während des gesamten Vorgangs blockiert war, entspricht das tatsächlich angezeigte Verhalten eher dem Hinweis, dass der Indikator gestartet und sofort gestoppt werden soll, obwohl dazwischen eine (möglicherweise) große Download-Aufgabe lag.
Im besten Fall führt dies nur zu einer schlechten Benutzererfahrung (immer noch sehr schlecht). Selbst wenn Sie der Meinung sind, dass dies keine große Sache ist, weil Sie nur ein kleines Bild herunterladen und der Download fast sofort erfolgt, ist dies nicht immer der Fall. Einige Ihrer Benutzer haben möglicherweise langsame Internetverbindungen, oder auf der Serverseite stimmt möglicherweise etwas nicht, sodass der Download nicht sofort / überhaupt nicht gestartet werden kann.
In beiden Fällen kann die App keine UI-Updates verarbeiten oder sogar Ereignisse berühren, während Ihre Download-Aufgabe mit den Daumen herumwirbelt und darauf wartet, dass der Download abgeschlossen wird oder der Server auf seine Anfrage reagiert.
Dies bedeutet, dass Sie durch synchrones Herunterladen vom Hauptthread möglicherweise nichts implementieren können, um dem Benutzer anzuzeigen, dass gerade ein Download ausgeführt wird. Und da Berührungsereignisse auch im Haupt-Thread verarbeitet werden, besteht die Möglichkeit, dass auch jede Art von Abbrechen-Schaltfläche hinzugefügt werden kann.
Im schlimmsten Fall erhalten Sie dann Absturzberichte mit den folgenden Angaben.
Diese sind leicht an dem Ausnahmecode zu erkennen
0x8badf00d
, der als "schlechtes Essen gegessen" gelesen werden kann. Diese Ausnahme wird vom Watchdog-Timer ausgelöst, dessen Aufgabe es ist, auf lange laufende Aufgaben zu achten, die den Hauptthread blockieren, und die fehlerhafte App zu beenden, wenn dies zu lange dauert. Dies ist wahrscheinlich immer noch ein Problem mit der Benutzererfahrung, aber wenn dies auftritt, hat die App die Grenze zwischen schlechter Benutzererfahrung und schrecklicher Benutzererfahrung überschritten.Weitere Informationen dazu, was dazu führen kann, finden Sie in den technischen Fragen und Antworten von Apple zu synchronen Netzwerken (der Kürze halber verkürzt).
...
An diesem Punkt werde ich aufhören, darüber nachzudenken, warum die bereitgestellten Antworten problematisch sein könnten, und einige alternative Lösungen anbieten. Beachten Sie, dass ich in diesen Beispielen die URL eines kleinen Bildes verwendet habe und Sie einen größeren Unterschied feststellen werden, wenn Sie ein Bild mit höherer Auflösung verwenden.
Lösungen
Ich werde zunächst eine sichere Version der anderen Antworten anzeigen und zusätzlich erläutern, wie mit UI-Updates umgegangen wird. Dies ist das erste von mehreren Beispielen, bei denen davon ausgegangen wird, dass die Klasse, in der sie implementiert sind, gültige Eigenschaften für eine UIImageView, eine UIActivityIndicatorView sowie die
documentsDirectoryURL
Methode für den Zugriff auf das Dokumentenverzeichnis aufweist. Im Produktionscode möchten Sie möglicherweise Ihre eigene Methode implementieren, um auf das Dokumentenverzeichnis als Kategorie in NSURL zuzugreifen, um die Wiederverwendbarkeit des Codes zu verbessern. In diesen Beispielen ist dies jedoch in Ordnung.- (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; }
In diesen Beispielen wird auch davon ausgegangen, dass der Thread, mit dem sie beginnen, der Hauptthread ist. Dies ist wahrscheinlich das Standardverhalten, es sei denn, Sie starten Ihre Download-Aufgabe von einem Ort wie dem Rückrufblock einer anderen asynchronen Aufgabe aus. Wenn Sie den Download an einem typischen Ort starten, z. B. bei einer Lebenszyklusmethode eines View Controllers (z. B. viewDidLoad, viewWillAppear: usw.), wird das erwartete Verhalten erzeugt.
In diesem ersten Beispiel wird die
+[NSData dataWithContentsOfURL:]
Methode verwendet, jedoch mit einigen wesentlichen Unterschieden. Zum einen werden Sie feststellen, dass in diesem Beispiel der erste Aufruf, den wir tätigen, darin besteht, die Aktivitätsanzeige anzuweisen, mit der Animation zu beginnen. Dann gibt es einen unmittelbaren 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.Zu diesem Zeitpunkt haben Sie Ihre Download-Aufgabe bereits erheblich verbessert. Da jetzt alles im Block dispatch_async () außerhalb des Hauptthreads stattfindet, wird Ihre Benutzeroberfläche nicht mehr blockiert und Ihre App kann auf Berührungsereignisse reagieren.
Was hier zu beachten ist, ist, dass der gesamte Code in diesem Block im Hintergrund-Thread ausgeführt wird, bis zu dem Punkt, an dem das Herunterladen / Speichern des Bildes erfolgreich war. An diesem Punkt möchten Sie möglicherweise den Aktivitätsindikator anweisen, die Animation zu stoppen oder wenden Sie das neu gespeicherte Bild auf eine UIImageView an. In beiden Fällen handelt es sich um Aktualisierungen der Benutzeroberfläche. Dies bedeutet, dass Sie den Hauptthread mithilfe von dispatch_get_main_queue () zurücksenden müssen, um sie auszuführen. Andernfalls kommt es zu undefiniertem Verhalten, das dazu führen kann, dass die Benutzeroberfläche nach einem unerwarteten Zeitraum aktualisiert wird oder sogar abstürzt. Stellen Sie immer sicher, dass Sie zum Haupt-Thread zurückkehren, bevor Sie UI-Updates 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]; }); } });
Denken Sie jetzt daran, dass die oben gezeigte Methode immer noch keine ideale Lösung ist, da sie nicht vorzeitig abgebrochen werden kann. Sie gibt keinen Hinweis auf den Fortschritt des Downloads und kann keine Authentifizierungsaufforderung bewältigen Es wird kein bestimmtes Zeitlimit usw. angegeben (viele, viele Gründe). Ich werde im Folgenden einige der besseren Optionen behandeln.
In diesen Beispielen werde ich nur Lösungen für Apps behandeln, die auf iOS 7 und höher abzielen, da (zum Zeitpunkt des Schreibens) iOS 8 die aktuelle Hauptversion ist und Apple nur die Versionen N und N-1 unterstützt . Wenn Sie ältere iOS-Versionen unterstützen müssen, empfehlen wir Ihnen , sich die NSURLConnection- Klasse sowie die 1.0-Version von AFNetworking anzusehen. Wenn Sie sich den Revisionsverlauf dieser Antwort ansehen , finden Sie grundlegende Beispiele für die Verwendung von NSURLConnection und ASIHTTPRequest. Beachten Sie jedoch, dass ASIHTTPRequest nicht mehr verwaltet wird und nicht für neue Projekte verwendet werden sollte.
NSURLSession
Beginnen wir mit NSURLSession , das in iOS 7 eingeführt wurde und die Benutzerfreundlichkeit von Netzwerken in iOS erheblich verbessert. Mit NSURLSession können Sie problemlos asynchrone HTTP-Anforderungen mit einem Rückrufblock ausführen und Authentifizierungsprobleme mit dem Delegaten lösen. Das Besondere an dieser Klasse ist jedoch, dass Download-Aufgaben auch dann weiter ausgeführt werden können, wenn die Anwendung in 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];
Daraus erkennen Sie, dass die
downloadTaskWithURL: completionHandler:
Methode eine Instanz von NSURLSessionDownloadTask zurückgibt, für die eine Instanzmethode-[NSURLSessionTask resume]
aufgerufen wird. Dies ist die Methode, mit der die Download-Aufgabe tatsächlich gestartet wird. Dies bedeutet, dass Sie Ihre Download-Aufgabe starten und bei Bedarf beim Starten warten können (falls erforderlich). Dies bedeutet auch, dass Sie, solange Sie einen Verweis auf die Aufgabe speichern, auch derencancel
undsuspend
Methoden verwenden können, um die Aufgabe bei Bedarf abzubrechen oder anzuhalten.Was an NSURLSessionTasks wirklich cool ist, ist, dass Sie mit ein wenig KVO die Werte der Eigenschaften countOfBytesExpectedToReceive und countOfBytesReceived überwachen, diese Werte einem NSByteCountFormatter zuführen und Ihrem Benutzer auf einfache Weise eine Anzeige für den Download-Fortschritt mit lesbaren Einheiten erstellen können (z. B. 42) KB von 100 KB).
Bevor ich mich jedoch von NSURLSession entferne, möchte ich darauf hinweisen, dass die Hässlichkeit, an mehreren verschiedenen Stellen im Rückrufblock des Downloads_async zurück zu den Hauptthreads senden zu müssen, vermieden werden kann. Wenn Sie sich für diese Route entschieden haben, können Sie die Sitzung mit ihrem Initialisierer initialisieren, mit dem Sie den Delegaten sowie die Delegatenwarteschlange angeben können. Dies erfordert, dass Sie das Delegatenmuster anstelle der Rückrufblöcke verwenden. Dies kann jedoch von Vorteil sein, da nur so Hintergrund-Downloads unterstützt werden können.
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
AFNetworking 2.0
Wenn Sie noch nie von AFNetworking gehört haben , ist es meiner Meinung nach das Ende aller Netzwerkbibliotheken. Es wurde für Objective-C erstellt, funktioniert aber auch in Swift. In den Worten seines Autors:
AFNetworking 2.0 unterstützt iOS 6 und höher. In diesem Beispiel verwende ich jedoch die AFHTTPSessionManager-Klasse, für die iOS 7 und höher erforderlich ist, da alle neuen APIs rund um die NSURLSession-Klasse verwendet werden. Dies wird deutlich, wenn Sie das folgende Beispiel lesen, das viel Code mit dem obigen NSURLSession-Beispiel teilt.
Es gibt jedoch einige Unterschiede, auf die ich hinweisen möchte. Anstatt Ihre eigene NSURLSession zu erstellen, erstellen Sie zunächst eine Instanz von AFURLSessionManager, die eine NSURLSession intern verwaltet. Auf diese Weise können Sie einige der praktischen Methoden wie nutzen
-[AFURLSessionManager downloadTaskWithRequest:progress:destination:completionHandler:]
. Das Interessante an dieser Methode ist, dass Sie damit eine Download-Aufgabe mit einem bestimmten Zieldateipfad , einem Abschlussblock und einer Eingabe für einen NSProgress- Zeiger ziemlich präzise erstellen können , auf der 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 die
-[NSObject observeValueForKeyPath:ofObject:change:context:]
Methode natürlich implementieren . In diesem Fall habe ich ein Beispiel beigefügt, wie Sie eine Fortschrittsbezeichnung aktualisieren können, um den Fortschritt des Downloads anzuzeigen. Es ist wirklich einfach. NSProgress verfügt über eine Instanzmethode, mitlocalizedDescription
der Fortschrittsinformationen in einem lokalisierten, für Menschen lesbaren Format angezeigt werden.- (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 die Installationsanweisungen befolgen und dies unbedingt tun
#import <AFNetworking/AFNetworking.h>
.Alamofire
Und zum Schluss möchte ich noch ein letztes Beispiel mit Alamofire geben . Dies ist die Bibliothek, die das Networking in Swift zu einem Kinderspiel macht. Ich habe keine Charaktere mehr, um detailliert auf den Inhalt dieses Beispiels einzugehen, aber es macht so ziemlich das Gleiche 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) } } } }
quelle
NSURL *filePathURL = [NSURL fileURLWithPath:filePathString];
.Sie können nichts im App-Bundle
+[NSData dataWithContentsOfURL:]
speichern , aber Sie können das Bild im Dokumentenverzeichnis Ihrer App 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 gerade permanent , aber es bleibt mindestens so lange dort, bis der Benutzer die App löscht.
quelle
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];
quelle
Da wir jetzt auf IO5 sind, müssen Sie keine Bilder mehr auf die Festplatte schreiben.
Sie können jetzt "externen Speicher zulassen" für ein Coredata-Binärattribut festlegen. Laut den Versionshinweisen für Äpfel bedeutet dies Folgendes:
quelle
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 besteht meine Lieblingslösung darin, eine bequeme Methode mit Blöcken wie diesen zu verwenden: (Kredit -> iOS: So laden Sie Bilder asynchron herunter (und machen Sie Ihren UITableView-Bildlauf schnell) )
- (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... }];
quelle
So lade ich ein Werbebanner herunter. Am besten im Hintergrund, 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; }
quelle
Hier ist Code, um ein Bild asynchron von der URL herunterzuladen und dann in Objective-C an der gewünschten Stelle 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); } }]; }
quelle
Wenn Sie die AFNetworking-Bibliothek zum Herunterladen von Bildern verwenden und diese Bilder in UITableview verwenden, 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; } } } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) { NSLog(@"No Image"); }]; [operation start];}
quelle
Mit NSURLSessionDataTask können Sie Bilder herunterladen, ohne die Benutzeroberfläche zu blockieren.
+(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]; }
quelle
Hier ist eine Swift 5- Lösung zum Herunterladen und Speichern eines Bildes oder allgemein einer Datei im Dokumentenverzeichnis unter Verwendung von
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) } } }
quelle