การโหลดภาพ Async จาก url ภายในเซลล์ UITableView - ภาพจะเปลี่ยนเป็นภาพผิดขณะเลื่อน


158

ฉันได้เขียนสองวิธีเพื่อ async โหลดภาพภายในเซลล์ UITableView ของฉัน ในทั้งสองกรณีรูปภาพจะโหลดได้ดี แต่เมื่อฉันจะเลื่อนตารางรูปภาพจะเปลี่ยนไปสองสามครั้งจนกว่าการเลื่อนจะสิ้นสุดและภาพจะกลับไปเป็นภาพที่ถูกต้อง ฉันไม่รู้ว่าทำไมสิ่งนี้จึงเกิดขึ้น

#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

- (void)viewDidLoad
{
    [super viewDidLoad];
    dispatch_async(kBgQueue, ^{
        NSData* data = [NSData dataWithContentsOfURL: [NSURL URLWithString:
                                                       @"http://myurl.com/getMovies.php"]];
        [self performSelectorOnMainThread:@selector(fetchedData:)
                               withObject:data waitUntilDone:YES];
    });
}

-(void)fetchedData:(NSData *)data
{
    NSError* error;
    myJson = [NSJSONSerialization
              JSONObjectWithData:data
              options:kNilOptions
              error:&error];
    [_myTableView reloadData];
}    

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    // Return the number of sections.
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    // Return the number of rows in the section.
    // Usually the number of items in your array (the one that holds your list)
    NSLog(@"myJson count: %d",[myJson count]);
    return [myJson count];
}
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

        myCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
        if (cell == nil) {
            cell = [[myCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
        }

        dispatch_async(kBgQueue, ^{
        NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]]];

            dispatch_async(dispatch_get_main_queue(), ^{
        cell.poster.image = [UIImage imageWithData:imgData];
            });
        });
         return cell;
}

... ...

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

            myCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
            if (cell == nil) {
                cell = [[myCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
            }
    NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]];
    NSURLRequest* request = [NSURLRequest requestWithURL:url];


    [NSURLConnection sendAsynchronousRequest:request
                                       queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse * response,
                                               NSData * data,
                                               NSError * error) {
                               if (!error){
                                   cell.poster.image = [UIImage imageWithData:data];
                                   // do whatever you want with image
                               }

                           }];
     return cell;
}

5
คุณกำลังพยายามเก็บข้อมูลในเซลล์จริง มันแย่มากแย่มาก คุณควรเก็บข้อมูลไว้ใน n array (หรือสิ่งที่คล้ายกัน) แล้วแสดงในเซลล์ ข้อมูลในกรณีนี้คือ UIImage จริง ใช่โหลดแบบอะซิงโครนัส แต่โหลดลงในอาร์เรย์
Fogmeister

1
@Fogmeister คุณหมายถึงposterอะไร นั่นคือมุมมองภาพในเซลล์ที่กำหนดเองของเขาดังนั้นสิ่งที่ EXEC_BAD_ACCESS กำลังทำอยู่นั้นถูกต้องสมบูรณ์ คุณถูกต้องที่คุณไม่ควรใช้เซลล์เป็นที่เก็บข้อมูลโมเดล แต่ฉันไม่คิดว่านั่นคือสิ่งที่เขาทำ เขาแค่ให้เซลล์ที่กำหนดเองในสิ่งที่มันต้องการนำเสนอ นอกจากนี้และนี่เป็นปัญหาที่ละเอียดอ่อนกว่านี้ฉันจะระวังเรื่องการจัดเก็บรูปภาพด้วยตัวเองในอาเรย์รุ่นของคุณที่สนับสนุนมุมมองตารางของคุณ เป็นการดีกว่าที่จะใช้กลไกการแคชรูปภาพและวัตถุโมเดลของคุณควรดึงจากแคชนั้น
Rob

1
ใช่จุดของฉัน ดูที่คำขอ (ซึ่งแสดงเต็ม) เขากำลังดาวน์โหลดรูปภาพแบบอะซิงโครนัสและวางลงใน imageView ในเซลล์โดยตรง (ดังนั้นการใช้เซลล์เพื่อเก็บข้อมูลเช่นภาพ) สิ่งที่เขาควรทำคืออ้างอิงวัตถุและขอภาพจากวัตถุนั้น (อยู่ในอาเรย์หรือที่อื่น) หากวัตถุยังไม่มีรูปก็ควรคืนค่าที่สำรองไว้และดาวน์โหลดรูปภาพ จากนั้นเมื่อรูปภาพถูกดาวน์โหลดและพร้อมที่จะแสดงให้ตารางทราบเพื่อให้สามารถอัปเดตเซลล์ (หากมองเห็นได้)
Fogmeister

