39

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.

kai Taylor
  • 785
  • 1
  • 7
  • 21
  • I don't understand the issue. Why are the empty rows at the bottom? How are you representing these empty rows internally? – Droppy Jan 23 '15 at 08:45
  • 1
    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 Jan 23 '15 at 09:00
  • @kai Taylor I had a small mistake in my code, try it now please. – KlimczakM Jan 23 '15 at 09:15
  • 1
    `cell.setTitle.text = myArray.reversed()[indexPath.row]` Reversing your array will do the trick. – Khaledonia Aug 30 '17 at 12:29

12 Answers12

41

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)
KlimczakM
  • 12,576
  • 11
  • 64
  • 83
  • @kai Taylor I had a small mistake in my code, try it now please. – KlimczakM Jan 23 '15 at 09:15
  • there also needs to be an allowance for the height of the first section header since you can't remove it? – kai Taylor Jan 23 '15 at 09:20
  • @kai Taylor Please specify, do you want to use section headers and there is a problem with it? – KlimczakM Jan 23 '15 at 09:24
  • 1
    Sorry, your answer is excellent and I am able to adjust it for my needs with and without headers. I just thought I would point out the issue for those with the default header setting using (blank header with height 20) for anyone who might come across this answer in future who may not understand why they are getting extra padding. Thanks again. – kai Taylor Jan 23 '15 at 09:29
  • 5
    Unfortuantely this solution won't work when you are using AutoLayout on your UITableViewCells which can have dynamic heights. – user3344977 Jul 19 '15 at 21:54
  • 4
    Any solution that works with auto-layout / dynamic cell heights? – Marchy Jan 05 '16 at 15:47
  • 3
    syntax and logic changed since 3.0 ? I'm getting: `Cannot call value of non-function type 'UITableView!'` at the *syntax changed* line: `tableView(self.tableView, heightForRowAt: IndexPath(item: i, section: 0))` – David Seek Apr 21 '17 at 13:25
  • @DavidSeek I had syntax error, it's already fixed. Thanks for letting me know! – KlimczakM Apr 24 '17 at 10:13
  • @KlimczakM Please can you tell how to use this? – Anirudha Mahale Jul 09 '17 at 06:30
  • 1
    @AnirudhaMahale You just need to call the `updateTableContentInset` function after everything is set up, it can be even in `viewWillAppear` method. – KlimczakM Jul 10 '17 at 07:52
  • Should This method be called every time i reload the data – Santhosh S Kashyap Sep 01 '17 at 07:07
  • I have a tableview with dynamic cells my problem is there is overscroll is there a way to fix this?? – Santhosh S Kashyap Sep 14 '17 at 07:48
  • Something is wrong with `contentInsetTop -= tableView(tableView, heightForRowAt: IndexPath(item: i, section: 0))` – Jonny Feb 16 '18 at 03:29
  • @Jonny what do you mean? Provide some details so I or someone else could help you. – KlimczakM Feb 16 '18 at 06:52
  • Actually David Seek already commented on it above. Might be a swift syntax mismatch. – Jonny Feb 16 '18 at 06:56
  • @Jonny Ok, got this, already fixed, please see updated answer, thanks for noticing. – KlimczakM Feb 16 '18 at 07:35
  • Okay. Looks like the answer below which I ended up using - using autosized cell heights. Anyway, it causes jumpy behavior, especially if user scrolls up a bit in the table view. Even avoiding to set the inset to 0 when it is already 0 does not help. It's so weird, it seems that only inspecting those values of the tableview causes it to change some on-screen UI. Specifically the table view has a tendency to jump down to last row. This is really hard to get right. – Jonny Feb 16 '18 at 07:45
  • @Jonny Seem weird, I'll check this out, maybe I'll find something. – KlimczakM Feb 16 '18 at 07:46
  • Not sure if I will find an answer but it seems that calling `rectForRow` specifically - just to READ the value - causes the tableview to recalculate/redisplay/whatever some things in the UI. Even if I comment out the set to `tableView.contentInset = xxx`. Might be an autolayout issue and I guess this answer didn't cater for that. – Jonny Feb 16 '18 at 07:58
  • `let numRows = tableView(self.tableView, numberOfRowsInSection: 0)` fails for me with `Cannot call value of non-function type 'UITableView'` but `let numRows = self.tableView.numberOfRows(inSection: 0)` works – Roman Vasilyev Feb 18 '20 at 02:24
  • 1
    @RomanVasilyev You're right - I've updated the answer. Thank you! – KlimczakM Feb 18 '20 at 05:19
  • @KlimczakM Perhaps you could add a `break` after `contentInsetTop = 0`, if you've reached zero already, there's no need to evaluate further rows. (I see the ObjC version has it already). – magma Sep 10 '20 at 04:37
32

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);
waynett
  • 337
  • 3
  • 2
28

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
    }
Akbar Khan
  • 2,215
  • 19
  • 27
4

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!

Widerberg
  • 1,118
  • 1
  • 10
  • 24
4

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.

Aaban Tariq Murtaza
  • 1,155
  • 14
  • 16
3

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.

Trung Phan
  • 923
  • 10
  • 18
1

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

Gulz
  • 1,773
  • 19
  • 15
1
     //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)
    }
  • in my case, I have multiple sections with different numbers of rows in every section and can't manage to keep the state of uitableview on reverse pagination... – Wahab Khan Jadon May 14 '22 at 18:12
1

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)
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
0

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];

    }
}
Eran Goldin
  • 980
  • 12
  • 21
0

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

Doug Amos
  • 4,243
  • 1
  • 22
  • 23
Maximo Lucosi
  • 378
  • 4
  • 9
-2

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.

Greg
  • 25,317
  • 6
  • 53
  • 62
  • 1
    This approach will give the result I showed in the third example. The order will be reversed, but will not start from the physical bottom of the table view. – kai Taylor Jan 23 '15 at 09:08