How to Populate Uitableview from the Bottom Upwards

How to populate UITableView from the bottom upwards?

To populate UITableView from the bottom:

- (void)updateTableContentInset {
NSInteger numRows = [self.tableView numberOfRowsInSection:0];
CGFloat contentInsetTop = self.tableView.bounds.size.height;
for (NSInteger i = 0; i < numRows; i++) {
contentInsetTop -= [self tableView:self.tableView heightForRowAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
if (contentInsetTop <= 0) {
contentInsetTop = 0;
break;
}
}
self.tableView.contentInset = UIEdgeInsetsMake(contentInsetTop, 0, 0, 0);
}

To reverse the order of elements:

dataSourceArray = dataSourceArray.reverseObjectEnumerator.allObjects;

Swift 4.2/5 version:

func updateTableContentInset() {
let numRows = self.tableView.numberOfRows(inSection: 0)
var contentInsetTop = self.tableView.bounds.size.height
for i in 0..<numRows {
let rowRect = self.tableView.rectForRow(at: IndexPath(item: i, section: 0))
contentInsetTop -= rowRect.size.height
if contentInsetTop <= 0 {
contentInsetTop = 0
break
}
}
self.tableView.contentInset = UIEdgeInsets(top: contentInsetTop,left: 0,bottom: 0,right: 0)
}

Swift 3/4.0 version:

self.tableView.contentInset = UIEdgeInsetsMake(contentInsetTop, 0, 0, 0)

Load tableview from bottom, scroll up (reverse tableview) (iOS)

The best way was to flip the tableView and it's cells. This way, if a cell were to change size (due to things like asynchronous downloading), you will still load from the bottom of the tableview than at an offset.

//In ViewDidLoad
conversationTableView.transform = CGAffineTransform(rotationAngle: -(CGFloat)(Double.pi));

//In cellForRowAtIndexPath
cell.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi));

Also: If you have a headerView, simply set it to the tableView.tableFooterView:

var tableHeaderView = UIView(frame: headerFrame)
tableHeaderView.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi));
conversationTableView.tableFooterView = tableHeaderView

And if you have a footerView and headerView, just set the header to the footer, and the footer to the header.

Edit:
If you want to flip the scroll indicator:

conversationTableView.scrollIndicatorInsets = UIEdgeInsetsMake(0.0, 0.0, 0.0, conversationTableView.bounds.size.width - 8.0)

swift start tableView from bottom (reverse tableView)

Just scroll to the end and do the same whenever you insert a cell.

tableView.scrollToRowAtIndexPath(bottomIndexPath, atScrollPosition: .Bottom,
animated: true)

Load UITableView from the bottom

You can avoid the call from viewDidLoad because scrolling from within viewDidAppear makes that first call redundant. viewDidAppear is called every time you navigate back to the view but viewDidLoad is only called once when the view is initialized.

I would agree with earlier suggestions of hiding the scroll from the user instead of changing the way a UITableView is loading data. My suggestion would be to use the scrollToRowAtIndexPath method in the viewWillAppear method with animation set to NO. After that if you have to add a new row while the table is visible to the user, use insertRowsAtIndexPaths:withRowAnimation: to add a row at the bottom of the table view. Be sure to take care of adding the data at the end of your data model so that when the user navigates away and comes back, s/he comes back to the same layout.

Hope this helps.

edit:
Just saw your reason for not accepting the previous answers and thought I'd elaborate a little more. The solution I propose would require minimum effort, avoid calling reloadData time and again and thus avoid calling the scrollToRowAtIndexPath method again and again. You only need to make one call to scrollToRowAtIndexPath in viewWillAppear to scroll to the bottom of the table view (hiding the transition from the user when doing so) and you wouldn't need to do that again.

UITableView anchor rows to bottom

I've got a solution that works for me perfectly, but it causes a bunch of double thinking so it's not as simple in theory as it is in practice... kinda...

Step 1, apply a transform to the table view rotating it 180deg

tableView.transform = CGAffineTransformMakeRotation(-M_PI);

Step 2, rotate your raw cell 180deg in tableView:cellForRowAtIndexPath:

cell.transform = CGAffineTransformMakeRotation(M_PI);

Step 3, reverse your datasource. If you're using an NSMutableArray insert new objects at location 0 instead of using AddObject...

Now, the hard part is remembering that left is right and right is left only at the table level, so if you use

[tableView insertRowsAtIndexPaths:targetPath withRowAnimation:UITableViewRowAnimationLeft]

