web-dev-qa-db-ja.com

iOSはアプリ内で画像をダウンロードして保存します

ウェブサイトから画像をダウンロードし、アプリ内に永続的に保存することは可能ですか?私には本当にわからないが、それは私のアプリにとって素晴らしい機能になるだろう。

77
Samuli Lehtonen
51
Jhaliya

ここでの他の答えが機能することは事実ですが、これらは実際に本番コードで使用されるべきソリューションではありません。 (少なくとも修正なしではない)

問題点

これらの答えの問題は、それらがそのまま実装され、バックグラウンドスレッドから呼び出されない場合、イメージのダウンロードおよび保存中にメインスレッドをブロックすることです。これはbadです。

メインスレッドがブロックされている場合、画像のダウンロード/保存が完了するまでUIの更新は行われません。これが意味する例として、UIActivityIndi​​catorViewをアプリに追加して、次の大まかな制御フローを使用して、ダウンロードがまだ進行中であることをユーザーに示します(この回答全体でこれを例として使用します)。

  1. ダウンロードの開始を担当するオブジェクトがロードされます。
  2. アクティビティインジケータにアニメーションを開始するよう指示します。
  3. +[NSData dataWithContentsOfURL:]を使用して同期ダウンロードプロセスを開始します
  4. ダウンロードしたばかりのデータ(画像)を保存します。
  5. アニメーションを停止するようアクティビティインジケータに指示します。

現在、これは合理的な制御フローのように見えるかもしれませんが、重大な問題を隠しています。

メイン(UI)スレッドでアクティビティインジケーターのstartAnimatingメソッドを呼び出すと、このイベントのUI更新は、実際に次回 メイン実行ループ が更新されるまで行われません。大きな問題です。

この更新が行われる前に、ダウンロードがトリガーされます。これは同期操作であるため、ダウンロードが完了するまでメインスレッドをブロックします(保存にも同じ問題があります)。これにより、アクティビティインジケータがアニメーションを開始できなくなります。その後、アクティビティインジケータのstopAnimatingメソッドを呼び出して、すべてが適切であると期待しますが、そうではありません。

この時点で、おそらく次のことを疑問に思うでしょう。

アクティビティインジケーターが表示されないのはなぜですか?

さて、このように考えてください。インジケーターに開始するように指示しますが、ダウンロードが開始される前にチャンスを得ることができません。ダウンロードが完了したら、インジケータにアニメーションを停止するよう指示します。メインスレッドは操作全体でブロックされたため、実際に表示される動作は、(おそらく)大きなダウンロードタスクが間にあったとしても、インジケーターに開始するよう指示し、すぐに停止するよう指示する行に沿っています。

さて、ベストケースシナリオでは、これはユーザーエクスペリエンスの低下を引き起こします(それでも本当に悪い)。小さな画像をダウンロードしているだけで、ダウンロードはほとんど瞬時に行われるため、これは大したことではないと思っても、常にそうなるとは限りません。一部のユーザーは、インターネット接続が遅いか、サーバー側で何かが間違っている可能性があり、ダウンロードがすぐに/まったく開始されない可能性があります。

どちらの場合でも、アプリはUIの更新を処理できず、ダウンロードが完了するまで、またはサーバーがリクエストに応答するのを待っている間、ダウンロードタスクが親指をいじっている間にイベントに触れることもできません。

これが意味することは、メインスレッドから同期的にダウンロードすることで、ダウンロードが現在進行中であることをユーザーに示すために何かを実装することができなくなることです。また、タッチイベントはメインスレッドでも処理されるため、これにより、任意の種類のキャンセルボタンを追加する可能性もなくなります。

次に、最悪のシナリオでは、次のようなクラッシュレポートの受信を開始します。

例外タイプ:00000020例外コード:0x8badf00d

これらは例外コード0x8badf00dで簡単に識別できます。例外コードは「悪い食べ物を食べた」と読むことができます。この例外は、メインスレッドをブロックする長時間実行中のタスクを監視するウォッチドッグタイマーによってスローされ、これが長時間続くと問題のアプリを強制終了します。おそらく、これは依然としてユーザーエクスペリエンスの質の低い問題ですが、これが発生し始めると、アプリはユーザーエクスペリエンスの低下とひどいユーザーエクスペリエンスの境界を越えました。

同期ネットワーク(簡潔にするために短縮)について AppleのテクニカルQ&A からこれが発生する原因についての詳細情報を次に示します。

