Get notified when UITableView has finished asking for data?
Asked Answered
T

18

110

Is there some way to find out when a UITableView has finished asking for data from its data source?

None of the viewDidLoad/viewWillAppear/viewDidAppear methods of the associated view controller (UITableViewController) are of use here, as they all fire too early. None of them (entirely understandably) guarantee that queries to the data source have finished for the time being (eg, until the view is scrolled).

One workaround I have found is to call reloadData in viewDidAppear, since, when reloadData returns, the table view is guaranteed to have finished querying the data source as much as it needs to for the time being.

However, this seems rather nasty, as I assume it is causing the data source to be asked for the same information twice (once automatically, and once because of the reloadData call) when it is first loaded.

The reason I want to do this at all is that I want to preserve the scroll position of the UITableView - but right down to the pixel level, not just to the nearest row.

When restoring the scroll position (using scrollRectToVisible:animated:), I need the table view to already have sufficient data in it, or else the scrollRectToVisible:animated: method call does nothing (which is what happens if you place the call on its own in any of viewDidLoad, viewWillAppear or viewDidAppear).

Tambourine answered 27/9, 2009 at 13:29 Comment(2)
It seems that you are looking something similar to this https://mcmap.net/q/23859/-how-to-detect-the-end-of-loading-of-uitableview .Displode
In my experience, UITableView can cache calls to reloadData, just like it caches calls to insert/delete rows and other things. UITableView will call the data source delegate when it gets ready, depending on cpu usage and other things.Brassiere
F
61

This answer doesn't seem to be working anymore, due to some changes made to UITableView implementation since the answer was written. See this comment : Get notified when UITableView has finished asking for data?

I've been playing with this problem for a couple of days and think that subclassing UITableView's reloadData is the best approach :

- (void)reloadData {

    NSLog(@"BEGIN reloadData");

    [super reloadData];

    NSLog(@"END reloadData");

}

reloadData doesn't end before the table has finish reload its data. So, when the second NSLog is fired, the table view has actually finish asking for data.

I've subclassed UITableView to send methods to the delegate before and after reloadData. It works like a charm.

