Подтвердить что ты не робот

IOS загружает и сохраняет изображение внутри приложения

Возможно ли загрузить изображение с веб-сайта и сохранить его навсегда в моем приложении? Я действительно не знаю, но это сделает приятную функцию для моего приложения.

4b9b3361

Ответ 2

Хотя верно, что другие ответы здесь будут работать, они действительно не являются решениями, которые когда-либо должны использоваться в производственном коде. (по крайней мере, не без изменений)

Проблемы

Проблема с этими ответами заключается в том, что если они реализованы как есть и не вызываются из фонового потока, они будут блокировать основной поток при загрузке и сохранении изображения. Это плохой.

Если основной поток заблокирован, обновления пользовательского интерфейса не будут выполняться до тех пор, пока загрузка/сохранение изображения не будет завершена. Например, вы добавляете в приложение UIActivityIndicatorView, чтобы показать пользователю, что загрузка все еще выполняется (я буду использовать это в качестве примера на протяжении всего этого ответа) со следующим грубым потоком управления:

  • Загружен объект, ответственный за загрузку загрузки.
  • Сообщите индикатору активности, чтобы начать анимацию.
  • Запустите процесс синхронной загрузки, используя +[NSData dataWithContentsOfURL:]
  • Сохраните только что загруженные данные (изображения).
  • Сообщите индикатору активности прекратить анимацию.

Теперь это может показаться разумным потоком управления, но оно маскирует критическую проблему.

Когда вы вызываете метод startAnimating индикатора активности в основном потоке (UI), обновления пользовательского интерфейса для этого события фактически не будут выполняться до следующего раза main run loop, и это первая проблема.

Прежде чем это обновление имеет шанс, загрузка запускается, и поскольку это синхронная операция, она блокирует основной поток, пока не завершит загрузку (сохранение имеет ту же проблему). Это фактически предотвратит запуск индикатора активности. После этого вы вызываете индикатор активности stopAnimating и ожидаете, что все будет хорошо, но это не так.

На этом этапе вам, вероятно, будет интересно узнать следующее.

Почему мой индикатор активности не появляется?

Хорошо, подумайте об этом так. Вы говорите, что индикатор запускается, но у него нет шанса до начала загрузки. После завершения загрузки вы укажете индикатор, чтобы остановить анимацию. Поскольку основной поток был заблокирован через всю операцию, поведение, которое вы на самом деле видите, больше похоже на строки, указывающие на запуск индикатора, а затем сразу же он должен остановить его, даже если между ними была (возможно) большая задача загрузки.

Теперь, в наилучшем сценарии, все это приводит к плохой работе с пользователем (по-прежнему очень плохой). Даже если вы считаете, что это не очень важно, потому что вы загружаете небольшое изображение, и загрузка происходит почти мгновенно, это не всегда так. Некоторые из ваших пользователей могут иметь медленное подключение к Интернету, или что-то может быть неправильной стороной на сервере, чтобы загрузка сразу начиналась/вообще.

В обоих случаях приложение не сможет обрабатывать обновления пользовательского интерфейса или даже прикасаться к событиям, когда задача загрузки находится вокруг сгибания больших пальцев, ожидая завершения загрузки или для ответа сервера на его запрос.

Это означает, что синхронная загрузка из основного потока не позволяет вам реализовать что-либо, чтобы указать пользователю, что загрузка в данный момент выполняется. И так как события касания обрабатываются также в основном потоке, это также исключает возможность добавления любой кнопки отмены.

Затем в наихудшем сценарии вы начнете получать отчеты о сбоях, в которых указывается следующее.

Тип исключения: 00000020 Коды исключений: 0x8badf00d

Они легко идентифицируются кодом исключения 0x8badf00d, который можно читать как "ел плохую пищу". Это исключение выбрасывается таймером сторожевой собаки, задачей которого является наблюдение за длительными задачами, которые блокируют основной поток, и убивать оскорбительное приложение, если это происходит слишком долго. По-видимому, это все еще проблема с плохой работой пользователей, но если это произойдет, приложение пересекло линию между плохим пользовательским интерфейсом и ужасным пользовательским интерфейсом.

Вот еще информация о том, что может произойти из Apple Technical Q & A об синхронной сети (сокращенно для краткости).