ネットワークアプリケーションでのウォッチドッグタイムアウトクラッシュの最も一般的な原因は、メインスレッドでの同期ネットワーキングです。ここには4つの要因があります。

  1. 同期ネットワーク—これは、ネットワーク要求を作成し、応答の待機をブロックする場所です。
  2. メインスレッド—同期ネットワークは一般的に理想的ではありませんが、メインスレッドで実行すると特定の問題が発生します。メインスレッドがユーザーインターフェイスの実行を担当していることに注意してください。かなりの時間メインスレッドをブロックすると、ユーザーインターフェイスが応答不能になります。
  3. 長いタイムアウト—ネットワークがなくなる(たとえば、ユーザーがトンネルに入る列車に乗っている)場合、保留中のネットワーク要求は、タイムアウトが期限切れになるまで失敗しません。

...

  1. ウォッチドッグ—ユーザーインターフェイスの応答性を維持するために、iOSにはウォッチドッグメカニズムが含まれています。アプリケーションが特定のユーザーインターフェイスイベント(起動、一時停止、再開、終了)に時間内に応答しない場合、ウォッチドッグはアプリケーションを強制終了し、ウォッチドッグタイムアウトクラッシュレポートを生成します。ウォッチドッグが提供する時間は正式には文書化されていませんが、ネットワークタイムアウトよりも常に短い時間です。

この問題の難しい点の1つは、ネットワーク環境に大きく依存していることです。ネットワーク接続が良好なオフィスで常にアプリケーションをテストする場合、このタイプのクラッシュは決して見られません。ただし、アプリケーションをすべての種類のネットワーク環境で実行するエンドユーザーへの展開を開始すると、このようなクラッシュが一般的になります。

この時点で、提供された回答に問題がある可能性がある理由についてとりとめずに、代替ソリューションの提供を開始します。これらの例では小さな画像のURLを使用したことを覚えておいてください。高解像度の画像を使用すると、大きな違いに気付くでしょう。


解決策

UIの更新を処理する方法を追加して、他の回答の安全なバージョンを表示することから始めます。これはいくつかの例の最初のものであり、それらはすべて、それらが実装されるクラスがUIImageView、UIActivityIndi​​catorView、およびドキュメントディレクトリにアクセスする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;
}

また、これらの例では、開始するスレッドがメインスレッドであると想定しています。これは、他の非同期タスクのコールバックブロックのような場所からダウンロードタスクを開始しない限り、デフォルトの動作になる可能性があります。 View Controllerのライフサイクルメソッド(viewDidLoad、viewWillAppear:など)のような一般的な場所でダウンロードを開始すると、予想される動作が生成されます。

この最初の例では+[NSData dataWithContentsOfURL:]メソッドを使用しますが、いくつかの重要な違いがあります。たとえば、この例では、最初に呼び出すことはアクティビティインジケータにアニメーションを開始するよう指示することであることに気付くでしょう。この例と同期の例にはすぐに違いがあります。すぐに、dispatch_async()を使用して、グローバルコンカレントキューを渡し、実行をバックグラウンドスレッドに移動します。

この時点で、ダウンロードタスクが大幅に改善されています。これで、dispatch_async()ブロック内のすべてがメインスレッドから発生するため、インターフェイスがロックされなくなり、アプリはタッチイベントに自由に応答できるようになります。

ここで重要なのは、このブロック内のすべてのコードが、画像のダウンロード/保存が成功するまでバックグラウンドスレッドで実行されることです。この時点で、アクティビティインジケータにstopAnimating 、または新しく保存した画像をUIImageViewに適用します。いずれにせよ、これらはUIの更新です。つまり、dispatch_get_main_queue()を使用してメインスレッドをディスパッチし、実行する必要があります。そうしないと、未定義の動作が発生し、予期しない期間後にUIが更新されたり、クラッシュする可能性があります。 UIの更新を実行する前に、必ずメインスレッドに戻るようにしてください。

// 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はもはやメンテナンスされておらず、not新しいプロジェクトに使用されます。


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 にフィードできることです。人間が読める単位(100 KBの42 KBなど)を使用して、ユーザーにダウンロード進行状況インジケーターを簡単に作成します。

ただし、NSURLSessionから離れる前に、ダウンロードのコールバックブロックのいくつかの異なるポイントでメインスレッドにディスパッチ_asyncしなければならないというさを回避できることを指摘したいと思います。このルートを使用することを選択した場合、デリゲートとデリゲートキューを指定できる初期化子でセッションを初期化できます。これには、コールバックブロックの代わりにデリゲートパターンを使用する必要がありますが、バックグラウンドダウンロードをサポートする唯一の方法であるため、これは有益な場合があります。

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

AFNetworking 2.0