1
สิ่งที่เขาทำจะบังคับให้ดาวน์โหลดทุกครั้งที่เขาเลื่อนไปที่เซลล์ในตาราง ไม่ว่าภาพจะถูกจัดเก็บอย่างไม่หยุดยั้งจะขึ้นอยู่กับเขา แต่อย่างน้อยก็เก็บภาพเหล่านั้นไว้ตลอดชีวิตของมุมมองตาราง
Fogmeister

1
ตรง: D ด้วยวิธีนี้คุณจะต้องดึงภาพจาก URL เพียงครั้งเดียว คุณจะเห็นสิ่งนี้ในสิ่งที่เพื่อน Facebook เลือก เมื่อคุณเริ่มใช้มันรูปประจำตัวทั้งหมดเป็นตัวยึดสีเทา จากนั้นในขณะที่คุณเลื่อนพวกเขาทั้งหมดเติมในขณะที่มันเคลื่อนที่ไปตาม แต่เมื่อคุณเลื่อนกลับไปที่เซลล์ที่แสดงก่อนหน้านี้มันจะแสดงภาพที่ดาวน์โหลดไปแล้วทันที
Fogmeister

คำตอบ:


230

สมมติว่าคุณกำลังมองหาการแก้ปัญหาทางยุทธวิธีอย่างรวดเร็วสิ่งที่คุณต้องทำคือทำให้แน่ใจว่าภาพเริ่มต้นของเซลล์และแถวของเซลล์นั้นยังคงมองเห็นได้เช่น:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    MyCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];

    cell.poster.image = nil; // or cell.poster.image = [UIImage imageNamed:@"placeholder.png"];

    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg", self.myJson[indexPath.row][@"movieId"]]];

    NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (data) {
            UIImage *image = [UIImage imageWithData:data];
            if (image) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    MyCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];
                    if (updateCell)
                        updateCell.poster.image = image;
                });
            }
        }
    }];
    [task resume];

    return cell;
}

โค้ดด้านบนเน้นปัญหาเล็กน้อยที่เกิดจากการที่เซลล์ถูกนำกลับมาใช้ใหม่:

  1. คุณไม่ได้เริ่มต้นภาพเซลล์ก่อนเริ่มคำขอพื้นหลัง (หมายถึงภาพสุดท้ายของเซลล์ที่ถูกตัดผ่านจะยังคงมองเห็นได้ในขณะที่ภาพใหม่กำลังดาวน์โหลด) ให้แน่ใจว่าจะทรัพย์สินของมุมมองภาพใด ๆ หรืออื่น ๆ คุณจะเห็นริบหรี่ของภาพnilimage

  2. ปัญหาที่ลึกซึ้งยิ่งขึ้นคือในเครือข่ายที่ช้ามากคำขอแบบอะซิงโครนัสของคุณอาจไม่เสร็จสิ้นก่อนที่เซลล์จะเลื่อนออกจากหน้าจอ คุณสามารถใช้UITableViewวิธีการcellForRowAtIndexPath:(เพื่อไม่ให้สับสนกับUITableViewDataSourceวิธีที่มีชื่อคล้ายกันtableView:cellForRowAtIndexPath:) เพื่อดูว่าเซลล์สำหรับแถวนั้นยังคงมองเห็นได้หรือไม่ วิธีนี้จะส่งคืนnilหากไม่สามารถมองเห็นเซลล์ได้

    ปัญหาคือเซลล์เลื่อนออกไปตามเวลาที่วิธีการซิงค์ของคุณเสร็จสมบูรณ์และยิ่งแย่ไปกว่านั้นเซลล์นั้นถูกนำมาใช้ซ้ำสำหรับแถวอื่นของตาราง โดยการตรวจสอบเพื่อดูว่าแถวนั้นยังคงปรากฏอยู่หรือไม่คุณจะมั่นใจได้ว่าคุณจะไม่อัปเดตรูปภาพโดยไม่ได้ตั้งใจกับรูปภาพสำหรับแถวที่เลื่อนไปมาตั้งแต่หน้าจอ

  3. ค่อนข้างไม่เกี่ยวข้องกับคำถามในมือฉันยังรู้สึกว่าจำเป็นต้องอัปเดตสิ่งนี้เพื่อยกระดับการประชุมและ API ที่ทันสมัยโดยเฉพาะอย่างยิ่ง:

    • ใช้NSURLSessionแทนการส่ง-[NSData contentsOfURL:]ไปยังคิวพื้นหลัง

    • ใช้dequeueReusableCellWithIdentifier:forIndexPath:แทนdequeueReusableCellWithIdentifier:(แต่ต้องแน่ใจว่าใช้เซลล์ต้นแบบหรือรีจิสเตอร์คลาสหรือ NIB สำหรับตัวระบุนั้น); และ

    • ฉันใช้ชื่อคลาสที่สอดคล้องกับข้อกำหนดการตั้งชื่อของโกโก้ (เช่นเริ่มต้นด้วยตัวอักษรตัวพิมพ์ใหญ่)