Fretted answered 17/6, 2010 at 9:1 Comment(10)
Gotta say, this seems to be the best solution. Just implemented it and, like you say, it works like a charm. Thanks!Philodendron
Good news ! Could please you answer this question with your solution ? I'm very interested...Fretted
I have subclassed UITableView, but unfortunately my delegate method is being called BEFORE cellForRowAtIndexPath. I want my delegate method to do its job AFTER cellForRowAtIndexPath... Is there any way to accomplish this?Mediocre
@Scott Use this method, but subclass layoutSubviews() instead.Convenient
@EricMORAND You say that "reloadData doesn't end before the table has finish reload its data." Can you clarify what you mean by that? I find that reloadData returns immediately and I see "END reloadData" before the cells are actually reloaded (i.e. before the UITableViewDataSource methods are called). My experimentation demonstrates the precise opposite of what you say. I must misunderstand what you're trying to say.Gmur
Isn't Eric's answer same as not implementing (read not overriding) reloaddata, calling reloaddata (which will be essentially [super reloaddata], and then after calling it, do the stuff you want at its completion?Ultimate
Doesn't work for me. This is executed before cellForRowAtIndexPathAboard
Once upon a time, reloadData did complete the loading process before it ended. But Apple changed it at some point. Now the UITableView class can cache the reloadData call with all the row insert and delete calls. If you look at the @interface declaration for UITableView, you will find NSMutableArray member _reloadItems right under _insertItems and _deleteItems. (I've had to rework code I inherited because of this change.)Brassiere
Posting the completion code in a block on the main queue after calling [super reloadData] works for me: dispatch_async(dispatch_get_main_queue(), ^{NSLog(@"reload complete");});. It basically leap frogs the blocks that get posted by the table view in reloadData.Angularity
Quite accidentally I bumped into this solution: tableView.tableFooterView = UIView() / tableViewHeight.constant = tableView.contentSize.height. You need to set the footerView before getting the contentSize e.g. in viewDidLoad.Capriole
M
53

I did have a same scenario in my app and thought would post my answer to you guys as other answers mentioned here does not work for me for iOS7 and later

Finally this is the only thing that worked out for me.

[yourTableview reloadData];

dispatch_async(dispatch_get_main_queue(),^{
        NSIndexPath *path = [NSIndexPath indexPathForRow:yourRow inSection:yourSection];
        //Basically maintain your logic to get the indexpath
        [yourTableview scrollToRowAtIndexPath:path atScrollPosition:UITableViewScrollPositionTop animated:YES];
 });

Swift Update:

yourTableview.reloadData()
dispatch_async(dispatch_get_main_queue(), { () -> Void in
    let path : NSIndexPath = NSIndexPath(forRow: myRowValue, inSection: mySectionValue)
    //Basically maintain your logic to get the indexpath
    yourTableview.scrollToRowAtIndexPath(path, atScrollPosition: UITableViewScrollPosition.Top, animated: true)

})

So how this works.

Basically when you do a reload the main thread becomes busy so at that time when we do a dispatch async thread, the block will wait till the main thread gets finished. So once the tableview has been loaded completely the main thread will gets finish and so it will dispatch our method block

Tested in iOS7 and iOS8 and it works awesome;)

Update for iOS9: This just works fine is iOS9 also. I have created a sample project in github as a POC. https://github.com/ipraba/TableReloadingNotifier

I am attaching the screenshot of my test here.

Tested Environment: iOS9 iPhone6 simulator from Xcode7

enter image description here

Melissamelisse answered 5/2, 2014 at 15:48 Comment(6)
best answear I have found!Commie
@Gon i did do a test in iOS9 and it just works fine. Can you please refer github.com/ipraba/TableReloadingNotifierMelissamelisse
It works fine on my emulator but doesn't seem to work on the actual device. Any one else having this issue?Anchor
This Solution finally works for me! it works fine on iOS 9Barong
I've tested on Real Device, a iPhone 6s. It works fine too.Barong
This sometimes does not work and "Reload Finished" is right after "Reload button pressed".Prakash
Y
25

EDIT: This answer is actually not a solution. It probably appears to work at first because reloading can happen pretty fast, but in fact the completion block doesn't necessarily get called after the data has fully finished reloading - because reloadData doesn't block. You should probably search for a better solution.

To expand on @Eric MORAND's answer, lets put a completion block in. Who doesn't love a block?

@interface DUTableView : UITableView

   - (void) reloadDataWithCompletion:( void (^) (void) )completionBlock;

@end

and...

#import "DUTableView.h"

@implementation DUTableView

- (void) reloadDataWithCompletion:( void (^) (void) )completionBlock {
    [super reloadData];
    if(completionBlock) {
        completionBlock();
    }
}

@end

Usage:

[self.tableView reloadDataWithCompletion:^{
                                            //do your stuff here
                                        }];
Yardarm answered 30/5, 2012 at 13:21 Comment(5)
I can't get enough of blocks. Who needs a delegate when you've got a nice block!Yardarm
I like this, but what about times when the system calls reloadData(), e.g. when the table is first displayed?Convenient
This is not solution Completition block starts execution before cellForRowAtIndexPathAboard
This will not work; reloadData is not a blocking method. This code is called immediately after calling reloadData, even if the cells aren't reloaded yet. Besides, look at this code, and you'll see that you might as well just put your code after reloadData.Ides
This works for me with a minor change. Instead of calling the completion block directly, call it in a block posted on the main queue: dispatch_async(dispatch_get_main_queue(), ^{completionBlock();});. This basically leap frogs the blocks posted by the table view in reloadData.Angularity
M
11

reloadData just asking for data for the visible cells. Says, to be notified when specify portion of your table is loaded, please hook the tableView: willDisplayCell: method.

- (void) reloadDisplayData
{
    isLoading =  YES;
    NSLog(@"Reload display with last index %d", lastIndex);
    [_tableView reloadData];
    if(lastIndex <= 0){
    isLoading = YES;
    //Notify completed
}

- (void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if(indexPath.row >= lastIndex){
    isLoading = NO;
    //Notify completed
}
Medical answered 10/4, 2012 at 7:21 Comment(1)
I'm not sure if this work. Last index is the end of the table data (eg. 100 records) but table will only display what is visible on the screen (eg. 8 records).Lift
L
11

That is my solution. 100% works and used in many projects. It's a simple UITableView subclass.

@protocol MyTableViewDelegate<NSObject, UITableViewDelegate>
@optional
- (void)tableViewWillReloadData:(UITableView *)tableView;
- (void)tableViewDidReloadData:(UITableView *)tableView;
@end

@interface MyTableView : UITableView {
    struct {
        unsigned int delegateWillReloadData:1;
        unsigned int delegateDidReloadData:1;
        unsigned int reloading:1;
    } _flags;
}
@end

@implementation MyTableView
- (id<MyTableViewDelegate>)delegate {
    return (id<MyTableViewDelegate>)[super delegate];
}

- (void)setDelegate:(id<MyTableViewDelegate>)delegate {
    [super setDelegate:delegate];
    _flags.delegateWillReloadData = [delegate respondsToSelector:@selector(tableViewWillReloadData:)];
    _flags.delegateDidReloadData = [delegate    respondsToSelector:@selector(tableViewDidReloadData:)];
}

- (void)reloadData {
    [super reloadData];
    if (_flags.reloading == NO) {
        _flags.reloading = YES;
        if (_flags.delegateWillReloadData) {
            [(id<MyTableViewDelegate>)self.delegate tableViewWillReloadData:self];
        }
        [self performSelector:@selector(finishReload) withObject:nil afterDelay:0.0f];
    }
}

- (void)finishReload {
    _flags.reloading = NO;
    if (_flags.delegateDidReloadData) {
        [(id<MyTableViewDelegate>)self.delegate tableViewDidReloadData:self];
    }
}

@end

It's similar to Josh Brown's solution with one exception. No delay is needed in performSelector method. No matter how long reloadData takes. tableViewDidLoadData: always fires when tableView finishes asking dataSource cellForRowAtIndexPath.

Even if you do not want to subclass UITableView you can simply call [performSelector:@selector(finishReload) withObject:nil afterDelay:0.0f] and your selector will be called right after the table finishes reloading. But you should ensure that selector is called only once per call to reloadData:

[self.tableView reloadData];
[self performSelector:@selector(finishReload) withObject:nil afterDelay:0.0f];

Enjoy. :)

Leathaleather answered 18/2, 2013 at 19:48 Comment(5)
Using performSelector call is brilliant. Simple and working, thanxLallygag
This is absolutely awesome. I've been wrangling with this for days. Thank you!Tancred
@MarkKryzhanouski , have you tested on iOS 9?Salters
@MarkKryzhanouski , thanks for the prompt feedback! Works great on iOS 7 and 8.Salters
The solutions performSelector or executing on the main thread with dispatch_asynch do not work on iOS 9.Injection
C
8

This is an answer to a slightly different question: I needed to know when UITableView had also finished calling cellForRowAtIndexPath(). I subclassed layoutSubviews() (thanks @Eric MORAND) and added a delegate callback:

SDTableView.h:

@protocol SDTableViewDelegate <NSObject, UITableViewDelegate>
@required
- (void)willReloadData;
- (void)didReloadData;
- (void)willLayoutSubviews;
- (void)didLayoutSubviews;
@end

@interface SDTableView : UITableView

@property(nonatomic,assign) id <SDTableViewDelegate> delegate;

@end;

SDTableView.m:

#import "SDTableView.h"

@implementation SDTableView

@dynamic delegate;

- (void) reloadData {
    [self.delegate willReloadData];

    [super reloadData];

    [self.delegate didReloadData];
}

- (void) layoutSubviews {
    [self.delegate willLayoutSubviews];

    [super layoutSubviews];

    [self.delegate didLayoutSubviews];
}

@end

Usage:

MyTableViewController.h:

#import "SDTableView.h"
@interface MyTableViewController : UITableViewController <SDTableViewDelegate>
@property (nonatomic) BOOL reloadInProgress;

MyTableViewController.m:

#import "MyTableViewController.h"
@implementation MyTableViewController
@synthesize reloadInProgress;

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

    if ( ! reloadInProgress) {
        NSLog(@"---- numberOfSectionsInTableView(): reloadInProgress=TRUE");
        reloadInProgress = TRUE;
    }

    return 1;
}

- (void)willReloadData {}
- (void)didReloadData {}
- (void)willLayoutSubviews {}

- (void)didLayoutSubviews {
    if (reloadInProgress) {
        NSLog(@"---- layoutSubviewsEnd(): reloadInProgress=FALSE");
        reloadInProgress = FALSE;
    }
}

NOTES: Since this is a subclass of UITableView which already has a delegate property pointing to MyTableViewController there's no need to add another one. The "@dynamic delegate" tells the compiler to use this property. (Here's a link describing this: http://farhadnoorzay.com/2012/01/20/objective-c-how-to-add-delegate-methods-in-a-subclass/)

The UITableView property in MyTableViewController must be changed to use the new SDTableView class. This is done in the Interface Builder Identity Inspector. Select the UITableView inside of the UITableViewController and set its "Custom Class" to SDTableView.

Convenient answered 1/6, 2012 at 16:33 Comment(3)
Where do you set your MyTableViewController to be the delegate for the SDTableView? How come it is possible to set a property of name "delegate" on SDTableView, when its superclass already has a property of that name (UITableView.delegate)? How do you atach your custom SDTableView to the MyTableViewController.tableView property when the property is of "UITablewView" type and the object (an instance of SDTableView) is of "SDTableView" type? I am fighting the same problem ,so I am hopeful there is a solution to these issues :)Intuitive
In the code of Symmetric (SDTableView), shouldn't reloadInProgress be set to FALSE in didReloadData instead of didLayoutSubviews ? Because reloadData is called after layoutSubviews and the loading should not be considered done before reloadData finishes.Hovey
This doesn't work for iOS 8, had to incorporate iPrabu's answer.Bobbette
O
6

