Tải hình ảnh không đồng bộ từ url bên trong một ô UITableView - hình ảnh thay đổi thành hình ảnh sai trong khi cuộn


158

Tôi đã viết hai cách để không đồng bộ tải ảnh bên trong ô UITableView của mình. Trong cả hai trường hợp, hình ảnh sẽ tải tốt nhưng khi tôi cuộn bảng, hình ảnh sẽ thay đổi một vài lần cho đến khi cuộn kết thúc và hình ảnh sẽ trở lại đúng hình ảnh. Tôi không biết tại sao điều này lại xảy ra.

#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
Bạn đang cố lưu trữ thông tin trong các tế bào thực tế. Điều này là xấu, rất xấu. Bạn nên lưu trữ thông tin trong n mảng (hoặc một cái gì đó tương tự) và sau đó hiển thị nó trong các ô. Thông tin trong trường hợp này là UIImage thực tế. Có tải nó không đồng bộ nhưng tải nó vào một mảng.
Fogmeister

1
@Fogmeister Bạn đang đề cập đến poster? Đó có lẽ là một hình ảnh trong ô tùy chỉnh của anh ấy, vì vậy những gì EXEC_BAD_ACCESS đang làm là hoàn toàn đúng. Bạn đúng rằng bạn không nên sử dụng ô làm kho lưu trữ dữ liệu mô hình, nhưng tôi không nghĩ đó là những gì anh ấy đang làm. Anh ta chỉ đưa ra tế bào tùy chỉnh những gì nó cần để trình bày. Hơn nữa, và đây là một vấn đề tinh tế hơn, tôi sẽ cảnh giác về việc lưu trữ một hình ảnh, trong mảng mô hình của bạn sao lưu bảng xem của bạn. Tốt hơn là sử dụng cơ chế lưu trữ hình ảnh và đối tượng mô hình của bạn sẽ truy xuất từ ​​bộ đệm đó.
Cướp

1
Vâng, chính xác quan điểm của tôi. Nhìn vào yêu cầu (được hiển thị đầy đủ) anh ta đang tải xuống hình ảnh không đồng bộ và đưa nó trực tiếp vào imageView trong ô. (Do đó, sử dụng ô để lưu trữ dữ liệu, tức là hình ảnh). Những gì anh ta nên làm là tham chiếu một đối tượng và yêu cầu hình ảnh từ đối tượng đó (chứa trong một mảng hoặc ở đâu đó). Nếu đối tượng chưa có hình ảnh, nó sẽ trả về một trình giữ chỗ và tải xuống hình ảnh. Sau đó, khi hình ảnh được tải xuống và sẵn sàng hiển thị, hãy cho bảng biết để nó có thể cập nhật ô (nếu nó hiển thị).
Fogmeister

1
Những gì anh ta đang làm sẽ buộc tải xuống mỗi khi anh ta di chuyển đến ô đó trong bảng. Cho dù các hình ảnh được lưu trữ liên tục là tùy thuộc vào anh ta, nhưng ít nhất là lưu trữ chúng trong thời gian cuộc sống của bàn.
Fogmeister

1
Chính xác: D Bằng cách đó, bạn chỉ cần tìm nạp hình ảnh từ URL một lần. Bạn sẽ thấy điều này trên những thứ như Facebook Friend Picker. Khi bạn bắt đầu, tất cả các avatar là giữ chỗ màu xám. Sau đó, khi bạn cuộn tất cả chúng điền vào khi nó di chuyển dọc. Nhưng sau đó khi bạn cuộn trở lại một ô được hiển thị trước đó, nó sẽ ngay lập tức hiển thị hình ảnh đã tải xuống.
Fogmeister

Câu trả lời:


230

Giả sử bạn đang tìm kiếm một bản sửa lỗi chiến thuật nhanh chóng, điều bạn cần làm là đảm bảo hình ảnh ô được khởi tạo và hàng của ô vẫn hiển thị, ví dụ:

- (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;
}