แม้จะมีการแก้ไขเหล่านี้ก็ยังมีปัญหา:

  1. รหัสด้านบนไม่ได้แคชภาพที่ดาวน์โหลด นั่นหมายความว่าหากคุณเลื่อนภาพออกจากหน้าจอและกลับมาที่หน้าจอแอปอาจพยายามดึงภาพอีกครั้ง บางทีคุณอาจโชคดีพอที่ส่วนหัวการตอบกลับของเซิร์ฟเวอร์ของคุณจะอนุญาตให้มีการแคชที่ค่อนข้างโปร่งใสที่เสนอโดยNSURLSessionและNSURLCacheแต่ถ้าไม่ใช่คุณจะทำการร้องขอเซิร์ฟเวอร์ที่ไม่จำเป็นและเสนอ UX ที่ช้ากว่ามาก

  2. เราไม่ได้ยกเลิกการร้องขอสำหรับเซลล์ที่เลื่อนหน้าจอออก ดังนั้นหากคุณเลื่อนไปที่แถวที่ 100 อย่างรวดเร็วภาพสำหรับแถวนั้นอาจถูกย้อนกลับไปด้านหลังคำขอสำหรับ 99 แถวก่อนหน้าซึ่งไม่สามารถมองเห็นได้อีกต่อไป คุณต้องการให้แน่ใจว่าคุณจัดลำดับความสำคัญคำขอสำหรับเซลล์ที่มองเห็นได้สำหรับ UX ที่ดีที่สุด

การแก้ไขที่ง่ายที่สุดที่อยู่ในประเด็นที่เหล่านี้คือการใช้UIImageViewหมวดหมู่เช่นมีให้กับSDWebImageหรือAFNetworking หากคุณต้องการคุณสามารถเขียนรหัสของคุณเองเพื่อจัดการกับปัญหาข้างต้น แต่มันทำงานได้มากและUIImageViewหมวดหมู่ข้างต้นได้ทำสิ่งนี้ให้คุณแล้ว


1
ขอบคุณ ฉันเชื่อว่าคุณต้องแก้ไขคำตอบของคุณ updateCell.poster.image = nilto cell.poster.image = nil;updateCell ถูกเรียกใช้ก่อนที่จะมีการประกาศ
Segev

