iOS скачать и сохранить изображение внутри приложения - PullRequest
77 голосов
/ 04 июня 2011

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

Ответы [ 10 ]

92 голосов
/ 27 октября 2012

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

Проблемы

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

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

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

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

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

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

В этот момент вы, вероятно, обнаружите, что задаетесь вопросом:

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

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

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

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

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

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

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

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

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

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

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

...

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

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

Теперь на этом этапе я перестану рассказывать опочему предоставленные ответы могут быть проблематичными и начнут предлагать некоторые альтернативные решения.Помните, что в этих примерах я использовал URL-адрес небольшого изображения, и вы заметите большую разницу при использовании изображения с более высоким разрешением.


Решения

I 'Начнем с показа безопасной версии других ответов с добавлением того, как обрабатывать обновления пользовательского интерфейса.Это будет первый из нескольких примеров, каждый из которых будет предполагать, что класс, в котором они реализованы, имеет допустимые свойства для 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: и т. Д.), Это приведет к ожидаемому поведению.

Это fВ первом примере будет использоваться метод +[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

Начнем с 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-адресов, расширяя мощные высокоуровневые сетевые абстракции, встроенные вКакао.Он имеет модульную архитектуру с хорошо разработанными, многофункциональными 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)
                }
            }
        }
    }
39 голосов
/ 04 июня 2011

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

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

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

13 голосов
/ 04 июня 2011

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

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];
7 голосов
/ 02 июня 2012

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

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

3 голосов
/ 13 мая 2015

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

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

Вот код для асинхронной загрузки изображения с 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 голос
/ 12 апреля 2015

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

- (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;
}
0 голосов
/ 05 июля 2017

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

Если вы используете библиотеку 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 <em>btn =(UIButton</em>)ctrl;
                [btn setBackgroundImage:image forState:UIControlStateNormal];
            }
            else
            {
                UIImageView <em>imgView = (UIImageView</em>)ctrl;
                imgView.image = image;
            }</p>

    }

}
                                                                                       failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
                                                                                           NSLog(@"No Image");
                                                                                       }];

[operation start];}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...