Đoạn mã trên giải quyết một vài vấn đề xuất phát từ thực tế là tế bào được sử dụng lại:

  1. Bạn không khởi tạo hình ảnh ô trước khi bắt đầu yêu cầu nền (có nghĩa là hình ảnh cuối cùng cho ô bị khử sẽ vẫn hiển thị trong khi hình ảnh mới đang tải xuống). Đảm bảo nilthuộc imagetính của bất kỳ chế độ xem hình ảnh nào nếu không bạn sẽ thấy hình ảnh nhấp nháy.

  2. Một vấn đề tinh tế hơn là trên một mạng thực sự chậm, yêu cầu không đồng bộ của bạn có thể không kết thúc trước khi ô di chuyển khỏi màn hình. Bạn có thể sử dụng UITableViewphương thức cellForRowAtIndexPath:(không bị nhầm lẫn với UITableViewDataSourcephương thức được đặt tên tương tự tableView:cellForRowAtIndexPath:) để xem liệu ô cho hàng đó có còn hiển thị hay không. Phương pháp này sẽ trở lại nilnếu ô không nhìn thấy được.

    Vấn đề là ô đã được cuộn ra khi thời gian phương thức async của bạn hoàn thành và tệ hơn là ô đã được sử dụng lại cho một hàng khác của bảng. Bằng cách kiểm tra xem hàng có còn hiển thị hay không, bạn sẽ đảm bảo rằng bạn không vô tình cập nhật hình ảnh với hình ảnh cho một hàng kể từ khi cuộn ra khỏi màn hình.

  3. Hơi không liên quan đến câu hỏi, tôi vẫn cảm thấy bắt buộc phải cập nhật điều này để tận dụng các quy ước và API hiện đại, đáng chú ý:

    • Sử dụng NSURLSessionthay vì gửi -[NSData contentsOfURL:]đến một hàng đợi nền;

    • Sử dụng dequeueReusableCellWithIdentifier:forIndexPath:thay vì dequeueReusableCellWithIdentifier:(nhưng đảm bảo sử dụng nguyên mẫu ô hoặc lớp đăng ký hoặc NIB cho mã định danh đó); và

    • Tôi đã sử dụng một tên lớp phù hợp với quy ước đặt tên ca cao (nghĩa là bắt đầu bằng chữ in hoa).

Ngay cả với những sửa chữa này, có những vấn đề:

  1. Các mã trên không lưu trữ các hình ảnh được tải xuống. Điều đó có nghĩa là nếu bạn cuộn hình ảnh khỏi màn hình và quay lại màn hình, ứng dụng có thể cố gắng truy xuất lại hình ảnh. Có lẽ bạn sẽ có đủ may mắn là tiêu đề phản ứng máy chủ của bạn sẽ cho phép bộ nhớ đệm khá minh bạch được cung cấp bởi NSURLSessionNSURLCache, nhưng nếu không, bạn sẽ được làm cho yêu cầu máy chủ không cần thiết và cung cấp một chậm hơn UX.

  2. Chúng tôi sẽ không hủy yêu cầu cho các ô cuộn màn hình. Do đó, nếu bạn nhanh chóng cuộn đến hàng thứ 100, hình ảnh cho hàng đó có thể bị tồn đọng phía sau các yêu cầu cho 99 hàng trước đó thậm chí không còn hiển thị nữa. Bạn luôn muốn đảm bảo rằng bạn ưu tiên các yêu cầu cho các ô hiển thị cho UX tốt nhất.

Cách khắc phục đơn giản nhất giải quyết các vấn đề này là sử dụng một UIImageViewdanh mục, chẳng hạn như được cung cấp với SDWebImage hoặc AFNetworking . Nếu bạn muốn, bạn có thể viết mã của riêng mình để giải quyết các vấn đề trên, nhưng đó là rất nhiều công việc và các UIImageViewdanh mục trên đã thực hiện điều này cho bạn.


1
Cảm ơn. Tôi tin rằng bạn cần phải chỉnh sửa câu trả lời của bạn. updateCell.poster.image = nilđể cell.poster.image = nil;updateCell được gọi trước khi nó được khai báo.
Segev