1
แอปของฉันใช้ json เป็นจำนวนมากดังนั้นAFNetworkingมันจึงเป็นหนทางที่แน่นอน ฉันรู้เรื่องนี้ แต่ก็ขี้เกียจที่จะใช้มัน ฉันแค่ชื่นชมวิธีที่แคชทำงานกับบรรทัดโค้ดอย่างง่ายของพวกเขา [imageView setImageWithURL:<#(NSURL *)#> placeholderImage:<#(UIImage *)#>];
Segev

2
พยายามทั้งหมดข้างต้นและ SDWebImage (หยุดจริงที่นี่และไม่จำเป็นต้องลอง AFNetworking) และนี่เป็นตัวเลือกที่ดีที่สุด ขอบคุณ @Rob
จันทร์ที่

1
เมื่อคุณเสร็จสิ้นการโหลดภาพและอัปเดตมุมมองภาพให้ลบตัวบ่งชี้กิจกรรม เคล็ดลับเดียวคือคุณต้องคาดการณ์ว่าจะเกิดอะไรขึ้นหากเซลล์เลื่อนออกไปจากมุมมองในขณะที่กำลังดึงภาพและเซลล์นั้นถูกนำไปใช้สำหรับแถวอื่นคุณจะต้องตรวจสอบว่ามีตัวบ่งชี้กิจกรรมที่มีอยู่และลบ / อัปเดต มันไม่ใช่แค่สมมติว่าเซลล์ไม่มีตัวบ่งชี้ที่มีอยู่
Rob

1
คำถามเดิมคือ "เหตุใดจึงcellForRowAtIndexPathทำให้เกิดภาพที่กะพริบเมื่อฉันเลื่อนอย่างรวดเร็ว" และฉันอธิบายว่าทำไมมันถึงเกิดขึ้นเช่นเดียวกับวิธีการแก้ไข แต่ฉันอธิบายต่อไปว่าทำไมถึงแม้จะไม่เพียงพออธิบายปัญหาที่ลึกลงไปสองสามข้อและแย้งว่าทำไมคุณควรใช้กับห้องสมุดเหล่านี้เพื่อจัดการกับสิ่งนี้อย่างสง่างามมากขึ้น (จัดลำดับความสำคัญของคำขอเซลล์ที่มองเห็นได้ คำขออื่น ๆ ) ฉันไม่ชัดเจนว่าคุณคาดหวังอะไรในการตอบคำถาม "ฉันจะหยุดภาพที่ริบหรี่ในมุมมองตารางของฉันได้อย่างไร"
Rob

15

/ * ฉันทำแบบนี้แล้วก็ทดสอบมันด้วย * /

ขั้นตอนที่ 1 = ลงทะเบียนคลาสเซลล์ที่กำหนดเอง (ในกรณีของเซลล์ต้นแบบในตาราง) หรือปลายปากกา (ในกรณีของปลายปากกาที่กำหนดเองสำหรับเซลล์ที่กำหนดเอง) สำหรับตารางแบบนี้ในเมธอด viewDidLoad:

[self.yourTableView registerClass:[CustomTableViewCell class] forCellReuseIdentifier:@"CustomCell"];

หรือ

[self.yourTableView registerNib:[UINib nibWithNibName:@"CustomTableViewCell" bundle:nil] forCellReuseIdentifier:@"CustomCell"];

ขั้นตอนที่ 2 = ใช้วิธีการ "dequeueReusableCellWithIdentifier ของ UITableView: forIndexPath:" เช่นนี้ (สำหรับสิ่งนี้คุณต้องลงทะเบียน class หรือปลายปากกา):

   - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
            CustomTableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"CustomCell" forIndexPath:indexPath];

            cell.imageViewCustom.image = nil; // [UIImage imageNamed:@"default.png"];
            cell.textLabelCustom.text = @"Hello";

            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                // retrive image on global queue
                UIImage * img = [UIImage imageWithData:[NSData dataWithContentsOfURL:     [NSURL URLWithString:kImgLink]]];

                dispatch_async(dispatch_get_main_queue(), ^{

                    CustomTableViewCell * cell = (CustomTableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
                  // assign cell image on main thread
                    cell.imageViewCustom.image = img;
                });
            });

            return cell;
        }

1
การเรียก cellForRowAtIndexPath ภายในบล็อกขั้นสุดท้ายไม่ทำให้เกิดการโดนไล่ออกเป็นครั้งที่สองหรือไม่
Mark Bridges

@ MarkBridges เลขที่จริงฉันกำลังเรียกวิธี cellForRowAtIndexPath ของ tableView ที่นี่ อย่าสับสนกับวิธีแหล่งข้อมูลของ tableView ที่มีชื่อเดียวกัน มันต้องการมันสามารถเรียกเช่น [self tableView: tableView cellForRowAtIndexPath: indexPath]; หวังว่าสิ่งนี้จะช่วยลดความสับสนของคุณ
Nitesh Borad

14

มีกรอบงานหลายอย่างที่ช่วยแก้ปัญหานี้ได้ เพียงเพื่อชื่อไม่กี่:

สวิฟท์:

Objective-C:


โปรดเพิ่มข้อเสนอแนะของคุณหากมีกรอบการพิจารณาอื่น ๆ ที่มีมูลค่าการพิจารณา
kean

3
ที่จริงSDWebImageไม่ได้แก้ปัญหานี้ คุณสามารถควบคุมได้เมื่อมีการดาวน์โหลดรูปภาพ แต่SDWebImageกำหนดภาพให้UIImageViewโดยไม่ต้องขออนุญาตจากคุณก่อน โดยพื้นฐานแล้วปัญหาจากคำถามยังไม่สามารถแก้ไขได้ด้วยห้องสมุดนี้
BartłomiejSemańczyk

ปัญหาจากคำถามคือผู้เขียนไม่ได้ตรวจสอบว่าเซลล์ถูกนำมาใช้ซ้ำหรือไม่ นี่เป็นปัญหาพื้นฐานมากซึ่งแก้ไขโดยกรอบงานเหล่านั้นรวมถึง SDWebImage
kean

