重新加载 table 导致闪烁

Reloading table causes flickering

我有一个搜索栏,下面有一个 table 视图。当我搜索某物时,会进行网络调用并将 10 个项目添加到数组中以填充 table。当我滚动到 table 的底部时,对另外 10 个项目进行了另一个网络调用,所以现在数组中有 20 个项目......这可能会继续,因为它是一个类似于 Facebook 的新闻提要的无限滚动.

我每次进行网络调用的时候,也会在主线程上调用self.tableView.reloadData()由于每个细胞都有一个图像,您可以看到闪烁 - 细胞图像闪烁白色

我尝试实施 this solution 但我不知道将它放在我的代码中的什么位置或如何。我的代码是 Swift 也就是 Objective-C。

有什么想法吗?

更新问题 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里面使用了这个方法:

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

当我滚动到 table 的底部时,我会检测是否到达 willDisplayCell 的底部。如果是底部,那么我再次进行相同的网络调用。

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

}

这是网络调用代码。当我在搜索栏上按 Enter 时第一次调用它,然后每次我到达单元格底部时调用它,如您在 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()
        }
    }
}

尝试在调用 [tableView reloadData] 方法之前和之后更改 table alpha 值..喜欢

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

        }

我在 UIWebView 重新加载中使用了相同的方法..它对我有用。

如果只有图像闪烁白色,而其旁边的文字没有闪烁,可能是当您调用 reloadData() 时图像从源中再次下载,从而导致闪烁。在这种情况下,您可能需要将图像保存在缓存中。

我建议使用 SDWebImage 来缓存图像并异步下载。它非常简单,我在我的大部分项目中都使用它。要确认是这种情况,只需将您资产中的静态图像添加到单元格而不是调用 convertURLToImagesAsynchronouslyAndUpdateCells,您将看到它不会再次闪烁。

我不会在 Swift 中编程,但我发现它和 cell.imageView.sd_setImageWithURL(myImageURL) 一样简单。完成了!

这是使用 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 无关紧要,它可能是您的 GoogleBooksClient.getBooksFromGoogleBooks,或者您正在使用的任何数据源。