I had found something similar to get notification for change in contentSize of TableView. I think that should work here as well since contentSize also changes with loading data.

Try this:

In viewDidLoad write,

[self.tableView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionPrior context:NULL];

and add this method to your viewController:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"contentSize"]) {
        DLog(@"change = %@", change.description)

        NSValue *new = [change valueForKey:@"new"];
        NSValue *old = [change valueForKey:@"old"];

        if (new && old) {
            if (![old isEqualToValue:new]) {
                // do your stuff
            }
        }


    }
}

You might need slight modifications in the check for change. This had worked for me though.

Cheers! :)

Opec answered 17/2, 2013 at 10:12 Comment(1)
Elegant! The only thing I would add is that you need to remove yourself and an observer (perhaps in the -dealloc method). Or add yourself as an observer in the -viewWillAppear and remove yourself in the -viewWillDisapear method.Withindoors
H
2

Here's a possible solution, though it's a hack:

[self.tableView reloadData];
[self performSelector:@selector(scrollTableView) withObject:nil afterDelay:0.3];

Where your -scrollTableView method scrolls the table view with -scrollRectToVisible:animated:. And, of course, you could configure the delay in the code above from 0.3 to whatever seems to work for you. Yeah, it's ridiculously hacky, but it works for me on my iPhone 5 and 4S...