it now has to be

[tableView insertRowsAtIndexPaths:targetPath withRowAnimation:UITableViewRowAnimationRight]

and same for deletes, etc.

Depending on what your data store is you may have to handle that in reverse order as well...

Note: rotate the cells OPPOSITE the table, otherwise floating point innacuracy might cause the transform to get off perfect and you'll get crawlies on some graphics from time to time as you scroll... minor but annoying.

How to transform Delete option in Tableview cell

First reverse UITableView in viewDidLoad

override func viewDidLoad() {
super.viewDidLoad()
tableView.transform = CGAffineTransform(scaleX: 1, y: -1)
}

Then reverse the cell in cellForRowAt.

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "MyTableViewCell", for: indexPath) as? MyTableViewCell else { fatalError() }

cell.transform = CGAffineTransform(scaleX: 1, y: -1)
cell.contentView.transform = CGAffineTransform(scaleX: 1, y: -1)

return cell
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let delete = UITableViewRowAction(style: .default, title: nil) { (action, indexPath) in
// delete item at indexPath
}

let label = UILabel(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 75, height: tableView.rectForRow(at: indexPath).height)))
label.numberOfLines = 0
label.textAlignment = .center
label.textColor = UIColor.white
label.backgroundColor = UIColor.red
label.text = "Delete"
UIGraphicsBeginImageContextWithOptions(label.bounds.size, false, 0.0)
label.layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

if let cgImage = image?.cgImage {
let rotada3 = UIImage(cgImage: cgImage, scale: image!.scale, orientation: .downMirrored)
delete.backgroundColor = UIColor(patternImage: rotada3)

}

return [delete]
}

Here on editActionsForRowAt I did a work around to print image with mirrored text

How to return to main viewcontroller from bottom uitableview

Assuming you have a navigation controller to "drill down" and that your table is in the root view controller, how about UINavigationController's popToRootViewControllerAnimated method?

UITableView powered by FetchedResultsController with UITableViewAutomaticDimension - Cells move when table is reloaded

It happens because of the following sequence:

  1. UITableView initialized and showing 5 cells. Height of each of that cells is known to UITableView. It asks its delegate for exact height before displaying each cell by calling a method -tableView:heightForRowAtIndexPath:.
  2. UITableView scrolled exactly 3 cells from top. Heights of this cells are known to be exactly [60, 70, 90] = 220 summarily. UITableView's contentOffset.y is now 220.
  3. UITableView gets reloaded. It purges all its knowledge about cells. It now still knows its contentOffset.y which is 220.
  4. UITableView asking its data source about general metrics - number of sections and number of rows in each section.
  5. UITableView now beginning to fill its contents. First it needs to know size of its contents to correctly size and position its scroll indicators. It also needs to know which objects - table header, section headers, rows, section footers and table footer - it should display according to its current bounds, which position is also represented by contentOffset. To begin placing that visible objects it first needs to skip objects that falls in invisible vertical range of [0…220].

    1. If you haven't provided values for any of estimated… properties and haven't implemented any of tableViewController:estimated…methods then UITableView asks its delegate about exact height of headers, footers and rows by calling appropriate delegate methods such as -tableView:heightForRowAtIndexPath:. And if your delegate reports the same number of objects and the same heights for them as before reload, then you will not see any visual changes to position and size of any table elements. Downside of this "strait" behavior became obvious when your table should display large number of rows, lets say 50000. UITableView asks its delegate about height of each of this 50000 rows, and you have to calculate it yourself by measuring your text for each corresponding object, or when using UITableViewAutomaticDimension UITableView doing the same measuring itself, asking its delegate for cells filled with text. Believe me, it's slow. Each reload will cause a few seconds of interface freeze.
    2. If you have supplied UITableView with estimated heights, then it will ask its delegate only for heights of currently visible objects. Objects in vertical range of [0…220] are counted by using values provided in estimatedRowHeight or -tableView:estimatedHeightForRowAtIndexPath: for rows and by corresponding methods for section headers and footers. By setting estimatedRowHeight to 60, you telling UITableView to skip three rows (60 * 3 = 180) and to place row 4 with offset of -40 from top visible edge. Hence visual "jump" by 40 pixels up.

A "right" solution here would be not to call reloadData. Reload rows only for changed objects instead, use -reloadRowsAtIndexPaths:withRowAnimation:. In case of NSFetchedResultsController + UITableView use this classic scheme.



Related Topics



Leave a reply



Submit