AFNetworking を聞いたことがないなら、それは私見のネットワークライブラリのすべてです。 Objective-C用に作成されましたが、Swiftでも機能します。著者の言葉で:

AFNetworkingは、iOSおよびMac OS X用の楽しいネットワークライブラリです。FoundationURL Loading Systemの上に構築され、Cocoaに組み込まれた強力な高レベルのネットワーク抽象化を拡張します。よく設計された機能豊富なAPIを備えたモジュラーアーキテクチャで、使用するのが楽しいです。

AFNetworking 2.0はiOS 6以降をサポートしますが、この例では、AFHTTPSessionManagerクラスを使用します。このクラスでは、NSURLSessionクラスの周りのすべての新しいAPIを使用するため、iOS 7以降が必要です。これは、上記のNSURLSessionの例と多くのコードを共有する以下の例を読むと明らかになります。

ただし、指摘したいいくつかの違いがあります。最初に、独自のNSURLSessionを作成する代わりに、NSURLSessionを内部的に管理するAFURLSessionManagerのインスタンスを作成します。そうすることで、-[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インスタンスのプロパティの1つに追加したので、-[NSObject observeValueForKeyPath:ofObject:change:context:]メソッドを実装する必要があります。この場合、進行状況ラベルを更新してダウンロードの進行状況を表示する方法の例を含めました。とても簡単です。 NSProgressには、ローカライズされた人間が読める形式で進捗情報を表示するインスタンスメソッドlocalizedDescriptionがあります。

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    // We only care about updates to fractionCompleted
    if ([keyPath isEqualToString:NSStringFromSelector(@selector(fractionCompleted))]) {
        NSProgress *progress = (NSProgress *)object;
        // localizedDescription gives a string appropriate for display to the user, i.e. "42% completed"
        self.progressLabel.text = progress.localizedDescription;
    } else {
        [super observeValueForKeyPath:keyPath
                             ofObject:object
                               change:change
                              context:context];
    }
}

プロジェクトでAFNetworkingを使用する場合は、 インストール手順 を実行し、#import <AFNetworking/AFNetworking.h>を確認する必要があることを忘れないでください。

アラモファイア

最後に、 Alamofire を使用した最後の例を示します。これは、Swiftのネットワークをケーキウォークにするライブラリです。私はこのサンプルの内容について非常に詳細に説明するためにキャラクターが不足していますが、それは最後の例とほぼ同じことを、おそらくより美しい方法で行います。

// Create the destination closure to pass to the download request. I haven't done anything with them
// here but you can utilize the parameters to make adjustments to the file name if neccessary.
let destination = { (url: NSURL!, response: NSHTTPURLResponse!) -> NSURL in
    var error: NSError?
    // Get the documents directory
    let documentsDirectory = NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory,
        inDomain: .UserDomainMask,
        appropriateForURL: nil,
        create: false,
        error: &error
    )

    if let error = error {
        // This could be bad. Make sure you have a backup plan for where to save the image.
        println("\(error.localizedDescription)")
    }

    // Return a destination of .../Documents/Alamofire.png
    return documentsDirectory!.URLByAppendingPathComponent("Alamofire.png")
}

Alamofire.download(.GET, "http://www.google.com/images/srpr/logo3w.png", destination)
    .validate(statusCode: 200..<299) // Require the HTTP status code to be in the Successful range.
    .validate(contentType: ["image/png"]) // Require the content type to be image/png.
    .progress { (bytesRead, totalBytesRead, totalBytesExpectedToRead) in
        // Create an NSProgress object to represent the progress of the download for the user.
        let progress = NSProgress(totalUnitCount: totalBytesExpectedToRead)
        progress.completedUnitCount = totalBytesRead

        dispatch_async(dispatch_get_main_queue()) {
            // Move back to the main thread and update some progress label to show the user the download is in progress.
            self.progressLabel.text = progress.localizedDescription
        }
    }
    .response { (request, response, _, error) in
        if error != nil {
            // Something went wrong. Handle the error.
        } else {
            // Open the newly saved image data.
            if let imageData = NSData(contentsOfURL: destination(nil, nil)) {
                dispatch_async(dispatch_get_main_queue()) {
                    // Move back to the main thread and add the image to your image view.
                    self.imageView.image = UIImage(data: imageData)
                }
            }
        }
    }
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];

permanentではありませんが、少なくともユーザーがアプリを削除するまでそこにとどまります。

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: を参照してくださいデータベースに保存するか、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

RLから画像を非同期でダウンロードし、目的の場所を保存するコードがあります-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を使用してUIをブロックせずにイメージをダウンロードできます。

+(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

これは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)
        }
    }
}
0
tanaschita