Homorganic answered 14/2, 2013 at 16:34 Comment(3)
It may seem "hacky", but this is really a standard approach for scrolling: defer it to the next run cycle. I would change the delay to 0, since that works just as well and has less latency.Embank
Didn't work for me with the delay set to 0; 0.3 was the lowest I could go and still get the data.Homorganic
@Embank This worked for me. I was also able to use 0 for my delay. Thanks to both of you.Cloudscape
B
1

I had something similar I believe. I added a BOOL as instance variable which tells me if the offset has been restored and check that in -viewWillAppear:. When it has not been restored, I restore it in that method and set the BOOL to indicate that I did recover the offset.

It's kind of a hack and it probably can be done better, but this works for me at the moment.

Baggett answered 27/9, 2009 at 14:0 Comment(6)
Yeah, the problem is that viewWillAppear seems to be too early (at least in my scenario). Trying to restore the offset in viewWillAppear does nothing - unless I add the hack of calling reloadData first. As I understand it, viewWillAppear/viewDidAppear only refer to the table view itself actually appearing - they do not make any claims about whether the table cells in the view have either been enumerated or populated... and this needs to happen before you can recover an offset, as otherwise you would be recovering an offset on an empty view (and I understand why that won't work!).Tambourine
But maybe you meant that you are also forcing the loading of the table cells first by calling reloadData within viewWillAppear?Tambourine
No, I am not forcing a reload. When I had the problem, I tried to restore the offset in -viewDidLoad (where it should happen of course) but that did only work when I set the offset animated. By moving the setting of the offset to -viewWillAppear: it worked, but I had to maintain a flag to only set it once. I suppose the table view reloads its data once added to a view, so that's already in -loadView. Are you sure you have your data available on view load? Or is it being loaded in a separate thread or something?Baggett
OK, perhaps you can aid my understanding here. This is how I understand the sequence of calls to be when a UITableView is built up. 1) viewDidLoad is fired first. This indicates the UITableView is loaded into memory. 2) viewWillAppear is next to fire. This indicates that the UITableView will be displayed, but not necessarily that all the visible UITableViewCell objects will be fully instantiated/finished rendering.Tambourine
3) viewDidAppear is then next to fire. This indicates that the UITableView was displayed, but again not necessarily that all the visible UITableViewCell objects were fully instantiated/finished rendering at that point in time. Is this also your understanding? If so, then it seems clear to me that a call to scrollRectToVisible:animated: will only work if the UITableView has obtained enough information about the contained UITableViewCell objects to scroll to the specified position.Tambourine
And all of the above is true, the question then is: what non-hacky options do we have for finding out when the UITableView has obtained sufficient information from its data source to guarantee that a scroll request (ie, a scrollRectToVisible:animated: call) will actually work (and not just do nothing)?Tambourine
B
1

