UITableview on nib (view) doesn't reload (Objective-C) - ios

I wanna reload some data in my FAQViewcontroller. The tableview is placed in a nib file (view) which is loaded with the Viewcontroller, called this way:
FAQViewController *vc = [[FAQViewController alloc] initWithNibName:#"FAQViewController" bundle:nil];
[self presentViewController:vc animated:YES completion:nil];
In viewDidLoad of FAQViewcontroller, the tableview is displaying the right way (I see the separator lines and when I'm changing the backgroundColor, it is working well). But there's no information to display yet, so the cells are empty (as expected).
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self.tableView registerNib:[UINib nibWithNibName:#"FAQCell" bundle:nil] forCellReuseIdentifier:#"FAQCell"];
if (self.faqData == nil || [_faqData count] == 0) {
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
[[DataModel sharedInstance] getFrequentlyAskedQuestions:^(NSError *error) {
}];
});
}
}
When the network call is done, this function in the FAQViewcontroller is called:
- (void)setFaqData:(NSArray *)faqData {
_faqData = faqData;
NSLog(#"%#", _faqData);
dispatch_async(dispatch_get_main_queue(), ^() {
[self.tableView reloadData];
});
}
But reloadData is doing nothing. The numberofrowsinsection isn't called.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _faqData.count;
}
I don't know what to do about it. Never had this before (normally I code in Swift, maybe I forget something in Objective-C)?
Does anyone know what I'm doing wrong?
Thank you very much!

Could you try making tableView as a strong reference?
something like:
#property (nonatomic, strong) IBOutlet UITableView *tableView

Related

UITableViewController Delegate and DataSource method not called

I am quite new in ios dev and i am trying to populate table view using Array of HCPerson class. I am also playing around with bring object created in nib to storyboard by code. But no matter what i do the table doesn't become populated
#import "HCTableViewController.h"
NSString *const HCTableCellNibName=#"HCTableCell";
NSString *const HCCellIdentifier=#"personCell";
#interface HCTableViewController()
#end
#implementation HCTableViewController
-(void)viewDidLoad
{
[super viewDidLoad];
[self.tableView registerNib:[UINib nibWithNibName:HCTableCellNibName bundle:nil] forCellReuseIdentifier:HCCellIdentifier];
}
-(NSInteger)tableView:(UITableView*)tableView numberOfRowInSection:(NSInteger)section
{
return self.persons.count;
}
-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
UITableViewCell* cell=(UITableViewCell*)[self.tableView dequeueReusableCellWithIdentifier:HCCellIdentifier forIndexPath:indexPath];
HCPerson *person=self.persons[indexPath.row];
[self configureCell:cell forPerson:person];
return cell;
}
-(void)configureCell:(UITableViewCell*)cell forPerson:(HCPerson*)person
{
cell.textLabel.text=person.name;
cell.detailTextLabel.text=[[NSNumberFormatter alloc] stringFromNumber:person.age];
}
#end
//HCPerson
#import "HCPerson.h"
#interface HCPerson()
#end
#implementation HCPerson
+(HCPerson*)personWithName:(NSString*)name age:(NSNumber*)age residence:(NSString*)residence contact:(NSString *)contact
{
HCPerson *newPerson=[[self alloc] init];
newPerson.name=name;
newPerson.age=age;
newPerson.residence=residence;
newPerson.contact=contact;
return newPerson;
}
#end
EDIT: Karthik solved this using UIViewController with outleting TableView from it. But is there any way to call the delegate and datasource method using UITableViewController
ok lets try on your viewDidLoad
-(void)viewDidLoad
{
[super viewDidLoad];
[self.tableView registerNib:[UINib nibWithNibName:HCTableCellNibName bundle:nil] forCellReuseIdentifier:HCCellIdentifier];
if ( self.persons.count>0)
{
[self.yourtableview reloadData];
}
else
{
// no data found , put the NSLog and print the count
}
ok choice no-2
try this
UITableViewCell* cell=(UITableViewCell*)[tableView dequeueReusableCellWithIdentifier:HCCellIdentifier forIndexPath:indexPath];
Remove that self because it show be kept of that method. Not your Tableview and also try with different name than tableView because Xcode finds it difficult to select one from them.
Did you add table view controller from storyboard?
If yes, Go to storyborad -> Inspector Area ->File Inspector - add your class name there.
Screenshot attached.

cellForRowAtIndexPath not being called after successful api call

In my app(Using Storyboards FYI) I have a viewController which contains a tableView. My viewController hits an API and returns an array of items that my tableView should populate with. The problem is, after successfully retrieving the array, the reloadData method does not trigger a call to cellForRowAtIndexPath. I've set the delegate and datasource properly, and still have no luck. Here is my code:
in my viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView = [[UITableView alloc]init];
self.tableView.dataSource = self;
self.tableView.delegate = self;
[[CCHTTPClient sharedClient] getItemsWithSuccess:^(NSArray *items) {
self.items = items;
[self.tableView reloadData];
} failure:^(NSError *error) {
}];
}
and also in my numberOfRowsInSection:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.items count];
}
I've also found that if i did an API call on the previous VC and set the array of the viewController's items before the viewController is pushed, the table populates properly. really unsure about what's going on here. Any help would be gratefully appreciated !
self.tableView should be an outlet if your controller and table view are made in a storyboard. You're creating a different table view in viewDidLoad with alloc init, so you're calling reloadData on that one, not the one you have on screen. Remove that alloc init line, and connect the outlet in IB.
I would suspect this is caused by calling reloadData from a secondary thread. Try this:
[[AGHTTPClient sharedClient] getItemsWithSuccess:^(NSArray *items) {
self.items = items;
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
} failure:nil];