1
Ứng dụng của tôi sử dụng rất nhiều json vì vậy AFNetworkingchắc chắn là cách để đi. Tôi biết về nó nhưng lười sử dụng nó. Tôi chỉ đang ngưỡng mộ cách bộ đệm hoạt động với dòng mã đơn giản của chúng. [imageView setImageWithURL:<#(NSURL *)#> placeholderImage:<#(UIImage *)#>];
Segev

2
Đã thử tất cả các mục trên và SDWebImage (thực sự dừng ở đây và thậm chí không cần thử AFNetworking) và đây là sự lựa chọn tốt nhất. Cảm ơn @Rob.
mondousage

1
Khi bạn tải xong hình ảnh và cập nhật chế độ xem hình ảnh, hãy xóa chỉ báo hoạt động. Bí quyết duy nhất là bạn phải dự đoán điều gì sẽ xảy ra nếu ô di chuyển khỏi tầm nhìn trong khi hình ảnh đang được truy xuất và ô được sử dụng lại cho một hàng khác, bạn sẽ phải phát hiện sự hiện diện của bất kỳ chỉ báo hoạt động hiện có nào và xóa / cập nhật nó, không chỉ giả sử tế bào không có chỉ số hiện có.
Rob

1
Câu hỏi ban đầu là "tại sao điều này lại cellForRowAtIndexPathdẫn đến hiện tượng nhấp nháy khi tôi cuộn nhanh" và tôi đã giải thích lý do tại sao điều đó xảy ra cũng như cách khắc phục nó. Nhưng tôi tiếp tục giải thích lý do tại sao điều đó là không đủ, mô tả một vài vấn đề sâu sắc hơn và tranh luận lý do tại sao bạn nên sử dụng một trong những thư viện đó để xử lý việc này một cách duyên dáng hơn (ưu tiên các yêu cầu cho các ô hiển thị, lưu vào bộ nhớ cache để tránh mạng dư thừa yêu cầu, v.v.). Tôi không rõ những gì bạn mong đợi để trả lời cho câu hỏi "làm thế nào để tôi dừng hình ảnh nhấp nháy trong chế độ xem bảng của tôi".
Cướp

15

/ * Tôi đã thực hiện theo cách này và cũng đã thử nghiệm nó * /

Bước 1 = Đăng ký lớp ô tùy chỉnh (trong trường hợp ô nguyên mẫu trong bảng) hoặc nib (trong trường hợp ngòi tùy chỉnh cho ô tùy chỉnh) cho bảng như thế này trong phương thức viewDidLoad:

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

HOẶC LÀ

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

Bước 2 = Sử dụng phương thức "dequeueReabilitiesCellWithIdentifier: forIndexPath:" của UITableView như thế này (đối với điều này, bạn phải đăng ký lớp hoặc nib):

   - (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
Không gọi cellForRowAtIndexPath trong khối cuối cùng sẽ khiến toàn bộ điều này bị hủy bỏ lần thứ hai?
Mark Bridges

@MarkB Ink, Không. Thực ra tôi đang gọi phương thức cellForRowAtIndexPath của bảngView ở đây. Đừng nhầm lẫn với phương pháp nguồn dữ liệu của tableView có cùng tên. Nó được yêu cầu, nó có thể được gọi như [self tableView: tableView cellForRowAtIndexPath: indexPath]; Hy vọng điều này sẽ xóa sự nhầm lẫn của bạn.
Nitesh Borad

14

Có nhiều khung giải quyết vấn đề này. Chỉ cần nêu một vài tên:

Nhanh:

Mục tiêu-C:


Vui lòng thêm đề xuất của bạn nếu có các khung khác đáng xem xét.
Kean

3
Trên thực tế SDWebImagekhông giải quyết được vấn đề này. Bạn có thể kiểm soát khi hình ảnh được tải xuống, nhưng SDWebImagechỉ định hình ảnh UIImageViewmà không cần hỏi bạn về quyền để làm điều này. Về cơ bản, vấn đề từ câu hỏi vẫn chưa được giải quyết với thư viện này.
Bartłomiej Semańchot

Vấn đề từ câu hỏi là tác giả đã không kiểm tra xem tế bào có được tái sử dụng hay không. Đây là một vấn đề rất cơ bản được giải quyết bởi các khung đó, bao gồm SDWebImage.
kean

SDWebImage rất chậm kể từ iOS 8, nó là một trong những khung fav của tôi nhưng bây giờ tôi đang bắt đầu sử dụng PinRemoteImage hoạt động rất tốt.
Joan Cardona

@ BartłomiejSemańchot Bạn nói đúng, vấn đề này không được SDWebimage giải quyết
ngày

9

Swift 3

Tôi viết triển khai ánh sáng của riêng mình cho trình tải hình ảnh bằng cách sử dụng NSCache. Không có hình ảnh di động nhấp nháy!

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()
        }
    }
}

