UITableViewCell asynchronously loading images issue - Swift

后端 未结 1 866
时光说笑
时光说笑 2021-02-10 18:03

In my app, I built my own asynchronous image loading class. I pass in a object, then it checks if the cache (NSCache) has the image, if not it will then check the file system if

相关标签:
1条回答
  • 2021-02-10 18:40

    Table views reuse cells to save memory, which can cause problems with any async routines that need to be performed to display the cell's data (like loading an image). If the cell is supposed to be displaying different data when the async operation completes, the app can suddenly go into an inconsistent display state.

    To get around this, I recommend adding a generation property to your cells, and checking that property when the async operation completes:

    protocol MyImageManager {
        static var sharedManager: MyImageManager { get }
        func getImageForUrl(url: String, completion: (UIImage?, NSError?) -> Void)
    }
    
    struct MyCellData {
        let url: String
    }
    
    class MyTableViewCell: UITableViewCell {
    
        // The generation will tell us which iteration of the cell we're working with
        var generation: Int = 0
    
        override func prepareForReuse() {
            super.prepareForReuse()
            // Increment the generation when the cell is recycled
            self.generation++
            self.data = nil
        }
    
        var data: MyCellData? {
            didSet {
                // Reset the display state
                self.imageView?.image = nil
                self.imageView?.alpha = 0
                if let data = self.data {
                    // Remember what generation the cell is on
                    var generation = self.generation
                    // In case the image retrieval takes a long time and the cell should be destroyed because the user navigates away, make a weak reference
                    weak var wcell = self
                    // Retrieve the image from the server (or from the local cache)
                    MyImageManager.sharedManager.getImageForUrl(data.url, completion: { (image, error) -> Void in
                        if let error = error {
                            println("There was a problem fetching the image")
                        } else if let cell = wcell, image = image where cell.generation == generation {
                            // Make sure that UI updates happen on main thread
                            dispatch_async(dispatch_get_main_queue(), { () -> Void in
                                // Only update the cell if the generation value matches what it was prior to fetching the image
                                cell.imageView?.image = image
                                cell.imageView?.alpha = 0
                                UIView.animateWithDuration(0.25, animations: { () -> Void in
                                    cell.imageView?.alpha = 1
                                })
                            })
                        }
                    })
                }
            }
        }
    }
    
    class MyTableViewController: UITableViewController {
    
        var rows: [MyCellData] = []
    
        override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
            var cell = tableView.dequeueReusableCellWithIdentifier("Identifier") as! MyTableViewCell
            cell.data = self.rows[indexPath.row]
            return cell
        }
    
    }
    

    A couple other notes:

    • Don't forget to do your display updates on the main thread. Updating on a network activity thread can cause the display to change at a seemingly random time (or never)
    • Be sure to weakly reference the cell (or any other UI elements) when you're performing an async operation in case the UI should be destroyed before the async op completes.
    0 讨论(0)
提交回复
热议问题