34
votes

is it possible to reverse the order of a tableView. I have searched a lot for a solution but all the results have not quite been a solution to what I am trying to achieve. They all suggest scrolling to the last position of a table with scrollToRowAtIndexPath and populating the data in reverse. But this doesn't work if the table content is dynamic and in some instances not all the cells have data. For example in a normal tableView the order is:


label 1

label 2

label 3

empty

empty

scroll direction
v
V


the desired result would be:
scroll direction
^
^

empty

empty

empty

label 3

label 2

label 1


in this example if I used the suggested method of scrollToRowAtIndexPath and use the length of the array of objects, I would only get the third cell from the top. And end up with something like this:


unwanted outcome:

label 3

label 2

label 1

empty

empty

scroll direction
v
V


any help would be great thank you.

12
I don't understand the issue. Why are the empty rows at the bottom? How are you representing these empty rows internally? - Droppy
One HACKY solution would be to flip the table view, and flip each cell in the table view. That way, you populate the table normally, and the first cell will appear at the bottom. Simplest way to do it, IMHO. - n00bProgrammer
@kai Taylor I had a small mistake in my code, try it now please. - KlimczakM
cell.setTitle.text = myArray.reversed()[indexPath.row] Reversing your array will do the trick. - Alexandros

12 Answers

39
votes

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)
26
votes

first reverse uitableview

    tableView.transform = CGAffineTransformMakeScale (1,-1);

then reverse cell in cell create.

- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

    ...

    cell.contentView.transform = CGAffineTransformMakeScale (1,-1);
17
votes

Swift 4.0 and 4.2 version

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.contentView.transform = CGAffineTransform(scaleX: 1, y: -1)
        return cell
    }
4
votes

Don't bother to write the code by yourself. Why don't you use ReverseExtension. Its very easy and will give you all required results. Please follow this url https://github.com/marty-suzuki/ReverseExtension

Note: Whenever you need to add a new cell, please insert newly added model at zeroth index of datasource array, so new cell should add at bottom. Otherwise it would add the cell at top and you would get confused again.

4
votes

The simple way is use like UILabel multiple lines + autolayout. -> UITableView should resize it base on it content(aka intrinsic layout). Create your tableview and set base class following:

class IntrinsicTableView: UITableView {

override var contentSize:CGSize {
    didSet {
        self.invalidateIntrinsicContentSize()
    }
}

override var intrinsicContentSize: CGSize {
    self.layoutIfNeeded()
    return CGSize(width: UIViewNoIntrinsicMetric, height: contentSize.height)
}

}

Now set left right and bottom layout constraints for your tableview pin to it parent view. The most important is top layout constraint should set to great than or equal 0, This condition guaranteed table will not tall than it parent view.

3
votes

Here is a refined solution of KlimczakM´s solution that works with autolayouted tableview cells (as well as the fixed ones). This solution also works with sections, section headers and section footers.

Swift 3.0:

func updateTableContentInset(forTableView tv: UITableView) {
    let numSections = tv.numberOfSections
    var contentInsetTop = tv.bounds.size.height -
        (self.navigationBar?.frame.size.height ?? 0)

    for section in 0..<numSections {
        let numRows = tv.numberOfRows(inSection: section)
        let sectionHeaderHeight = tv.rectForHeader(inSection: section).size.height
        let sectionFooterHeight = tv.rectForFooter(inSection: section).size.height
        contentInsetTop -= sectionHeaderHeight + sectionFooterHeight
        for i in 0..<numRows {
            let rowHeight = tv.rectForRow(at: IndexPath(item: i, section: section)).size.height
            contentInsetTop -= rowHeight
            if contentInsetTop <= 0 {
                contentInsetTop = 0
                break
            }
        }
        // Break outer loop as well if contentInsetTop == 0
        if contentInsetTop == 0 {
            break
        }
    }
    tv.contentInset = UIEdgeInsetsMake(contentInsetTop, 0, 0, 0)
}

NOTE:

Above code is untested but should work. Just make sure that you cope for the height of any navbar or tabbar and you'll be fine. In the code above i only do that for the navbar!

1
votes

I did in cellForRowAt method:

let reverseIndex = myArray.count-indexPath.row-1

let currCellData = myArray.object(at: reverseIndex) 

and then you continue working with currCellData

0
votes

This solution adjust the content inset as the content size changes using KVO. It also takes the content inset into account when scrolling to top as simply scrolling to CGPointZero will scroll to the top of the content instead of scrolling to the top of the table.

-(void)startObservingContentSizeChanges
{
    [self.tableView addObserver:self forKeyPath:kKeyPathContentSize options:0 context:nil];
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if([keyPath isEqualToString:kKeyPathContentSize] && object == self.tableView)
    {
        // difference between content and table heights. +1 accounts for last row separator
        CGFloat height = MAX(self.tableView.frame.size.height - self.tableView.contentSize.height, 0) + 1;

        self.tableView.contentInset = UIEdgeInsetsMake(height, 0, 0, 0);

        // "scroll" to top taking inset into account
        [self.tableView setContentOffset:CGPointMake(0, -height) animated:NO];

    }
}
0
votes

Swift 3.01 - Other solution can be, rotate and flip the tableView.

self.tableView.transform = CGAffineTransform.init(rotationAngle: (-(CGFloat)(M_PI)))
self.tableView.transform = CGAffineTransform.init(translationX: -view.frame.width, y: view.frame.height)

UITableView anchor rows to bottom

0
votes
     //Add these lines where you want to reload your tableView  
let indexpath = IndexPath(row: self.Array.count-1, section: 0)
                                self.tableView.scrollToRow(at: indexpath, at: .top, animated: true)
                                self.updateTableContentInset()
    
    
    
    //Add this function below
    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)
    }
0
votes

I you want that every new cell should appear from the bottom of the tableView, use this:-

  1. Invert the tableView: myTableView.transform = CGAffineTransform (scaleX: -1,y: -1)
  2. Invert the cells also: cell.contentView.transform = CGAffineTransform (scaleX: -1,y: -1)
  3. Now populate your tabelViewDataSource in opposite direction, like if you are using an array, then you may do like this: myTableViewData.insert(<Your New Array Element>), at: 0)
-2
votes

If you have an array of object you display in cellForRowAtIndexPath:, lets say dataArray you can reverse it, or in cellForRowAtIndexPath: you can do something like that:

NSString *yourObject = dataArray[[dataArray count] - 1 - indexPath.row];
cell.textLabel.text = yourObject

I assume you keep strings in dataArray.