質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.53%

  • Swift

    7213questions

    Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

  • Xcode

    4082questions

    Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

  • iOS

    3980questions

    iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Swift3:画像の非同期読み込み時に別の画像が表示される

受付中

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 774

mokumoku

score 34

画像をURLから読み込み、TableViewのセルに
それぞれが保持しているURL先の画像を表示させる処理を実装しております。

表示するセルの数が何百件とあり、
素早くスクロールすると、別のセルで使っている画像が表示されてしまうことがあるため改善したいです。

scrollView.decelerationRate = UIScrollViewDecelerationRateFast


上記を指定してスクロール速度を下げてみましたが、
解決には至りませんでした。

SDWebImageというライブラリを使用した実装にしようかとも考えましたが、
いまいち実装方法が不明なため、下記のような実装となりました。

何か、解決方法等がございましたら、ご教示お願い致します。

import UIKit
import SwiftyJSON

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, URLSessionDelegate {

    @IBOutlet weak var tableView: UITableView!

~ 省略 ~

    // セクション数
    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }

    // セクションの行数
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return friends.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath:IndexPath) -> UITableViewCell {    
        let cell: CustomCell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath as IndexPath) as! CustomCell
        cell.setCell(friend: friends[indexPath.row])
        return cell
    }
//非同期
//friend.imageUrlには画像のURLが入っています

import UIKit

class CustomCell: UITableViewCell {

@IBOutlet weak var imageView: UIImageView!

~ 省略 ~

override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }


    func setCell(friend :Friend) {

            let req = URLRequest(url: NSURL(string:friend.imageUrl as String)! as URL,
                                 cachePolicy: .returnCacheDataElseLoad);
            //追記
            let urlStay = req.url
            let conf =  URLSessionConfiguration.default;
            let session = URLSession(configuration: conf, delegate: nil, delegateQueue: OperationQueue.main);

            session.dataTask(with: req, completionHandler:
                { (data, resp, err) in
                    //追記
                    if((err) == nil && urlStay == resp?.url){ //Success
                        let image = UIImage(data:data!)
                        self.imageView.image = image;

                    }else{ //Error
                        print("AsyncImageView:Error \(err?.localizedDescription)");
                    }

            }).resume();
           }
    }
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • fuzzball

    2017/03/13 15:47

    このリクエストはどこから呼んでいるのでしょうか?

    キャンセル

  • mokumoku

    2017/03/13 16:20

    すみません、friend.imageUrlに入っているURLのことでしょうか。

    キャンセル

  • fuzzball

    2017/03/13 16:25

    let req = URLRequest() 〜 session.dataTask().resume(); のコードはどこから(どういうタイミングで)呼ばれているのでしょうか?

    キャンセル

  • mokumoku

    2017/03/13 16:49

    ViewControllerのfunc tableView(_ tableView: UITableView, cellForRowAt indexPath:IndexPath) -> UITableViewCellで、画面表示時とTableViewをスクロールした際に呼ばれているかと思います。

    キャンセル

回答 2

0

セルが使われなくなったのにリクエスト(&画像セット)の処理が残っているのが原因だと思われます。

 リクエストのキャンセル

画像のリクエストをした後、リクエストが完了する前に画面外に出たとき、もしくは他のセルが使おうとしたとき、もしくは両方のタイミングで、リクエストをキャンセルして下さい。dataTask(with:completionHandler:)の戻り値を保存しておく必要があります。

 読み込み完了時にURL確認

画像リクエスト時にURLを保存しておき、読み込み完了時にresp.urlと比較し、一致すればimageをセット、一致しなければセットしないようにする。


どっちかだけでもいけそうな気がしますが、両方やっておくと安心です。
もしかしたら両方やっていてもがあるかも知れません。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/03/13 17:48

    読み込み完了時にURL確認の実装を質問文に追記しましたが、間違っていますでしょうか。
    実行したところ実装前と変化ありませんでした。
    リクエストのキャンセルの戻り値を保存しておくというのはどういった処理を実装すればよいのかイメージできません。何か参考等ございませんでしょうか。
    お手数ですがよろしくお願いします。

    キャンセル

  • 2017/03/13 18:04

    urlStayをプロパティにしてみてもらえますか?

    >>リクエストのキャンセルの戻り値を保存
    戻り値というのは URLSessionDataTask のことです。
    task = session.dataTask(...)
    task.resume()
    という形にしておいて、task.cancel()でリクエストをキャンセル出来ます。

    キャンセル

  • 2017/03/13 19:36

    キャンセル処理は入れないとマズイですね。そのまま放っておくと、スクロールしたときにリクエストがどんどん溜まっていってしまいます。

    キャンセル

  • 2017/03/13 20:47

    task.cancel()はtask.resume()を抜けた後に宣言するのでしょうか。
    Did you mean 'mask'?と表示されます。

    キャンセル

  • 2017/03/14 08:43 編集

    キャンセルは、キャンセルすべきときに呼び出して下さい。
    ところで、urlStayをプロパティにするとどうなりましたか?

    キャンセル

  • 2017/03/14 21:36

    elseでスクロール時の処理は入ってきていると思っておりましたが、
    別で処理を実装する必要があるということですか。
    プロパティにするというのはどういうことなのかわからないため、試せておりません。
    下記を参考に実装しようとしておりますが、まだ理解できておりません。
    http://tea-leaves.jp/swift/content/%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3

    キャンセル

  • 2017/03/15 01:35

    CustomCellに、
    var urlStay: URL?
    を追加して下さい。

    CustomCell自身からは、self.urlStay もしくは urlStay だけでアクセス出来ます。
    他のクラスから、例えば tableView(_:cellForRowAt:) の中でしたら、
    cell.urlStay でアクセス出来ます。

    キャンセル

0

試していないので外しているかもしれませんが、cellのprepareForReuse()でimageView.image=nilとしてみては?

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/03/13 22:30

    お手数おかけします。
    下記を追記しましたが、実装前と変化はありませんでした。
    printを使用して、呼ばれていることも確認しました。
    override func prepareForReuse()
    {
     self.imageView.image = nil;
    }

    キャンセル

  • 2017/03/13 22:36

    駄目でしたか(^^;)
    ちなみにsuper.prepareForReuse()
    が必要だったと思います。
    あと、ここでdataTaskのキャンセルを入れるといいかも。

    キャンセル

  • 2017/03/13 23:11

    super.prepareForReuse()を記載しましたが、ダメでした。
    dataTaskのキャンセルの記載方法がいまいち不明です。
    setCell内を、let task = session.dataTask(with: req, completionHandler:に変更して、
    prepareForReuse内にtask.cancel()を記載しましたがスコープ的に見えてないようなエラーがでました。
    dataTaskのキャンセルの記載方法はどういった記載になりますでしょうか。
    お手数ですが、よろしくお願いします。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.53%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • Swift

    7213questions

    Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

  • Xcode

    4082questions

    Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

  • iOS

    3980questions

    iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。