Reloading table causes flickering

2019-07-20 18:40发布

I have a search bar and a table view under it. When I search for something a network call is made and 10 items are added to an array to populate the table. When I scroll to the bottom of the table, another network call is made for another 10 items, so now there is 20 items in the array... this could go on because it's an infinite scroll similar to Facebook's news feed.

Every time I make a network call, I also call self.tableView.reloadData() on the main thread. Since each cell has an image, you can see flickering - the cell images flash white.

I tried implementing this solution but I don't know where to put it in my code or how to. My code is Swift and that is Objective-C.

Any thoughts?

Update To Question 1

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier(R.reuseIdentifier.searchCell.identifier, forIndexPath: indexPath) as! CustomTableViewCell

    let book = booksArrayFromNetworkCall[indexPath.row]

    // Set dynamic text
    cell.titleLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
    cell.authorsLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleFootnote)

    // Update title
    cell.titleLabel.text = book.title

    // Update authors
    cell.authorsLabel.text = book.authors

    /*
    - Getting the CoverImage is done asynchronously to stop choppiness of tableview.
    - I also added the Title and Author inside of this call, even though it is not
    necessary because there was a problem if it was outside: the first time a user
    presses Search, the call for the CoverImage was too slow and only the Title
    and Author were displaying.
    */
    Book.convertURLToImagesAsynchronouslyAndUpdateCells(book, cell: cell, task: task)

    return cell
}

cellForRowAtIndexPath uses this method inside it:

    class func convertURLToImagesAsynchronouslyAndUpdateCells(bookObject: Book, cell: CustomTableViewCell, var task: NSURLSessionDataTask?) {

    guard let coverImageURLString = bookObject.coverImageURLString, url = NSURL(string: coverImageURLString) else {
        return
    }

    // Asynchronous work being done here.
    task = NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: { (data, response, error) -> Void in

        dispatch_async(dispatch_get_main_queue(), {

            // Update cover image with data

            guard let data = data else {
                return
            }

            // Create an image object from our data
            let coverImage = UIImage(data: data)
            cell.coverImageView.image = coverImage
        })
    })

    task?.resume()
}

When I scroll to the bottom of the table, I detect if I reach the bottom with willDisplayCell. If it is the bottom, then I make the same network call again.

    override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {

    if indexPath.row+1 == booksArrayFromNetworkCall.count {

        // Make network calls when we scroll to the bottom of the table.
        refreshItems(currentIndexCount)
    }

}

This is the network call code. It is called for the first time when I press Enter on the search bar, then it is called everytime I reach the bottom of the cell as you can see in willDisplayCell.

    func refreshItems(index: Int) {

    // Make to network call to Google Books
    GoogleBooksClient.getBooksFromGoogleBooks(self.searchBar.text!, startIndex: index) { (books, error) -> Void in

        guard let books = books else {
            return
        }

        self.footerView.hidden = false

        self.currentIndexCount += 10

        self.booksArrayFromNetworkCall += books

        dispatch_async(dispatch_get_main_queue()) {
            self.tableView.reloadData()
        }
    }
}

3条回答
乱世女痞
2楼-- · 2019-07-20 18:50

Here's an example of infinite scroll using insertRowsAtIndexPaths(_:withRowAnimation:)

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    @IBOutlet weak var tableView: UITableView!

    var dataSource = [String]()
    var currentStartIndex = 0

    // We use this to only fire one fetch request (not multiple) when we scroll to the bottom.
    var isLoading = false

    override func viewDidLoad() {
        super.viewDidLoad()

        // Load the first batch of items.
        loadNextItems()
    }

    // Loads the next 20 items using the current start index to know from where to start the next fetch.
    func loadNextItems() {

        MyFakeDataSource().fetchItems(currentStartIndex, callback: { fetchedItems in

            self.dataSource += fetchedItems // Append the fetched items to the existing items.

            self.tableView.beginUpdates()

            var indexPathsToInsert = [NSIndexPath]()
            for i in self.currentStartIndex..<self.currentStartIndex + 20 {
                indexPathsToInsert.append(NSIndexPath(forRow: i, inSection: 0))
            }
            self.tableView.insertRowsAtIndexPaths(indexPathsToInsert, withRowAnimation: .Bottom)
            self.tableView.endUpdates()

            self.isLoading = false

            // The currentStartIndex must point to next index.
            self.currentStartIndex = self.dataSource.count
        })
    }

    // #MARK: - Table View Data Source Methods

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataSource.count
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = UITableViewCell()
        cell.textLabel!.text = dataSource[indexPath.row]
        return cell
    }

    // #MARK: - Table View Delegate Methods

    func scrollViewDidScroll(scrollView: UIScrollView) {
        if isLoading == false && scrollView.contentOffset.y + scrollView.bounds.size.height > scrollView.contentSize.height {
            isLoading = true
            loadNextItems()
        }
    }
}

MyFakeDataSource is irrelevant, it's could be your GoogleBooksClient.getBooksFromGoogleBooks, or whatever data source you're using.

查看更多
何必那么认真
3楼-- · 2019-07-20 18:57

Try to change table alpha value before and after calling [tableView reloadData] method..Like

dispatch_async(dispatch_get_main_queue()) {
            self.aTable.alpha = 0.4f;
            self.tableView.reloadData()
            [self.aTable.alpha = 1.0f;

        }

I have used same approach in UIWebView reloading..its worked for me.

查看更多
Anthone
4楼-- · 2019-07-20 19:02

If only the image flash white, and the text next to it doesn't, maybe when you call reloadData() the image is downloaded again from the source, which causes the flash. In this case you may need to save the images in cache.

I would recommend to use SDWebImage to cache images and download asynchronously. It is very simple and I use it in most of my projects. To confirm that this is the case, just add a static image from your assets to the cell instead of calling convertURLToImagesAsynchronouslyAndUpdateCells, and you will see that it will not flash again.

I dont' program in Swift but I see it is as simple as cell.imageView.sd_setImageWithURL(myImageURL). And it's done!

查看更多
登录 后发表回答