ReloadData for UITableView not working; tableView returns NULL when logged

I am calling a method in my TableViewController class from another class.
To call the method of displaying the tableview, I do this:
TableViewController *tableVC = [[TableViewController alloc]init];
[tableVC setTableViewContent];
then in TableViewController.h
#interface TableViewController : UITableViewController <UITableViewDelegate, UITableViewDataSource>
{
NSMutableArray *nameArray;
}
-(void)setTableViewContent;
#property (nonatomic, strong) IBOutlet UITableView *tableView;
#end
TableViewController.m
#implementation TableViewController
#synthesize tableView;
- (void)viewDidLoad
{
nameArray = [[NSMutableArray alloc]init];
[super viewDidLoad];
}
-(void)setTableViewContent{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
for(int i=0;i< [appDelegate.businessArray count];i++)
{
NSDictionary *businessDict = [[appDelegate.businessArray objectAtIndex:i] valueForKey:#"location"];
nameArray = [appDelegate.businessArray valueForKey:#"name"];
}
NSLog(#"%#", nameArray);
NSLog(#"tableview: %#", tableView);
// here tableview returns null
[tableView reloadData];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [nameArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"updating tableview...");
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell =[tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
cell.textLabel.text = [nameArray objectAtIndex:indexPath.row];
return cell;
}
For some reason when I try to log the tableview, it returns null, so the ReloadData doesn't work. The delegate and datasource is connected properly in IB, and there is a referencing outlet for tableView.
Any idea what is going on here? Thanks in advance
If you added the table view controller to a container view, then you can get a reference to that controller in prepareForSegue. For a controller in a container view, prepareForSegue will be called right before the parent controller's viewDidLoad, so you don't need to do anything to invoke it. In my example below, I've called the segue "TableEmbed" -- you need to give the segue that identifier in IB.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([segue.identifier isEqualToString:#"TableEmbed"]) {
TableViewController *tableVC = (TableViewController *)segue.destinationViewController;
[tableVC setTableViewContent];
}
}
Be aware that prepareForSegue:sender: is called before either controller's viewDidLoad is called, so you should move the initialization of your array to setTableViewContent, and your reloadTable should go into viewDidLoad.
BTW, it's not clear to me why you want to call setTableContent from your other class anyway. Why not move all the code in that method to the viewDidLoad method of the table view controller?
This is happening because you are calling a method on tableView before it actually exists. Simply initializing that class doesn't draw the table itself, so using reloadData before the table has actually been created doesn't really make any sense.
What you want to do in this situation is create your nameArray in whatever class is calling setTableViewContent, and then pass it in either via a custom init method, or by setting tableVC.nameArray before loading that table view controller.
What I would do is make custom init method like - (id)initWithArray:(NSMutableArray *)nameArr
Which should look something like this:
if (self = [super init]) {
nameArray = [nameArr copy];
}
return self;
Then where you have TableViewController *tableVC = [[TableViewController alloc]init]; put TableViewController *tableVC = [[TableViewController alloc]initWithArray:theNameArray]; where theNameArray is the content in setTableViewContent (which you are now generating in the same class that calls the table view instead of in the table view itself).
Make sense?
I solved a similar situation by creating a "safe" reload method on the UITableViewController:
- (void)reloadTableViewData
{
if ([self isViewLoaded])
[self.tableView reloadData];
}
According to the docs for isViewLoaded:
Calling this method reports whether the view is loaded. Unlike the view property, it does not attempt to load the view if it is not already in memory.
Therefore it is safe to call reloadTableViewData on the table view controller at any time.

Calling UITableView's reloadData method from appdelegate

I am having a serious issue with UITableView reloadData method. I have a UIViewController class, WiGiMainViewController that has a UITableView and NSMuttableArray in it. I am currently issuing network calls in the AppDelegate, and posting notifications to the WiGiMainViewController once the data has been downloaded. In my selector method for the notification, reloadWigiList, I am passing an NSArray containing the the recently downloaded items. I then initialize the WiGiMainViewController's NSMuttableArray with the passed in array and proceed to call reloadData() on my UITableView object. I can see from nslog statements that the numberOfRowsInSection is fired on reload but not the cellForRowAtIndexPath, therefore causing the UI NOT to reload the UITableView with the newly downloaded items. I have verified that the reloadData method is being called on the main thread and that the datasource delegate are set in IB and programatically in the viewDidLoad method of WiGiMainViewController. Any ideas why my UITableView, wigiLists isn't reloading the data, in particular, not calling the cellForRowAtIndexPath method?
#interface WiGiMainViewController : UIViewController<FBRequestDelegate,UITableViewDelegate, UITableViewDataSource> {
//setup UI
UITableView *wigiLists;
WiGiAppDelegate *myAppDelegate;
NSMutableArray *itemsList;
}
#property (nonatomic, retain) WiGiAppDelegate *myAppDelegate;
#property (nonatomic, retain) IBOutlet UITableView *wigiLists;
#property (nonatomic, retain) NSMutableArray *itemsList;
-(void) reloadWigiList: (NSNotification*) notification;
-(NSInteger) numberOfSectionsInTableView:(UITableView *)tableView;
-(NSInteger) tableView: (UITableView*) tableView numberOfRowsInSection:(NSInteger) section;
-(UITableViewCell *) tableView: (UITableView*)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath;
#end
#implementation WiGiMainViewController
#synthesize headerLabel = _headerLabel, userNameLabel = _userNameLabel, facebookPicture = _facebookPicture,
myAppDelegate, wigiLists, itemsList;
- (void)viewDidLoad {
NSLog(#"In viewDidLoad");
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(reloadWigiList:) name:#"wigiItemUpdate" object:nil];
// get appdelicate
self.myAppDelegate = (WiGiAppDelegate*) [[UIApplication sharedApplication] delegate];
self.itemsList = [[NSMutableArray alloc] init];
//setup tableview
self.wigiLists = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
self.wigiLists.delegate = self;
self.wigiLists.dataSource = self;
//set up view
[self.headerLabel setText:self.myAppDelegate.HEADER_TEXT];
//check if user is logged in
if (self.myAppDelegate.isLoggedIn) {
//user is logged in
NSLog(#"HERE");
//get facebook information to populate view
[self retrieveFacebookInfoForUser];
//[self.myAppDelegate retrieveWigiItems];
}else {
//user is not logged in
NSLog(#"user not logged in");
//show login modal
}
//[self.wigiLists reloadData];
[super viewDidLoad];
}
-(void) reloadWigiList: (NSNotification *) notification {
if ([NSThread isMainThread]) {
NSLog(#"main thread");
}else{
NSLog(#"METHOD NOT CALLED ON MAIN THREAD!");
}
NSLog(#"notification recieved:%#", notification.userInfo);
NSLog(#"in reloadwigilists:%#", wigiLists );
NSLog(#"list size:%#", self.itemsList);
NSLog(#"delegate:%#",self.wigiLists.delegate);
NSLog(#"datasource:%#",self.wigiLists.dataSource);
//populate previously empty itemsList
[self.itemsList setArray:notification.userInfo];
NSLog(#"list size:%#", self.itemsList);
[self.wigiLists reloadData];
}
/////////////////////////////////////
// UITableViewDelegate protocols
-(NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
//NSLog(#"numberofsections: %#", [self.itemsList count]);
return 1;
}
-(NSInteger) tableView: (UITableView *)tableView numberOfRowsInSection:(NSInteger) section {
NSLog(#"7t87giuiu%#",self.itemsList);
NSLog(#"numberofrows: %i", [self.itemsList count]);
return [self.itemsList count];
//return 6;
}
first thing i would do is set a breakpoint on [self.wigiMainViewController.wigiLists reloadData]
if the wigiLists tableView shows a null in the debugger then it is probably an outlet that hasn't been set. also, make sure that both the delegate and datasource have been set.
Wow! after 3 days of banging my head against this problem, it was something ridiculously simple. In my ViewDidLoad method of WiGiMainViewController, i was initializing my tableview:
self.wigiLists = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
Because I had linked the tableView I created in IB to my instance wigiLists, alloc'ing and initializing in the ViewDidLoad overwrote my link to the tableView created in IB and currently being displayed. Getting rid of this line fixed everything.

Get notified when UITableView has finished asking for data?

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).
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.
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
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
}];
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
}
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. :)
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.
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! :)
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...
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.
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.
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.
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 -
call it within "- (UITableViewCell *)MyTableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath"
just ensure that "scrollToRowAtIndexPath" message is sent to relevant instance of UITableView, which is definitely MyTableview in this case.
In my case UIView is the view which contains instance of UITableView
Also, this will be called for every cell load. Therefore, put up a logic inside "cellForRowAtIndexPath" to avoid calling "scrollToRowAtIndexPath" more than once.
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);
}
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;
}
}));
}
}
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.
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.
The best solution I've found in Swift
extension UITableView {
func reloadData(completion: ()->()) {
self.reloadData()
dispatch_async(dispatch_get_main_queue()) {
completion()
}
}
}
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

Resources