It sounds like you want to update cell content, but without the sudden jumps that can accompany cell insertions and deletions.

There are several articles on doing that. This is one.

I suggest using setContentOffset:animated: instead of scrollRectToVisible:animated: for pixel-perfect settings of a scroll view.

Brassiere answered 21/2, 2013 at 6:50 Comment(0)
F
1

You can try the following logic:

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

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyIdentifier"];

    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"MyIdentifier"];
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
    }

    if ( [self chkIfLastCellIndexToCreate:tableView :indexPath]){
        NSLog(@"Created Last Cell. IndexPath = %@", indexPath);
        //[self.activityIndicator hide];
        //Do the task for TableView Loading Finished
    }
    prevIndexPath = indexPath;

    return cell;
}



-(BOOL) chkIfLastCellIndexToCreate:(UITableView*)tableView : (NSIndexPath *)indexPath{

    BOOL bRetVal = NO;
    NSArray *visibleIndices = [tableView indexPathsForVisibleRows];

    if (!visibleIndices || ![visibleIndices count])
        bRetVal = YES;

    NSIndexPath *firstVisibleIP = [visibleIndices objectAtIndex:0];
    NSIndexPath *lastVisibleIP = [visibleIndices objectAtIndex:[visibleIndices count]-1];

    if ((indexPath.row > prevIndexPath.row) && (indexPath.section >= prevIndexPath.section)){
        //Ascending - scrolling up
        if ([indexPath isEqual:lastVisibleIP]) {
            bRetVal = YES;
            //NSLog(@"Last Loading Cell :: %@", indexPath);
        }
    } else if ((indexPath.row < prevIndexPath.row) && (indexPath.section <= prevIndexPath.section)) {
        //Descending - scrolling down
        if ([indexPath isEqual:firstVisibleIP]) {
            bRetVal = YES;
            //NSLog(@"Last Loading Cell :: %@", indexPath);
        }
    }
    return bRetVal;
}

