Чи можна мені завантажити зображення з веб-сайту та назавжди зберегти його у своєму додатку? Я справді не уявляю, але це було б приємною функцією для мого додатка.
Чи можна мені завантажити зображення з веб-сайту та назавжди зберегти його у своєму додатку? Я справді не уявляю, але це було б приємною функцією для мого додатка.
Відповіді:
Асинхронні завантажені зображення з кешуванням
Асинхронні завантажені зображення з кешуванням
Ось ще одне репозиторій, за допомогою якого можна завантажувати зображення у фоновому режимі
Хоча це правда, що інші відповіді тут будуть працювати, вони насправді не є рішеннями, які коли-небудь слід використовувати у виробничому коді . (принаймні не без змін)
Проблема цих відповідей полягає в тому, що якщо вони реалізовані як є і не викликаються з фонового потоку, вони будуть блокувати основний потік під час завантаження та збереження зображення. Це погано .
Якщо основний потік заблокований, оновлення інтерфейсу користувача не відбуватиметься, поки завантаження / збереження зображення не буде завершено. Як приклад того, що це означає, скажімо, ви додаєте UIActivityIndicatorView до свого додатка, щоб показати користувачеві, що завантаження все ще триває (я буду використовувати це як приклад у цій відповіді) із наступним грубим контролем:
+[NSData dataWithContentsOfURL:]
Зараз це може здатися розумним потоком управління, але він маскує критичну проблему.
Коли ви викликаєте метод startAnimating індикатора активності в основному потоці (UI), оновлення інтерфейсу для цієї події фактично не відбуватимуться до наступного разу, коли основний цикл запуску оновлення , і саме тут полягає перша велика проблема.
Перш ніж це оновлення має шанс, завантаження запускається, і оскільки це синхронна операція, вона блокує основний потік, поки не завершить завантаження (збереження має ту ж проблему). Це насправді не дозволить індикатору активності запускати анімацію. Після цього ви викликаєте метод stopAnimating індикатора активності і очікуєте, що все буде добре, але це не так.
На цьому етапі ви, мабуть, здивуєтесь наступному.
Чому мій показник активності ніколи не відображається?
Ну, подумайте про це так. Ви говорите, що індикатор запускається, але він не має шансів до початку завантаження. Після завершення завантаження ви кажете індикатору припинити анімацію. Оскільки основний потік був заблокований в ході всієї операції, поведінка, яку ви насправді бачите, полягає більше в тому, що індикатор запускає, а потім негайно говорить, що він повинен зупинитися, хоча між ними було (можливо) велике завдання завантаження.
Зараз, у найкращому випадку , все це спричиняє поганий досвід користувача (все ще дуже поганий). Навіть якщо ви думаєте, що це не є великою проблемою, оскільки ви завантажуєте лише невелике зображення, а завантаження відбувається майже миттєво, це не завжди буде так. Деякі з ваших користувачів можуть мати повільні підключення до Інтернету, або щось може бути неправильним на стороні сервера, що заважає завантаженню розпочинатися негайно / взагалі.
В обох цих випадках програма не зможе обробляти оновлення інтерфейсу користувача або навіть торкатися подій, поки ваше завдання завантаження сидить навколо, чекаючи завершення завантаження або відповіді сервера на його запит.
Це означає, що синхронне завантаження з основного потоку заважає можливо реалізувати що-небудь, що вказує користувачеві, що завантаження триває. І оскільки події дотику також обробляються в основному потоці, це виключає можливість додавання будь-якої кнопки скасування.
Тоді в найгіршому випадку ви почнете отримувати звіти про аварійні ситуації із зазначенням наступного.
Тип винятку: 00000020 Коди винятків: 0x8badf00d
Їх легко визначити за кодом винятку 0x8badf00d
, який можна прочитати як «з’їв погану їжу». Цей виняток створює таймер сторожової собаки, завдання якого полягає у відстеженні тривалих запущених завдань, що блокують основний потік, і вбивстві програми-порушника, якщо це триває занадто довго. Можливо, це все ще проблема з поганою взаємодією з користувачем, але якщо це починає відбуватися, програма перетнула межу між поганою взаємодією з користувачем та жахливою взаємодією з користувачем.
Ось додаткова інформація про те, що може спричинити це, з Технічних запитань та відповідей Apple щодо синхронних мереж (скорочено для стислості).
Найбільш поширеною причиною збою тайм-ауту сторожового таймера в мережевій програмі є синхронна мережа в основному потоці. Тут є чотири фактори, що сприяють цьому:
- синхронна мережа - тут ви робите мережевий запит і блокуєте очікування відповіді.
- основний потік - синхронна мережа взагалі менш ніж ідеальна, але це спричиняє конкретні проблеми, якщо ви робите це в основному потоці. Пам'ятайте, що основний потік відповідає за роботу інтерфейсу користувача. Якщо ви заблокуєте основний потік протягом будь-якої значної кількості часу, користувальницький інтерфейс стане неприйнятно невідповідним.
- довгі тайм-аути - якщо мережа просто зникає (наприклад, користувач перебуває в поїзді, який входить у тунель), будь-який очікуваний мережевий запит не буде провалюватися, поки не закінчиться деякий тайм-аут ....
...
- сторожовий - для того, щоб користувальницький інтерфейс відповідав, iOS включає сторожовий механізм. Якщо ваша програма не реагує на певні події користувальницького інтерфейсу (запуск, призупинення, відновлення, завершення) вчасно, сторожова програма вб’є вашу програму та згенерує звіт про аварійне завершення роботи сторожового таймера. Кількість часу, яку надає сторожовий собака, формально не задокументована, але це завжди менше часу очікування мережі.
Один хитрий аспект цієї проблеми полягає в тому, що вона сильно залежить від мережевого середовища. Якщо ви завжди тестуєте свою програму у своєму офісі, де підключення до мережі є добрим, ви ніколи не побачите такого типу збоїв. Однак, як тільки ви почнете розгортати свою програму для кінцевих користувачів - які будуть запускати її у всіляких мережевих середовищах - подібні збої стануть звичними.
Зараз на цьому етапі я перестану роздумувати про те, чому надані відповіді можуть бути проблематичними, і почну пропонувати деякі альтернативні рішення. Майте на увазі, що в цих прикладах я використовував URL-адресу невеликого зображення, і ви помітите більшу різницю при використанні зображення з більшою роздільною здатністю.
Я почну з того, що покажу безпечну версію інших відповідей із додаванням того, як обробляти оновлення інтерфейсу. Це буде перший із кількох прикладів, усі з яких припускатимуть, що клас, в якому вони реалізовані, має допустимі властивості для UIImageView, UIActivityIndicatorView, а також documentsDirectoryURL
методу доступу до каталогу документів. У виробничому коді вам може знадобитися застосувати власний метод доступу до каталогу документів як категорії на NSURL для кращого повторного використання коду, але для цих прикладів це буде добре.
- (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;
}
Ці приклади також передбачатимуть, що нитка, з якої вони починаються, є основною. Це, швидше за все, буде поведінкою за замовчуванням, якщо ви не почнете своє завдання завантаження звідкись, як блок зворотного виклику якогось іншого асинхронного завдання. Якщо ви починаєте завантаження в типовому місці, як метод життєвого циклу контролера перегляду (тобто viewDidLoad, viewWillAppear: тощо), це призведе до очікуваної поведінки.
У цьому першому прикладі буде використаний +[NSData dataWithContentsOfURL:]
метод, але з деякими ключовими відмінностями. По-перше, ви помітите, що в цьому прикладі найпершим викликом, який ми робимо, є сказати індикатору активності почати анімацію, тоді між цим і синхронними прикладами існує негайна різниця. Відразу ми використовуємо dispatch_async (), передаючи глобальну паралельну чергу для переміщення виконання у фоновий потік.
На даний момент ви вже значно покращили своє завдання завантаження. Оскільки все, що знаходиться в блоці dispatch_async (), тепер відбуватиметься поза основним потоком, ваш інтерфейс більше не буде блокуватися, а ваш додаток буде вільно реагувати на події дотику.
Тут важливо зауважити, що весь код у цьому блоці буде виконуватися у фоновому потоці, аж до того моменту, коли завантаження / збереження зображення було успішним, і в цей момент ви можете сказати індикатору активності зупинитися , або застосувати нещодавно збережене зображення до UIImageView. У будь-якому випадку, це оновлення інтерфейсу користувача, тобто ви повинні відправити основний потік за допомогою dispatch_get_main_queue () для їх виконання. Якщо цього не зробити, це спричиняє невизначену поведінку, що може спричинити оновлення інтерфейсу через несподіваний проміжок часу або навіть призвести до аварійного завершення роботи. Завжди переконайтеся, що повертаєтесь до основного потоку перед тим, як виконувати оновлення інтерфейсу.
// 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];
});
}
});
Тепер майте на увазі, що метод, показаний вище, все ще не є ідеальним рішенням, оскільки його не можна скасувати передчасно, він не дає жодних ознак прогресу завантаження, він не може впоратися з будь-якою проблемою автентифікації, він може не повинен бути вказаний певний інтервал очікування тощо (багато-багато причин). Нижче я розгляну кілька кращих варіантів.
У цих прикладах я розгляну лише рішення для додатків, орієнтованих на iOS 7 і новіших версій, враховуючи (на момент написання статті) iOS 8 є поточним основним випуском, і Apple пропонує лише підтримку версій N та N-1 . Якщо вам потрібна підтримка старих версій iOS, я рекомендую вивчити клас NSURLConnection , а також версію 1.0 AFNetworking. Якщо ви подивитесь на історію редагувань цієї відповіді, ви зможете знайти основні приклади за допомогою NSURLConnection та ASIHTTPRequest , хоча слід зазначити, що ASIHTTPRequest більше не підтримується і не повинен використовуватися для нових проектів.
Почнемо з NSURLSession , яка була введена в iOS 7, і значно покращує легкість створення мереж у iOS. За допомогою NSURLSession ви можете легко виконувати асинхронні HTTP-запити з блоком зворотного виклику та обробляти завдання автентифікації за допомогою його делегата. Але що робить цей клас справді особливим, це те, що він також дозволяє продовжувати виконувати завдання завантаження, навіть якщо програма надсилається у фоновий режим, припиняється або навіть аварійно завершує роботу. Ось базовий приклад його використання.
// 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];
З цього ви помітите, що downloadTaskWithURL: completionHandler:
метод повертає екземпляр NSURLSessionDownloadTask, для якого викликається метод екземпляра -[NSURLSessionTask resume]
. Це метод, який фактично вказує на початок завантаження. Це означає, що ви можете розкрутити своє завдання завантаження і, якщо потрібно, затриматися при його запуску (за потреби). Це також означає, що поки ви зберігаєте посилання на завдання, ви також можете використовувати його cancel
та suspend
методи, щоб скасувати або призупинити завдання, якщо це необхідно.
Що справді круто в NSURLSessionTasks, це те, що за допомогою трохи KVO ви можете відстежувати значення властивостей countOfBytesExpectedToReceive та countOfBytesReceived, подавати ці значення до NSByteCountFormatter і легко створювати індикатор прогресу завантаження для вашого користувача за допомогою зручних для читання одиниць (наприклад, 42 КБ 100 КБ).
Перш ніж відійти від NSURLSession, я хотів би зазначити, що можна уникнути потворності необхідності dispatch_async повернутися до основних потоків у декількох різних точках блоку зворотного виклику завантаження. Якщо ви вибрали цей шлях, ви можете ініціалізувати сеанс за допомогою його ініціалізатора, що дозволяє вказати делегата, а також чергу делегатів. Для цього потрібно буде використовувати шаблон делегатів замість блоків зворотного виклику, але це може бути корисно, оскільки це єдиний спосіб підтримувати фонове завантаження.
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
Якщо ви ніколи не чули про AFNetworking , це IMHO - кінцеві мережеві бібліотеки. Він був створений для Objective-C, але він працює і в Swift. За словами його автора:
AFNetworking - це чудова мережева бібліотека для iOS та Mac OS X. Вона побудована поверх Основної системи завантаження URL-адрес, розширюючи потужні мережеві абстракції високого рівня, вбудовані в Cocoa. Він має модульну архітектуру з добре розробленими, багатофункціональними API, які дуже раді використовувати.
AFNetworking 2.0 підтримує iOS 6 і новіших версій, але в цьому прикладі я буду використовувати його клас AFHTTPSessionManager, який вимагає iOS 7 і новіших версій завдяки використанню всіх нових API навколо класу NSURLSession. Це стане очевидним, коли ви прочитаєте приклад нижче, який містить багато коду з прикладом NSURLSession вище.
Хоча я хотів би зазначити кілька відмінностей. Для початку замість створення власної NSURLSession ви створите екземпляр AFURLSessionManager, який буде внутрішньо керувати NSURLSession. Це дозволяє вам скористатися деякими його зручними методами, такими як -[AFURLSessionManager downloadTaskWithRequest:progress:destination:completionHandler:]
. Цікавим у цьому методі є те, що він дозволяє досить коротко створити завдання завантаження із заданим шляхом до цільового файлу, блоком завершення та введенням для вказівника NSProgress , на якому ви можете спостерігати інформацію про хід завантаження. Ось приклад.
// 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];
Звичайно, оскільки ми додали клас, що містить цей код, як спостерігача до одного із властивостей екземпляра NSProgress, вам доведеться реалізувати -[NSObject observeValueForKeyPath:ofObject:change:context:]
метод. У цьому випадку я включив приклад того, як ви можете оновити мітку виконання, щоб відобразити хід завантаження. Це дуже просто. NSProgress має метод екземпляра, localizedDescription
який відображатиме інформацію про хід у локалізованому зручному для читання форматі.
- (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];
}
}
Не забувайте, якщо ви хочете використовувати AFNetworking у своєму проекті, вам потрібно буде слідувати інструкціям з його встановлення та обов’язково #import <AFNetworking/AFNetworking.h>
.
І нарешті, я хотів би навести останній приклад використання Alamofire . Це бібліотека, яка робить мережу в Swift торт-прогулянкою. Мені не вистачає персонажів детально розбиратись у змісті цього зразка, але він робить майже те саме, що і останні приклади, просто, мабуть, найкрасивішим чином.
// 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)
}
}
}
}
NSURL *filePathURL = [NSURL fileURLWithPath:filePathString];
.
Ви не можете нічого зберегти всередині набору додатків, але ви можете використовувати +[NSData dataWithContentsOfURL:]
для зберігання зображення в каталозі документів програми, наприклад:
NSData *imageData = [NSData dataWithContentsOfURL:myImageURL];
NSString *imagePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"/myImage.png"];
[imageData writeToFile:imagePath atomically:YES];
Не зовсім постійно , але він залишається там принаймні до тих пір, поки користувач не видалить програму.
Це головна концепція. Веселіться;)
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];
Оскільки ми зараз використовуємо IO5, вам більше не потрібно записувати зображення на диск.
Тепер ви можете встановити "дозволити зовнішнє зберігання" для двійкового атрибута coredata. Відповідно до приміток до випуску яблук це означає наступне:
Невеликі значення даних, як мініатюри зображень, можуть ефективно зберігатися в базі даних, але великі фотографії та інші носії найкраще обробляти безпосередньо файловою системою. Тепер ви можете вказати, що значення атрибута керованого об’єкта може зберігатися як зовнішній запис - див. SetAllowsExternalBinaryDataStorage: якщо цей параметр увімкнено, Core Data евристично вирішує на основі значення, чи слід зберігати дані безпосередньо в базі даних або зберігати URI в окремий файл, яким він керує. Якщо ви використовуєте цей параметр, не можна робити запити на основі вмісту властивості двійкових даних.
Як казали інші люди, є багато випадків, коли вам слід завантажувати зображення у фоновому режимі, не блокуючи користувальницький інтерфейс
У цьому випадку моїм улюбленим рішенням є використання зручного методу з блоками, наприклад, такого: (кредит -> iOS: Як асинхронно завантажувати зображення (та швидко прокручувати UITableView) )
- (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);
}
}];
}
І називайте це як
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...
}];
Ось як я завантажую рекламний банер. Найкраще це робити у фоновому режимі, якщо ви завантажуєте велике зображення або купу зображень.
- (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;
}
Ось код для завантаження зображення асинхронно з URL-адреси, а потім збереження, де ви хочете, у Cilj-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);
}
}];
}
Якщо ви використовуєте бібліотеку AFNetworking для завантаження зображень, а зображення використовуються в UITableview, тоді ви можете використовувати код нижче в 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];}
Ви можете завантажити зображення без блокування інтерфейсу за допомогою 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];
}
Ось рішення Swift 5 для завантаження та збереження зображення або загалом файлу до каталогу документів за допомогою 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)
}
}
}