SDWebImage ค่อนข้างล่าช้าตั้งแต่ iOS 8 มันเป็นหนึ่งในเฟรมเวิร์กที่ฉันชอบ แต่ตอนนี้ฉันเริ่มใช้ PinRemoteImage ซึ่งทำงานได้ดีจริงๆ
Joan Cardona

@ BartłomiejSemańczykคุณมีสิทธิที่ปัญหานี้ไม่ได้แก้ไขได้โดย SDWebimage
ม.ค.

9

สวิฟท์ 3

ฉันเขียนการใช้แสงของตัวเองสำหรับโหลดเดอร์อิมเมจด้วยการใช้ NSCache ไม่มีภาพบนมือถือกะพริบ!

ImageCacheLoader.swift

typealias ImageCacheLoaderCompletionHandler = ((UIImage) -> ())

class ImageCacheLoader {
    
    var task: URLSessionDownloadTask!
    var session: URLSession!
    var cache: NSCache<NSString, UIImage>!
    
    init() {
        session = URLSession.shared
        task = URLSessionDownloadTask()
        self.cache = NSCache()
    }
    
    func obtainImageWithPath(imagePath: String, completionHandler: @escaping ImageCacheLoaderCompletionHandler) {
        if let image = self.cache.object(forKey: imagePath as NSString) {
            DispatchQueue.main.async {
                completionHandler(image)
            }
        } else {
            /* You need placeholder image in your assets, 
               if you want to display a placeholder to user */
            let placeholder = #imageLiteral(resourceName: "placeholder")
            DispatchQueue.main.async {
                completionHandler(placeholder)
            }
            let url: URL! = URL(string: imagePath)
            task = session.downloadTask(with: url, completionHandler: { (location, response, error) in
                if let data = try? Data(contentsOf: url) {
                    let img: UIImage! = UIImage(data: data)
                    self.cache.setObject(img, forKey: imagePath as NSString)
                    DispatchQueue.main.async {
                        completionHandler(img)
                    }
                }
            })
            task.resume()
        }
    }
}

ตัวอย่างการใช้งาน

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
    let cell = tableView.dequeueReusableCell(withIdentifier: "Identifier")
    
    cell.title = "Cool title"

    imageLoader.obtainImageWithPath(imagePath: viewModel.image) { (image) in
        // Before assigning the image, check whether the current cell is visible
        if let updateCell = tableView.cellForRow(at: indexPath) {
            updateCell.imageView.image = image
        }
    }    
    return cell
}

3
ฉันจะบอกว่าขอบคุณ แต่รหัสมีปัญหาเล็กน้อย ถ้าให้ data = ลอง ข้อมูล (contentOf: url) {// โปรดแทนที่ url เป็นตำแหน่ง มันจะช่วยผู้คนจำนวนมาก
Carl Hung

2
ด้วยรหัสตามที่เป็นอยู่คุณดาวน์โหลดไฟล์สองครั้งผ่านเครือข่าย: หนึ่งครั้งใน downloadTaks หนึ่งครั้งด้วย Data (cntentsOf :) คุณต้องระบุตำแหน่งผู้ใช้แทน url เพราะงานดาวน์โหลดนั้นดาวน์โหลดผ่านเครือข่ายและเขียนข้อมูลลงในไฟล์ชั่วคราวและส่ง localUrl (ตำแหน่งในกรณีของคุณ) ดังนั้นข้อมูลต้องชี้ไปที่ Url ท้องถิ่นเพื่อให้อ่านได้จากไฟล์เท่านั้น
Stéphane de Luca

ในตัวอย่างการใช้งานมันควรจะเป็น "ImageCacheLoader.obtainImageWithPath (imagePath: viewModel.image) ....... " หรือไม่
Tim Kruger

ไม่ทำงานเมื่อเลื่อนอย่างรวดเร็วภาพจะสลับหลายครั้งเนื่องจากการใช้เซลล์ซ้ำ
Juan Boero

5

นี่เป็นเวอร์ชั่นที่รวดเร็ว (โดยใช้รหัสวัตถุประสงค์ @Nitesh Borad): -

   if let img: UIImage = UIImage(data: previewImg[indexPath.row]) {
                cell.cardPreview.image = img
            } else {
                // The image isn't cached, download the img data
                // We should perform this in a background thread
                let imgURL = NSURL(string: "webLink URL")
                let request: NSURLRequest = NSURLRequest(URL: imgURL!)
                let session = NSURLSession.sharedSession()
                let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
                    let error = error
                    let data = data
                    if error == nil {
                        // Convert the downloaded data in to a UIImage object
                        let image = UIImage(data: data!)
                        // Store the image in to our cache
                        self.previewImg[indexPath.row] = data!
                        // Update the cell
                        dispatch_async(dispatch_get_main_queue(), {
                            if let cell: YourTableViewCell = tableView.cellForRowAtIndexPath(indexPath) as? YourTableViewCell {
                                cell.cardPreview.image = image
                            }
                        })
                    } else {
                        cell.cardPreview.image = UIImage(named: "defaultImage")
                    }
                })
                task.resume()
            }