Ví dụ sử dụng

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
Tôi sẽ nói cảm ơn bạn. nhưng mã có một chút vấn đề. nếu để dữ liệu = thử? Dữ liệu (nội dungOf: url) {// vui lòng thay thế url đến vị trí. nó sẽ giúp rất nhiều người
Carl Hung

2
Với mã như hiện tại, bạn tải xuống hai lần tệp qua mạng: một lần trong downloadTaks, một lần với Dữ liệu (cntentsOf :). Bạn phải sử dụng vị trí của người dùng thay cho url vì tác vụ tải xuống chỉ đơn giản là thực sự tải xuống qua mạng và ghi dữ liệu vào một tệp tạm thời và chuyển cho bạn localUrl (vị trí trong trường hợp của bạn). Vì vậy, Dữ liệu cần trỏ đến Url cục bộ để nó chỉ đọc từ tệp.
Stéphane de Luca

Trong ví dụ sử dụng, nó có phải là "ImageCacheLoader.obtainImageWithPath (imagePath: viewModel.image) ......."?
Tim Kruger

Sẽ không hoạt động khi cuộn rất nhanh, hình ảnh sẽ hoán đổi nhiều lần vì sử dụng lại tế bào.
Juan Boero

5

Đây là phiên bản nhanh chóng (bằng cách sử dụng mã C mục tiêu @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

Câu trả lời tốt nhất không phải là cách chính xác để làm điều này :(. Bạn thực sự ràng buộc indexPath với mô hình, điều này không phải lúc nào cũng tốt. Hãy tưởng tượng rằng một số hàng đã được thêm trong khi tải hình ảnh. Bây giờ, ô cho indexPath đã cho tồn tại trên màn hình, nhưng hình ảnh không còn đúng nữa! Tình huống này khó xảy ra và khó có thể lặp lại nhưng điều đó là có thể.

Tốt hơn là sử dụng phương pháp MVVM, liên kết ô với viewModel trong bộ điều khiển và tải hình ảnh trong viewModel (gán tín hiệu ReactiveCocoa với phương thức switchToLatest), sau đó đăng ký tín hiệu này và gán hình ảnh cho ô! ;)

Bạn phải nhớ không lạm dụng MVVM. Lượt xem phải chết đơn giản! Trong khi đó ViewModels nên được tái sử dụng! Đó là lý do tại sao rất quan trọng để liên kết View (UITableViewCell) và ViewModel trong bộ điều khiển.


1
Có, đường dẫn chỉ mục của tôi là "sửa lỗi chiến thuật" (mà tôi không khuyến nghị, nhưng chỉ là chỉnh sửa khiêm tốn nhất để giải quyết vấn đề của OP) bị vấn đề này (nhưng chỉ khi chế độ xem bảng tiếp tục có thêm hàng / xóa). Và nếu hiện tượng này tự biểu hiện, tôi có thể khắc phục các cách khác (thay vì tìm kiếm bằng cách sử dụng cùng một đường dẫn chỉ mục, chỉ cần mô hình truy vấn cho hàng thích hợp). Nhưng bản sửa lỗi chiến thuật đó thậm chí còn có nhiều vấn đề nghiêm trọng hơn (mà tôi mô tả ở trên) so với vấn đề bạn nêu ra ở đây. Nếu bạn sử dụng UIImageViewgiải pháp danh mục tôi khuyên, thì không có vấn đề nào liên quan đến đường dẫn chỉ mục.
Cướp

2
Tôi nghe có vẻ hơi khoa trương, nhưng việc gọi bất kỳ loại logic nào từ VIEW là lạm dụng kiến ​​trúc này.
badeleux

3

Trong trường hợp của tôi, đó không phải là do bộ nhớ đệm hình ảnh (Được sử dụng SDWebImage). Đó là do thẻ của ô tùy chỉnh không khớp với indexPath.row.

Trên cellForRowAtIndexPath:

1) Gán một giá trị chỉ mục cho ô tùy chỉnh của bạn. Ví dụ,

cell.tag = indexPath.row

2) Trên luồng chính, trước khi gán ảnh, hãy kiểm tra xem ảnh có thuộc ô tương ứng hay không bằng cách khớp với thẻ.

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

