it-swarm.com.ru

iOS скачать и сохранить изображение внутри приложения

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

77
Samuli Lehtonen
51
Jhaliya

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

Проблемы

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

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

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

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

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

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

В этот момент вы, вероятно, обнаружите, что вам интересно следующее.

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

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

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

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

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

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

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

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

Вот еще некоторая информация о том, что может вызвать это из Технический вопрос и ответ Apple о синхронной сети (сокращенно для краткости).

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

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

...

  1. сторожевой таймер - для обеспечения отзывчивости пользовательского интерфейса 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 , а также версия AFNetworking 1.0. Если вы посмотрите историю изменений этого ответа Вы можете найти базовые примеры, используя 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'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 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'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

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

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

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

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

39
user142019

Это основная концепция. Повеселись ;)

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

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

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

7
Alexander

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

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

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

- (void)viewDidLoad {
    [super viewDidLoad];

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

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

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

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

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

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

                        });

      }];

     }

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

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