3

คำตอบที่ดีที่สุดไม่ใช่วิธีที่ถูกต้องในการทำเช่นนี้ :( จริง ๆ แล้วคุณผูก indexPath กับแบบจำลองซึ่งไม่ดีเสมอลองจินตนาการว่ามีบางแถวถูกเพิ่มระหว่างการโหลดอิมเมจขณะนี้เซลล์สำหรับ indexPath ที่ระบุมีอยู่บนหน้าจอ ไม่ถูกต้องอีกต่อไปแล้วสถานการณ์ไม่น่าเป็นไปได้และยากที่จะทำซ้ำ แต่เป็นไปได้

เป็นการดีกว่าที่จะใช้วิธี MVVM ผูกเซลล์กับ viewModel ในคอนโทรลเลอร์และโหลดอิมเมจใน viewModel (การกำหนดสัญญาณ ReactiveCocoa ด้วยวิธี switchToLatest) จากนั้นสมัครสมาชิกสัญญาณนี้และกำหนดภาพให้กับเซลล์! ;)

คุณต้องจำไว้ว่าอย่าละเมิด MVVM มุมมองต้องตายง่าย ๆ ! ในขณะที่ ViewModels ควรใช้ซ้ำได้! นี่เป็นเหตุผลว่าทำไมการผูก View (UITableViewCell) และ ViewModel ไว้ในคอนโทรลเลอร์จึงเป็นสิ่งสำคัญ


1
ใช่เส้นทางดัชนีของฉัน "ยุทธวิธีแก้ไข" (ซึ่งฉันไม่ได้แนะนำ แต่เป็นการแก้ไขที่เจียมเนื้อเจียมตัวที่สุดเพื่อแก้ไขปัญหาของ OP) ได้รับผลกระทบจากปัญหานี้ (แต่เฉพาะในกรณีที่มุมมองของตารางยังคงเพิ่ม / ลบแถว) และหากปรากฏการณ์นี้ประจักษ์เองฉันอาจแก้ไขวิธีอื่น ๆ (แทนที่จะค้นหาโดยใช้พา ธ ดัชนีเดียวกันเพียงแค่ค้นหาโมเดลสำหรับแถวที่เหมาะสม) แต่การแก้ไขทางยุทธวิธีนั้นมีปัญหาร้ายแรงมาก (ซึ่งฉันอธิบายไว้ข้างต้น) มากกว่าปัญหาที่คุณยกมาที่นี่ หากคุณใช้UIImageViewวิธีแก้ปัญหาหมวดหมู่ที่ฉันแนะนำก็จะไม่มีปัญหาเกี่ยวกับพา ธ ดัชนี
Rob

2
ฉันอาจฟังดูเชื่องช้านิดหน่อย แต่การเรียกใช้ตรรกะชนิดใดก็ได้จาก VIEW นั้นเป็นการใช้งานสถาปัตยกรรมในทางที่ผิด
badeleux

3

ในกรณีของฉันมันไม่ได้เกิดจากการแคชรูปภาพ (SDWebImage ใช้แล้ว) มันเป็นเพราะแท็กของเซลล์ที่กำหนดเองไม่ตรงกันกับ indexPath.row

บน cellForRowAtIndexPath:

1) กำหนดค่าดัชนีให้กับเซลล์ที่คุณกำหนดเอง ตัวอย่างเช่น

cell.tag = indexPath.row

2) ในเธรดหลักก่อนกำหนดรูปภาพให้ตรวจสอบว่ารูปภาพนั้นเป็นของเซลล์ที่เกี่ยวข้องหรือไม่โดยการจับคู่กับแท็ก

dispatch_async(dispatch_get_main_queue(), ^{
   if(cell.tag == indexPath.row) {
     UIImage *tmpImage = [[UIImage alloc] initWithData:imgData];
     thumbnailImageView.image = tmpImage;
   }});
});

2