2

Cảm ơn bạn "Rob" .... Tôi gặp vấn đề tương tự với UICollectionView và câu trả lời của bạn giúp tôi giải quyết vấn đề của mình. Đây là mã của tôi:

 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"];
    }

Đối với tôi, mycell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];không bao giờ là con số không, vì vậy điều này không có tác dụng.
carbocation

1
bạn có thể kiểm tra ô của mình có hiển thị hay không bằng cách: for (mycell * updateCell trong bộ sưu tậpView.visibleCells) {cellVisible = YES; } if (cellVisible) {cell.coverImageView.imageURL = [NSURL URLWithString: [Dict valueForKey: @ "ImageURL"]]; } Nó cũng hoạt động với tôi
sneha

@sneha Yep, bạn có thể kiểm tra xem nó có thể nhìn thấy bằng cách lặp qua visibleCellsnhư thế không, nhưng tôi nghi ngờ việc sử dụng [collectionView cellForItemAtIndexPath:indexPath]sẽ hiệu quả hơn (và đó là lý do tại sao bạn thực hiện cuộc gọi đó ngay từ đầu).
Cướp

@sneha Ngoài ra, nhân tiện, trong mẫu mã của bạn trong câu trả lời này, ở trên, bạn kiểm tra xem updateCellcó phải không nil, nhưng sau đó bạn không sử dụng nó. Bạn không chỉ sử dụng nó để xác định xem ô xem bộ sưu tập có còn hiển thị hay không, nhưng sau đó bạn nên sử dụng updateCellbên trong khối này, chứ không phải cell(có thể không còn hợp lệ nữa). Và rõ ràng, nếu đó là nil, bạn không cần phải làm gì cả (vì ô này không hiển thị).
Cướp

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

Tôi nghĩ rằng bạn muốn tăng tốc độ tải tế bào của mình tại thời điểm tải hình ảnh cho ô trong nền. Cho rằng chúng tôi đã thực hiện các bước sau:

  1. Kiểm tra tập tin có tồn tại trong thư mục tài liệu hay không.

  2. Nếu không thì tải hình ảnh lần đầu tiên và lưu nó vào thư mục tài liệu điện thoại của chúng tôi. Nếu bạn không muốn lưu hình ảnh trong điện thoại thì bạn có thể tải hình ảnh di động trực tiếp vào nền.

  3. Bây giờ quá trình tải:

Chỉ cần bao gồm: #import "ManabImageOperations.h"

Mã này giống như dưới đây cho một ô:

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

Vui lòng kiểm tra câu trả lời và nhận xét nếu có bất kỳ vấn đề xảy ra ....


0

Đơn giản chỉ cần thay đổi,

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];
     });
 });

Vào

    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

Bạn chỉ có thể vượt qua URL của bạn,

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];

Tôi có thể biết lý do?
Người dùng558

-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
Mã bãi mà không có bất kỳ lời giải thích hiếm khi hữu ích. Vui lòng xem xét chỉnh sửa câu trả lời này để cung cấp một số bối cảnh.
Chris
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.