And before you call reloadData, set prevIndexPath to nil. Like:

prevIndexPath = nil;
[mainTableView reloadData];

I tested with NSLogs, and this logic seems ok. You may customise/improve as needed.

Forewarn answered 21/2, 2013 at 20:38 Comment(0)
F
0

Isn't UITableView layoutSubviews called just before the table view displays it content? I've noticed that it is called once the table view has finished load its data, maybe you should investigate in that direction.

Fretted answered 31/5, 2010 at 10:28 Comment(0)
T
0

finally i have made my code work with this -

[tableView scrollToRowAtIndexPath:scrollToIndex atScrollPosition:UITableViewScrollPositionTop animated:YES];

there were few things which needed to be taken care of -

  1. call it within "- (UITableViewCell *)MyTableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath"
  2. just ensure that "scrollToRowAtIndexPath" message is sent to relevant instance of UITableView, which is definitely MyTableview in this case.
  3. In my case UIView is the view which contains instance of UITableView
  4. Also, this will be called for every cell load. Therefore, put up a logic inside "cellForRowAtIndexPath" to avoid calling "scrollToRowAtIndexPath" more than once.
Tonneau answered 6/10, 2010 at 22:28 Comment(2)
and just ensure that this piece is not called more than once. if not it locks the scrolling.Tonneau
This may work, but it's definitely not elegant and not the solution I'm looking for. Unfortunately, there doesn't seem to be a better way to determine whether -reloadData has actually finished getting the data...Homorganic
C
0

You can resize your tableview or set it content size in this method when all data loaded:

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    tableView.frame =CGRectMake(tableView.frame.origin.x, tableView.frame.origin.y, tableView.frame.size.width, tableView.contentSize.height);
}
Clive answered 18/2, 2013 at 13:17 Comment(0)
C
0

I just run repeating scheduled timer and invalidate it only when table's contentSize is bigger when tableHeaderView height (means there is rows content in the table). The code in C# (monotouch), but I hope the idea is clear:

    public override void ReloadTableData()
    {
        base.ReloadTableData();

        // don't do anything if there is no data
        if (ItemsSource != null && ItemsSource.Length > 0)
        {
            _timer = NSTimer.CreateRepeatingScheduledTimer(TimeSpan.MinValue, 
                new NSAction(() => 
                {
                    // make sure that table has header view and content size is big enought
                    if (TableView.TableHeaderView != null &&
                        TableView.ContentSize.Height > 
                            TableView.TableHeaderView.Frame.Height)
                    {
                        TableView.SetContentOffset(
                            new PointF(0, TableView.TableHeaderView.Frame.Height), false);
                        _timer.Invalidate();
                        _timer = null;
                    }
                }));
        }
    }
Curarize answered 10/8, 2013 at 1:51 Comment(0)
P
0

Since iOS 6 onwards, the UITableview delegate method called:

-(void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section

will execute once your table reloads successfully. You can do customisation as required in this method.

Penetrant answered 3/11, 2014 at 16:50 Comment(0)
O
0

The best solution I've found in Swift

extension UITableView {
    func reloadData(completion: ()->()) {
        self.reloadData()
        dispatch_async(dispatch_get_main_queue()) {
            completion()
        }
    }
}
Onslaught answered 20/4, 2016 at 8:15 Comment(0)
P
-1

Why no just extend?

@interface UITableView(reloadComplete)
- (void) reloadDataWithCompletion:( void (^) (void) )completionBlock;
@end

@implementation UITableView(reloadComplete)
- (void) reloadDataWithCompletion:( void (^) (void) )completionBlock {
    [self reloadData];
    if(completionBlock) {
        completionBlock();
    }
}
@end

scroll to the end:

[self.table reloadDataWithCompletion:^{
    NSInteger numberOfRows = [self.table numberOfRowsInSection:0];
    if (numberOfRows > 0)
    {
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:numberOfRows-1 inSection:0];
        [self.table scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
    }
}];

Not tested with a lot of data

Pointless answered 16/9, 2013 at 11:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.