ขอบคุณ "Rob" .... ฉันมีปัญหาเดียวกันกับ UICollectionView และคำตอบของคุณช่วยฉันแก้ไขปัญหาของฉัน นี่คือรหัสของฉัน:

 if ([Dict valueForKey:@"ImageURL"] != [NSNull null])
    {
        cell.coverImageView.image = nil;
        cell.coverImageView.imageURL=nil;

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

            if ([Dict valueForKey:@"ImageURL"] != [NSNull null] )
            {
                dispatch_async(dispatch_get_main_queue(), ^{

                    myCell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];

                    if (updateCell)
                    {
                        cell.coverImageView.image = nil;
                        cell.coverImageView.imageURL=nil;

                        cell.coverImageView.imageURL=[NSURL URLWithString:[Dict valueForKey:@"ImageURL"]];

                    }
                    else
                    {
                        cell.coverImageView.image = nil;
                        cell.coverImageView.imageURL=nil;
                    }


                });
            }
        });

    }
    else
    {
        cell.coverImageView.image=[UIImage imageNamed:@"default_cover.png"];
    }

สำหรับฉันmycell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];ไม่มีวันหมดดังนั้นนี่จึงไม่มีผลกระทบ
carbocation

1
คุณสามารถตรวจสอบเซลล์ของคุณสามารถมองเห็นได้หรือไม่โดย: สำหรับ (mycell * updateCell ใน collectionView.visibleCells) {cellVisible = YES; } if (cellVisible) {cell.coverImageView.imageURL = [NSURL URLWithString: [ค่า DictForKey: @ "ImageURL"]]; } มันใช้ได้กับฉันเช่นกัน
sneha

@sneha อ๋อคุณสามารถตรวจสอบเพื่อดูว่ามันมองเห็นได้โดยการทำซ้ำvisibleCellsเช่นนั้น แต่ฉันคิดว่าการใช้[collectionView cellForItemAtIndexPath:indexPath]มีประสิทธิภาพมากขึ้น
Rob

@sneha โดยวิธีการในตัวอย่างรหัสของคุณในคำตอบนี้ข้างต้นคุณตรวจสอบเพื่อดูupdateCellว่าไม่ได้nilแต่คุณไม่ได้ใช้ คุณควรใช้มันไม่เพียงเพื่อกำหนดว่าเซลล์มุมมองการรวบรวมยังคงมองเห็นได้หรือไม่ แต่คุณควรใช้updateCellภายในบล็อกนี้ไม่ใช่cell(ซึ่งอาจไม่ถูกต้องอีกต่อไป) และแน่นอนถ้าเป็นnilเช่นนั้นคุณไม่จำเป็นต้องทำอะไรเลย (เพราะมองไม่เห็นเซลล์นี้)
Rob

2
 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{
        MyCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];

        cell.poster.image = nil; // or cell.poster.image = [UIImage imageNamed:@"placeholder.png"];

        NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg", self.myJson[indexPath.row][@"movieId"]]];

        NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (data) {
                UIImage *image = [UIImage imageWithData:data];
                if (image) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        MyCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];
                        if (updateCell)
                            updateCell.poster.image = image;
                    });
                }
            }
        }];
        [task resume];

        return cell;
    }

0

ฉันคิดว่าคุณต้องการเพิ่มความเร็วในการโหลดเซลล์ของคุณในขณะโหลดรูปภาพสำหรับเซลล์ในพื้นหลัง เพื่อที่เราได้ทำตามขั้นตอนต่อไปนี้:

  1. ตรวจสอบไฟล์ที่มีอยู่ในไดเรกทอรีเอกสารหรือไม่

  2. หากไม่โหลดรูปภาพเป็นครั้งแรกและบันทึกลงในไดเรกทอรีเอกสารโทรศัพท์ของเรา หากคุณไม่ต้องการบันทึกภาพในโทรศัพท์คุณสามารถโหลดภาพเซลล์ในพื้นหลังได้โดยตรง

  3. ตอนนี้ขั้นตอนการโหลด:

เพียงรวมถึง: #import "ManabImageOperations.h"

รหัสเป็นเหมือนเซลล์ด้านล่าง:

NSString *imagestr=[NSString stringWithFormat:@"http://www.yourlink.com/%@",[dictn objectForKey:@"member_image"]];

        NSString *docDir=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0];
        NSLog(@"Doc Dir: %@",docDir);

        NSString  *pngFilePath = [NSString stringWithFormat:@"%@/%@",docDir,[dictn objectForKey:@"member_image"]];

        BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:pngFilePath];
        if (fileExists)
        {
            [cell1.memberimage setImage:[UIImage imageWithContentsOfFile:pngFilePath] forState:UIControlStateNormal];
        }
        else
        {
            [ManabImageOperations processImageDataWithURLString:imagestr andBlock:^(NSData *imageData)
             {
                 [cell1.memberimage setImage:[[UIImage alloc]initWithData: imageData] forState:UIControlStateNormal];
                [imageData writeToFile:pngFilePath atomically:YES];
             }];
}