Наиболее распространенной причиной сбоя тайм-аута сторожевого таймера в сетевом приложении является синхронная сеть в основном потоке. Здесь есть четыре фактора:

  • синхронная сеть. Здесь вы делаете сетевой запрос и блокируете ожидающий ответ.
  • Основной поток. Синхронная сеть в целом не идеальна, но вызывает определенные проблемы, если вы делаете это в основном потоке. Помните, что основной поток отвечает за запуск пользовательского интерфейса. Если вы блокируете основной поток за какое-то значительное количество времени, пользовательский интерфейс становится неприемлемо невосприимчивым.
  • длинные таймауты. Если сеть просто уходит (например, пользователь находится в поезде, который входит в туннель), любой ожидающий сетевой запрос не сбой, пока не истечет некоторое время ожидания.

...

  1. watchdog - для того, чтобы поддерживать пользовательский интерфейс, 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() для их выполнения. В противном случае это приведет к поведению undefined, которое может привести к обновлению пользовательского интерфейса после непредвиденного периода времени или может привести к сбою. Перед выполнением обновлений пользовательского интерфейса обязательно убедитесь, что вы вернетесь в основной поток.

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

Теперь имейте в виду, что метод , показанный выше, по-прежнему не является идеальным решением, поскольку он не может быть отменен преждевременно, он не дает никаких указаний на ход загрузки, t обрабатывать любой вызов аутентификации, ему не может быть задан определенный интервал времени ожидания и т.д. (много и много причин). Я расскажу о некоторых из лучших вариантов ниже.

В этих примерах я буду освещать решения для приложений, ориентированных на iOS 7, и, учитывая (во время написания), iOS 8 является текущей основной версией, а Apple предлагает только поддержку версий N и N-1. Если вам нужно поддерживать более старые версии iOS, я рекомендую изучить класс NSURLConnection, а также 1.0 версия AFNetworking. Если вы посмотрите историю изменений этого ответа, вы можете найти основные примеры, используя NSURLConnection и ASIHTTPRequest, хотя следует отметить, что ASIHTTPRequest больше не поддерживается, и для новых проектов следует использовать не.


NSURLSession

Давайте начнем с 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 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 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, я хотел бы указать, что избежать упущения при отправке_async обратно в основные потоки в нескольких разных точках блока обратного вызова загрузки можно. Если вы выбрали этот маршрут, вы можете инициализировать сеанс с его инициализатором, который позволяет указать делегат, а также очередь делегатов. Это потребует использования шаблона делегата вместо блоков обратного вызова, но это может быть полезно, потому что это единственный способ поддерживать загрузку фонограмм.

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

AFNetworking 2.0

Если вы никогда не слышали о AFNetworking, это ИМХО - конец всех сетевых библиотек. Он был создан для 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 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 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 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

И, наконец, я хотел бы привести последний пример, используя 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)
                }
            }
        }
    }

Ответ 3

Вы не можете сохранить что-либо внутри пакета приложений, но вы можете использовать +[NSData dataWithContentsOfURL:] для хранения изображения в каталоге документов приложения, например:

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

Не точно постоянный, но он остается там, по крайней мере, до тех пор, пока пользователь не удалит приложение.

Ответ 4

Это основная концепция. Удачи;)

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

Ответ 5

Так как мы сейчас на IO5, вам больше не нужно писать изображения на диск.
Теперь вы можете установить "разрешить внешнее хранилище" на бинарный атрибут coredata. Согласно заметкам о выпуске яблок, это означает следующее:

Небольшие значения данных, такие как миниатюры изображений, могут быть эффективно сохранены в базы данных, но большие фотографии или другие материалы лучше всего обрабатывать непосредственно файловой системы. Теперь вы можете указать, что значение управляемого атрибут объекта может быть сохранен как внешняя запись - см. setAllowsExternalBinaryDataStorage:Когда включено, Core Data эвристически решает на основе стоимости, если он должен сохранять данные непосредственно в базе данных или хранить URI для отдельный файл, который он управляет для вас. Вы не можете запрашивать на основе содержимое двоичного свойства данных, если вы используете эту опцию.

Ответ 6

Как говорили другие люди, есть много случаев, когда вы должны загружать картинку в фоновый поток без блокировки пользовательского интерфейса.

В этом случае моим любимым решением является использование удобного метода с блоками, вроде этого: (credit → 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...
}];

Ответ 7

Вот как я загружаю рекламный баннер. Лучше всего сделать это в фоновом режиме, если вы загружаете большое изображение или кучу изображений.

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

Ответ 8

Вот код для загрузки изображения асинхронно с URL-адреса, а затем сохраните, где вы хотите, в objective-c: →

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

Ответ 9

Если вы используете библиотеку 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];}

Ответ 10

Вы можете загружать изображение без блокировки пользовательского интерфейса с помощью 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];
}

Ответ 11

Вот решение 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)
        }
    }
}