重新加载 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
,或者您正在使用的任何数据源。
我有一个搜索栏,下面有一个 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
,或者您正在使用的任何数据源。