ManabImageOperations.h:

#import <Foundation/Foundation.h>

    @interface ManabImageOperations : NSObject
    {
    }
    + (void)processImageDataWithURLString:(NSString *)urlString andBlock:(void (^)(NSData *imageData))processImage;
    @end

ManabImageOperations.m:

#import "ManabImageOperations.h"
#import <QuartzCore/QuartzCore.h>
@implementation ManabImageOperations

+ (void)processImageDataWithURLString:(NSString *)urlString andBlock:(void (^)(NSData *imageData))processImage
{
    NSURL *url = [NSURL URLWithString:urlString];

    dispatch_queue_t callerQueue = dispatch_get_main_queue();
    dispatch_queue_t downloadQueue = dispatch_queue_create("com.myapp.processsmagequeue", NULL);
    dispatch_async(downloadQueue, ^{
        NSData * imageData = [NSData dataWithContentsOfURL:url];

        dispatch_async(callerQueue, ^{
            processImage(imageData);
        });
    });
  //  downloadQueue=nil;
    dispatch_release(downloadQueue);

}
@end

โปรดตรวจสอบคำตอบและแสดงความคิดเห็นหากมีปัญหาใด ๆ เกิดขึ้น ....


0

เพียงแค่เปลี่ยน

dispatch_async(kBgQueue, ^{
     NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:   [NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]]];
     dispatch_async(dispatch_get_main_queue(), ^{
        cell.poster.image = [UIImage imageWithData:imgData];
     });
 });

เข้าไป

    dispatch_async(kBgQueue, ^{
         NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:   [NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]]];
         cell.poster.image = [UIImage imageWithData:imgData];
         dispatch_async(dispatch_get_main_queue(), ^{
            [self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
         });
     });

0

คุณสามารถส่ง URL ของคุณได้

NSURL *url = [NSURL URLWithString:@"http://www.myurl.com/1.png"];
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data,    NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (data) {
        UIImage *image = [UIImage imageWithData:data];
        if (image) {
            dispatch_async(dispatch_get_main_queue(), ^{
                    yourimageview.image = image;
            });
        }
    }
}];
[task resume];

ฉันขอทราบเหตุผลได้ไหม
User558

-1
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    Static NSString *CellIdentifier = @"Cell";
    QTStaffViewCell *cell = (QTStaffViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    If (cell == nil)
    {

        NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"QTStaffViewCell" owner:self options:nil];
        cell = [nib objectAtIndex: 0];

    }

    StaffData = [self.staffArray objectAtIndex:indexPath.row];
    NSString *title = StaffData.title;
    NSString *fName = StaffData.firstname;
    NSString *lName = StaffData.lastname;

    UIFont *FedSanDemi = [UIFont fontWithName:@"Aller" size:18];
    cell.drName.text = [NSString stringWithFormat:@"%@ %@ %@", title,fName,lName];
    [cell.drName setFont:FedSanDemi];

    UIFont *aller = [UIFont fontWithName:@"Aller" size:14];
    cell.drJob.text = StaffData.job;
    [cell.drJob setFont:aller];

    if ([StaffData.title isEqualToString:@"Dr"])
    {
        cell.drJob.frame = CGRectMake(83, 26, 227, 40);
    }
    else
    {
        cell.drJob.frame = CGRectMake(90, 26, 227, 40);

    }

    if ([StaffData.staffPhoto isKindOfClass:[NSString class]])
    {
        NSURL *url = [NSURL URLWithString:StaffData.staffPhoto];
        NSURLSession *session = [NSURLSession sharedSession];
        NSURLSessionDownloadTask *task = [session downloadTaskWithURL:url
                completionHandler:^(NSURL *location,NSURLResponse *response, NSError *error) {

      NSData *imageData = [NSData dataWithContentsOfURL:location];
      UIImage *image = [UIImage imageWithData:imageData];

      dispatch_sync(dispatch_get_main_queue(),
             ^{
                    cell.imageView.image = image;
              });
    }];
        [task resume];
    }
       return cell;}

2
การทิ้งโค้ดโดยไม่มีคำอธิบายใด ๆ นั้นไม่ค่อยมีประโยชน์ โปรดพิจารณาการแก้ไขคำตอบนี้เพื่อให้บริบท
Chris
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.