in my View:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TransactionTableCell", for: indexPath) as! TransactionTableCell
let newItem = getTransactionsInSection(section: sectionHeader[indexPath.section])[indexPath.row]
cell.configure(item: newItem)
}
in my TransactionTableCell
func configure(item: TransactionModel) {
guard let withdrawalBonuses = item.withdrawalBonuses,
withdrawalBonuses < 0,
let accruedBonuses = item.accruedBonuses,
accruedBonuses > 0 else {
configureWithOneOperation(item)//shows one line of operation
return
}
//show 2 lines of operations
firstOperationAmountLabel.text = "+\(Int(accruedBonuses))"
secondOperationAmountLabel.text = "\(Int(withdrawalBonuses))"
}
When I scroll the cell , second operation line is appears in wrong cells where its shouldn't be, even If I reload my table , that also has this problem.
You should use prepareForReuse() method
Simply just clear data of your labels:
override func prepareForReuse() {
super.prepareForReuse()
firstOperationAmountLabel.text = nil
secondOperationAmountLabel.text = nil
}
There are few things to check here.
Make sure you reset all fields before configure a new cell.
If you have created a cell using xib or storyboard, make sure you haven't filled labels with static text.
Is your guard statements passing for every item?
Else block for guard configures cell with a single operation, Is it handling all ui elements in cell?
My app uses a UITableView to implement a TikTok-style UX. Each cell is the height of the entire screen. Pagination is enabled, and this works fine for the first batch of 10 records I load. Each UITableViewCell is one "page". The user can "flip" through the pages by swiping, and initially each "page" fully flips as expected. However when I add additional rows by checking to see if the currently visible cell is the last one and then loading 10 more rows, the pagination goes haywire. Swiping results in a partially "flipped" cell -- parts of two cells are visible at the same time. I've tried various things but I'm not even sure what the problem is. The tableView seems to lose track of geometry.
Note: After the pagination goes haywire I can flip all the way back to the first cell. At that point the UITableView seems to regain composure and once again I'm able to flip correctly through all of the loaded rows, including the new ones.
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// Pause the video if the cell is ended displaying
if let cell = cell as? HomeTableViewCell {
cell.pause()
}
if let indices = tableView.indexPathsForVisibleRows {
for index in indices {
if index.row >= self.data.count - 1 {
self.viewModel!.getPosts()
break
}
}
}
}
In order to create a "Tik Tok" style UX, I ended up using the Texture framework together with a cloud video provider (mux.com). Works fine now.
I was facing the same issue and as I couldn't find a solution anywhere else here's how I solved it without using Texture:
I used the UITableViewDataSourcePrefetching protocol to fetch the new data to be inserted
extension TikTokTableView: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
viewModel.prefetchRows(at: indexPaths)
}
}
prefetchRows will execute the request if the visible cell is the last one, as in my case
func prefetchRows(at indexPaths: [IndexPath]) {
if indexPaths.contains(where: isLastCell) {
getPosts(type: typeOfPosts, offset: posts.count, lastPostId: lastPost)
}
}
private func isLastCell(for indexPath: IndexPath) -> Bool {
return indexPath.row == posts.count - 1
}
I have a weak var view delegate type TikTokTableViewDelegate in my view model to have access to a function insertItems implemented by my TikTokTableView. This function is used to inform the UITableView where to insert the incoming posts at
self.posts.append(contentsOf: response.posts)
let indexPathsToReload = self.calculateIndexPathToReload(from: response.posts)
self.view?.insertItems(at: indexPathsToReload)
private func calculateIndexPathToReload(from newPosts: [Post]) -> [IndexPath] {
let startIndex = posts.count - newPosts.count
let endIndex = startIndex + newPosts.count
print(startIndex, endIndex)
return (startIndex..<endIndex).map { IndexPath(row: $0, section: 0) }
}
and this is the insertItems function implemented in TikTokTableView and here is the key: If we try to insert those rows, the pagination of the table will fail and leave that weird offset, we have to store the indexPaths in a local property and insert them once the scroll animation has finished.
extension TikTokTableView: TikTokTableViewDelegate {
func insertItems(at indexPathsToReload: [IndexPath]) {
DispatchQueue.main.async {
// if we try to insert rows in the table, the scroll animation will be stopped and the cell will have a weird offset
// that's why we keep the indexPaths and insert them on scrollViewDidEndDecelerating(:)
self.indexPathsToReload = indexPathsToReload
}
}
}
Since UITableView is a subclass of UIScrollView, we have access to scrollViewDidEndDecelerating, this func is triggered at the end of a user's scroll and this is the time when we insert the new rows
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if !indexPathsToReload.isEmpty {
tableView.insertRows(at: indexPathsToReload, with: .none)
indexPathsToReload = []
}
}
I have a UITableView used to show search results. As I type, I’m calling Tableview.reloadData(). Visually, everything works. As I begin typing, I show up to 5 matches and as I go below that, the list will show fewer items correctly. Here are the how the cells are created and number of rows reported.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "placeCell") as! PlaceCell
if shouldShowSearchResults {
let place = filteredPlaces[indexPath.row]
cell.dataSource = place
} else {
let place = allPlaces[indexPath.row]
cell.dataSource = place
}
cell.delegate = self
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if shouldShowSearchResults {
vlog?.debug("Number of FILTERED rows in PlacesTableView: \(filteredPlaces.count)")
return filteredPlaces.count
} else {
vlog?.debug("Number of unfiltered rows in PlacesTableView: \(allPlaces.count)")
return allPlaces.count
}
}
Since the PlaceCell is a custom class, here are some details of it:
// I've omitted labels, etc.
class PlaceCell: UITableViewCell {
var dataSource : PlaceView? {
didSet {
if let ds = dataSource {
self.isAccessibilityElement = true
self.accessibilityLabel = ds.getAccessibilityLabel()
} else {
self.isAccessibilityElement = true
self.accessibilityLabel = nil
}
}
}
weak var delegate : PlaceCellDelegate? = nil
override func prepareForReuse() {
self.isAccessibilityElement = false
self.accessibilityLabel = nil
super.prepareForReuse()
}
}
I began noticing a problem when UI Tests using Google's Earl Grey began failing due to multiple cells with the same Accessibility Label. Visually, I didn't understand why this was failing since there was only one cell visible that matched.
Upon inspect the views using Reveal, it seems that, as the count of cells drops below what was the maximum of 5, the old cells are still in the TableView, but hidden. So there is a hidden cell that used to be displaying the same data as is displayed by a different cell.
Any idea why this would be happening? This has worked for a number of months and I'm not sure what's changed.
It is always perilous when you traverse the view hierarchy; things can change, and perhaps that is what has happened here.
Regardless, you can make your test more robust by only selecting the visible item with the required label by using grey_sufficientlyVisible
Something like:
grey_allOf(grey_accessibilityLabel("Whole Foods Market, East Mayo Boulevard, Phoenix"), grey_sufficientlyVisible(), nil)
I have success working tableview with json parsing code. But may have 1000 more item so I need pagination when scrolling bottom side. I don't know how can I do this for my code shown below. For objective-C, there are a lot of examples but for Swift I didn't find a working example.
import UIKit
class ViewController: UIViewController, UITableViewDataSource,UITableViewDelegate {
let kSuccessTitle = "Congratulations"
let kErrorTitle = "Connection error"
let kNoticeTitle = "Notice"
let kWarningTitle = "Warning"
let kInfoTitle = "Info"
let kSubtitle = "You've just displayed this awesome Pop Up View"
#IBOutlet weak var myTableView: UITableView!
#IBOutlet weak var myActivityIndicator: UIActivityIndicatorView!
var privateList = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
loadItems()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
internal func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return privateList.count
}
internal func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell:myCell = tableView.dequeueReusableCellWithIdentifier("myCell") as! myCell
cell.titleLabel.text = privateList[indexPath.row]
return cell
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if (editingStyle == UITableViewCellEditingStyle.Delete){
print(indexPath.row)
let alert = SCLAlertView()
alert.addButton("Hayır"){ }
alert.addButton("Evet") {
self.myTableView.beginUpdates()
self.privateList.removeAtIndex(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Left)
print("Silindi")
self.myTableView.endUpdates()
self.loadItems()
}
alert.showSuccess(kSuccessTitle, subTitle: kSubtitle)
}
}
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// the cells you would like the actions to appear needs to be editable
return true
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "Detail") {
let destinationView = segue.destinationViewController as! DetailViewController
if let indexPath = myTableView.indexPathForCell(sender as! UITableViewCell) {
destinationView.privateLista = privateList[indexPath.row]
}
}
}
internal func tableView(tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat
{
return 0.0
}
func loadItems()
{
loadItemsNow("privateList")
}
func loadItemsNow(listType:String){
myActivityIndicator.startAnimating()
let listUrlString = "http://bla.com/json2.php?listType=" + listType + "&t=" + NSUUID().UUIDString
let myUrl = NSURL(string: listUrlString);
let request = NSMutableURLRequest(URL:myUrl!);
request.HTTPMethod = "GET";
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
print(error!.localizedDescription)
dispatch_async(dispatch_get_main_queue(),{
self.myActivityIndicator.stopAnimating()
})
return
}
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as? NSArray
if let parseJSON = json {
self.privateList = parseJSON as! [String]
}
} catch {
print(error)
}
dispatch_async(dispatch_get_main_queue(),{
self.myActivityIndicator.stopAnimating()
self.myTableView.reloadData()
})
}
task.resume()
}
}
For that you need to have server side change also.
Server will accept fromIndex and batchSize in the API url as query param.
let listUrlString = "http://bla.com/json2.php?listType=" + listType + "&t=" + NSUUID().UUIDString + "&batchSize=" + batchSize + "&fromIndex=" + fromIndex
In the server response, there will be an extra key totalItems. This will be used to identify all items are received or not. An array or items fromIndex to batchSize number of items.
In the app side
First loadItem() will be called with fromIndex = 0 and batchSize = 20 (for example in viewDidLoad() or viewWillAppear). removeAll items from privateList array before calling loadItem() for the first time
Server returns an array of first 20 items and totalItems total number of items in the server.
Append the 20 items in privateList array and reload tableView
In tableView:cellForRowAtIndexPath method check if the cell is the last cell. And check if totalItems (form server) is greater than privateList.count. That means there are more items in the server to load
if indexPath.row == privateList.count - 1 { // last cell
if totalItems > privateList.count { // more items to fetch
loadItem() // increment `fromIndex` by 20 before server call
}
}
Question: where is refresh ? will be scrolling ?
Refresh after appending new items in the array when server response received. (step 3)
Scrolling will trigger tableView:cellForRowAtIndexPath for every cell when user scrolls. Code is checking if it is the last cell and fetch remaining items. (step 4)
Sample project added: https://github.com/rishi420/TableViewPaging
SWIFT 3.0 and 4.0
If you're sending the page number in the API request then this is the ideal way for implementing pagination in your app.
declare the variable current Page with initial Value 0 and a bool to check if any list is being loaded with initial value false
var currentPage : Int = 0
var isLoadingList : Bool = false
This is the function that gets the list example:
func getListFromServer(_ pageNumber: Int){
self.isLoadingList = false
self.table.reloadData()
}
This is the function that increments page number and calls the API function
func loadMoreItemsForList(){
currentPage += 1
getListFromServer(currentPage)
}
this is the method that will be called when the scrollView scrolls
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (((scrollView.contentOffset.y + scrollView.frame.size.height) > scrollView.contentSize.height ) && !isLoadingList){
self.isLoadingList = true
self.loadMoreItemsForList()
}
}
P.S. the bool isLoadingList role is to prevent the scroll view from getting more lists in one drag to the bottom of the table view.
The good and efficient way to do it is by using scrollviewDelegate in tableview
Just add UIScrollViewDelegate in your viewController
In view controller
//For Pagination
var isDataLoading:Bool=false
var pageNo:Int=0
var limit:Int=20
var offset:Int=0 //pageNo*limit
var didEndReached:Bool=false
viewDidLoad(_){
tableview.delegate=self //To enable scrollviewdelegate
}
Override two methods from this delegate
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
print("scrollViewWillBeginDragging")
isDataLoading = false
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
print("scrollViewDidEndDecelerating")
}
//Pagination
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
print("scrollViewDidEndDragging")
if ((tableView.contentOffset.y + tableView.frame.size.height) >= tableView.contentSize.height)
{
if !isDataLoading{
isDataLoading = true
self.pageNo=self.pageNo+1
self.limit=self.limit+10
self.offset=self.limit * self.pageNo
loadCallLogData(offset: self.offset, limit: self.limit)
}
}
}
This is now a little bit easier with the addition of a new protocol in iOS10: UITableViewDataSourcePrefetching
https://developer.apple.com/documentation/uikit/uitableviewdatasourceprefetching
//It works fine
func getPageCount(TotalCount : Int) -> Int{
var num = TotalCount
let reminder = num % 50
print(reminder)
if reminder != 0{
num = TotalCount/50
num = num + 1
}else{
num = TotalCount/50
}
return num
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let TotalPage = self.getPageCount(TotalCount: Int(Datacount)!)
let lastItem = self.mainArr.count - 1
if indexPath.row == lastItem {
print("IndexRow\(indexPath.row)")
if self.page < TotalPage-1 {
self.view_Loader.isHidden = false
self.view_LoaderHeight.constant = 50
self.page += 1
self.YourAPI()
}
}
}`
By using UITableViewDelegate, u can call the function
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let lastItem = self.mes.count - 1
if indexPath.row == lastItem {
print("IndexRow\(indexPath.row)")
if currentPage < totalPage {
currentPage += 1
//Get data from Server
}
}
}
I needed something similar on a project and my solution was:
1 - create a variable numberOfObjectsInSubArray (initial value 30 or whatever you want)
2 - create a subarray to add a number of objects from your privateList array every time i tap "show more"
let subArray = privateList?.subarrayWithRange(NSMakeRange(0, numberOfObjectsInSubArray))
And use it on
internal func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return subArray.count
}
3- Whenever you need to show more objects, do:
func addMoreObjectsOnTableView () {
numberOfObjectsInSubArray += 30
if (numberOfObjectsInSubArray < privateList.count) {
subArray = privateList?.subarrayWithRange(NSMakeRange(0, numberOfObjectsInSubArray))
} else {
subArray = privateList?.subarrayWithRange(NSMakeRange(0, privateList.count))
}
tableView.reloadData()
}
I hope it helps
I've tried an approach with willDisplayCell. But it produces unwanted stops during scrolling which makes the user experience not good.
I think a better way is to do it in scrollViewDidEndDecelerating delegate method. It calls when the scroll finishes and only then new data comes. User sees that there is new content and scroll again if he wants. I've taken the answer here but instead of scrollViewDidEndDragging I use scrollViewDidEndDecelerating. It looks just better in my case. Here is some code from my project.
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
guard scrollView == tableView,
(scrollView.contentOffset.y + scrollView.frame.size.height) >= scrollView.contentSize.height,
!viewModel.isLastPeriodicsPage else { return }
viewModel.paginatePeriodics(tableView.getLastIndexPath())
}
Another way of doing this is: You may set a threshold for getting elements while sending request each time:
Lets say you you are fetching 20 elements first time. You will be saving last fetched record id or number for getting list of next 20 elements.
let lastFetchedIndex = 20;
I am assuming that you have already added these records in your myArray. MyArray is the dataSource of tableView. Now myArray is containing 40 objects. I am going to make a list of indexPaths of rows that needs to be inserted in tableView now.
var indexPathsArray = [NSIndexPath]()
for index in lastFetchedIndex..<myArray.count{
let indexPath = NSIndexPath(forRow: index, inSection: 0)
indexPathsArray.append(indexPath)
}
Here I am updating my tableView. Make sure your dataSource i mean your myArray has already been updated. So that it may insert rows properly.
self.tableView.beginUpdates()
tableView!.insertRowsAtIndexPaths(indexPathsArray, withRowAnimation: .Fade)
self.tableView.endUpdates()
Add another section to your tableview, let this section have only 1 row which will be a cell containing an activity indicator, to denote loading.
internal func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
return 2;
}
internal func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if section == 0 {
return privateList.count
} else if section == 1 { // this is going to be the last section with just 1 cell which will show the loading indicator
return 1
}
}
internal func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
if section == 0 {
let cell:myCell = tableView.dequeueReusableCellWithIdentifier("myCell") as! myCell
cell.titleLabel.text = privateList[indexPath.row]
return cell
} else if section == 1 {
//create the cell to show loading indicator
...
//here we call loadItems so that there is an indication that something is loading and once loaded we relaod the tableview
self.loadItems()
}
}
here is a sample code for collection view :
var page = 0
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
print("page Num:\(page)")
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath){
if arrImagesData.count-1 == indexPath.row && arrImagesData.count%10 == 0{
getMoreImages(page)
}
}
func getMoreImages(page:Int){
//hit api
if api_success == true {
if self.page == 0 {
self.arrImagesData.removeAll()
}
self.arrImagesData.appendContentsOf(api_data)
self.collectionImages.reloadData()
self.page = self.page + 1
}
}
API handler is api handler for network call that just do POST and GET calls. getNotifications is basically just a post call with params( offset and pageSize ) and in response there is list.
Main logic is changing offset depending on cell in willDisplay collectionView delegate. Comment if you having any question , happy to help.
var isFetching: Bool = false
var offset = 0
var totalListOnServerCount = 20 // it must be returned from server
var pageSize = 10 // get 10 objects for instance
// MARK: - API Handler
private func fetchNotifications(){
// return from function if already fetching list
guard !isFetching else {return}
if offset == 0{
// empty list for first call i.e offset = 0
self.anyList.removeAll()
self.collectionView.reloadData()
}
isFetching = true
// API call to fetch notifications with given offset or page number depends on server logic just simple POST Call
APIHandler.shared.getNotifications(offset: offset) {[weak self] (response, error) in
if let response = response {
self?.isFetching = false
if self?.offset == 0{
// fetch response from server for first fetch
self?.notificationsResponse = response
if self?.refreshControl.isRefreshing ?? false {
self?.refreshControl.endRefreshing()
}
}else{
// append if already exist ( pagination )
self?.notificationsResponse?.notifications.append(contentsOf: response.notifications)
}
self?.collectionView.reloadData()
}
}
}
// MARK: - Collection View Delegate
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
guard let anyList = responseFromServer else { return }
// check if scroll reach last index available and keep fetching till our model list has all entries from server
if indexPath.item == anyList.count - 1 && anyList.count < totalListOnServerCount{
offset += pageSize
fetchNotifications()
}
}
Made a General purpouse pagination framework: 🎉
https://github.com/eonist/PaginationTable
let table = Table(rowData: [], frame: .zero, style: .plain)
view = table
table.isFetching = true
Table.fetchData(range: table.paginationRange) { rowItem in
DispatchQueue.main.async { [weak table] in
table?.rowData += rowItem
table?.reloadData()
table?.paginationIndex += Table.paginationAmount // set the new pagination index
table?.isFetching = false
}
}
Swift 5 (Full comprehensive pagination solution)
The UI code:
https://github.com/eonist/PaginationTable
The Data Model code:
https://github.com/eonist/PaginationService
Core components:
rowData: This array will grow on each scroll-ended-event until it has loaded all items from backend-API
paginationAmount: The amount to fetch on each pagination cycle
paginationIndex: The current amount of cells (this grows as you load more data
isFetching: A boolean that lets the code know if data is already loading or not, to avoid double fetching etc
fetchData: Simulates getting data from remote-api
Gotchas:
The example code is not reliant on a backend. It simply tests with data from a file and simulates network calls by sleeping for some seconds
The example uses some dependencies in order to speed up the creation of this example. But its basic stuff like AFNetwork, Json parsing, Autollayout. All of which could easily be substituted
Requirements:
Backend-API that can provide the count of items
Backend-API that can return items for a range (startIndex, endIndex)
Hello I’ve been having this problem for awhile. I want to stop the tableview from reusing the cell. It keeps displaying the wrong information when i scroll then it shows the right thing like a few milliseconds. How can i stop the tableview from reusing the cell or how can i reuse the cell and make it not do that.
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cats.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "CategoryTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! CategoryTableViewCell
cell.nameLabel.text = cats[indexPath.row].categoryName
cell.subNameLabel.text = cats[indexPath.row].appShortDesc
let catImageUrl = cats[indexPath.row].imageUrl
let url = NSURL(string: "https:\(catImageUrl)")
let urlRequest = NSURLRequest(URL: url!)
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue()) { (response, data, error) -> Void in
if error != nil {
print(error)
} else {
if let ass = UIImage(data: data!) {
cell.photoImageView.image = ass
}
self.loading.stopAnimating()
}
}
return cell
}
The problem is that you are seeing an image from a previous cell. Simply initialize the image to nil when you dequeue the reused cell:
cell.photoImageView.image = nil
or set it to a default image of your choosing.
Note, the way you are updating the image after it loads has issues.
The row may no longer be on screen when the image finally loads, so you will be updating a cell that has been reused itself.
The update should be done on the main thread.
A better way to do this would be to have an array that caches the images for the cells. Load the image into the array, and then tell the tableView to reload that row.
Something like this:
dispatch_async(dispatch_get_main_queue()) {
self.imageCache[row] = ass
self.tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: row, inSection: 0)],
withRowAnimation: .None)
}
override prepareForReuse() method in your cell class and reset your values
override func prepareForReuse() {
super.prepareForReuse()
nameLabel.text = ""
}
This method is called every time the UITableView before reuses this cell