i. I have tried a lot but could not execute successfully .
ii. In tableview cell, there are 3 3 fields need to display. One image view, button1 -->taking photo button, button2---> browse button.
iii. First time tableview should display a custom cell with one row.
iv. When a user clicks "add new button" , which lays outside of tableview, a new row will create with all above 3 fields (image view, button1, button2)
v. Number of clicks of "add new button" , will create new rows with all above 3 fields.
vi. I could do all above things created dynamically successfully with a simple image view which contains above 3 fields but could not succeed to work on custom cell.
vii. Again I need to set the tag of each cell, broswe button, take photo button so that when clicks, will take the tag value.
The table view works by adding a delegate and a data source. Assuming your table view has an owner as a view controller and both delegate and data source are the view controller itself. All you need to do is implement those data source methods to return an appropriate data then you should call reloadData on the table view or if you want a bit of extra work to look nicer check how to add rows animated around the web.
This is a very simple and not optimized example but is very short and easy to read. I hope it helps you get on the right track:
#interface MyViewController()<UITableViewDataSource, UITableViewDelegate>
#property UITableView *tableView;
#property NSArray *myCells;
#end
#implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.delegate = self; // could be done in storyboard
self.tableView.dataSource = self; // could be done in storyboard
[self addACell];
}
- (void)addCellButtonPressed:(id)sender {
[self addACell];
}
- (void)addACell {
MyCell *cell = [[MyCell alloc] init];
[cell.button1 addTarget:self action:#selector(cellButton1Pressed:) forControlEvents:UIControlEventTouchUpInside];
[cell.button2 addTarget:self action:#selector(cellButton2Pressed:) forControlEvents:UIControlEventTouchUpInside];
self.myCells = [self.myCells arrayByAddingObject:cell];
[self.tableView reloadData]; // will call the delegate again and refresh cells
}
- (void)cellButton1Pressed:(id)sender {
MyCell *cellPressed = nil;
for(MyCell *cell in self.myCells) {
if(cell.button1 == sender) {
cellPressed = cell;
break;
}
}
// do whatever
}
- (void)cellButton2Pressed:(id)sender {
MyCell *cellPressed = nil;
for(MyCell *cell in self.myCells) {
if(cell.button2 == sender) {
cellPressed = cell;
break;
}
}
// do whatever
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.myCells.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
return self.myCells[indexPath.row];
}
#end
I have a UITableView with 2 sections (the 1st for the filters and the 2nd for the data).
I want to hide the 1st section when the view is loaded.
The section appears only when I scroll on top.
I tested with scrollToRowAtIndexPath: but the result is not as expected.
Please help. Thanks.
Take a BOOL variable for checking whether to load one section or
two section.
Make is YES in ViewDidLoad then,for the first time UITableView will load one section.
Then when UITableView will be scrolled to top,make that BOOL variable to NO
- (void)viewDidLoad
{
[super viewDidLoad];
isFirstTime = YES;
}
In datasource method of TableView
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
if (isFirstTime) {
return 1;
}
return 2;
}
For checking the UITableView to scroll TOP.We will use delegate methods of UIScrollview.UITableView is the subclass of UIScrollView.
-(void)scrollViewDidScroll:(UITableView *)tableView{
CGFloat content_Offset = tableView.contentOffset.y;
if (content_Offset >= 0 && isFirstTime){
//UITableview is scrolled to top
isFirstTime = NO;
[self.myTbaleView reloadData];
}else if (content_Offset<0) {
//UITableview is draged down
}
}
You can add a BOOL variable to find out whether it is loading first time or not. Set it true in the viewDidLoad like:
- (void)viewDidLoad
{
[super viewDidLoad];
isFirstTimeLoading = YES;
}
Then in your numberOfSectionsInTableView: delegate method,
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
if (isFirstTimeLoading) {
return 1;
} else {
return 2;
}
}
TableView scrolling to top can be find out using
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
In this method, check whether the row is top row. if yes then make isFirstTimeLoading to NO and reload data. Dont forget to change the data also if necessary.
I have a UITableView where I have section headers that can be tapped to expand or collapse the section. In my particular example each section only has one row, which is either visible (section expanded) or hidden (section collapsed).
As section header i'm using custom UITableViewHeaderFooterView - HeaderAccountView. I created *.xib file at Interface Builder , and set it custom class to my HeaderAccountView (still at IB field).
There are no any changes to init method or smth like this in my HeaderAccountView.h and HeaderAccountView.m files - only some functions to highlight self (selected section) etc.
in my main ViewController .m file
- (void)viewDidLoad
{
[super viewDidLoad];
.........
.........
UITableView *tableView = (id)[self.view viewWithTag:1];
UINib *nib= [UINib nibWithNibName:#"HeaderAccountView" bundle:nil];
[tableView registerNib:nib forHeaderFooterViewReuseIdentifier:#"HeaderCell"];
}
and then
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
HeaderAccountView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:#"HeaderCell"];
if (headerView==nil)
{ headerView = [[HeaderAccountView alloc]
initWithReuseIdentifier:#"HeaderCell"];
}
return headerView;
}
when i'm running project everything going OK - sections load with needed data in it, when section receive tap - it highlights (like standard cell).
But when i'm scrolling away tableview to bottom for example from selected highlighted section, and this highlighted section already is not visible at view - that section that just appeared from bottom - already highlighted!
I understand that its because it creates new instance of my HeaderAccountView with property BOOL selected set to YES.
But I'm new to objective-c (and coding) and don't understand how to correct resolve this.
I tried to use prepareForReuse method of my custom UITableViewHeaderFooterView like this
HeaderAccountView.m:
-(void) prepareForReuse
{
self.selectedBackground.alpha = 0;
}
It works better - but now i have another issue - when i returning to my first (truly) selected and highlighted section - it obviously don't highlight.
Thanks for any help and sorry if it elementary question.
You have to manually keep a list of your selected headers indexes.
Next, implement the method tableView:willDisplayHeaderView: in your view controller to refresh your header when it will be displayed.
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
{
view.selectedBackground.alpha = ([_highlightedHeadersList containsObject:#(section)] ? 0.0f : 1.0f);
}
And you have to add / remove indexes in _highlightedHeadersList.
I've done this using the following. In the table view controller I created a property (NSInteger), and called it sectionForSelectedHeader. Set it to -1 initially so no section will be initially selected.
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
RDHeader *header = [tableView dequeueReusableHeaderFooterViewWithIdentifier:#"Header"];
header.tag = section;
if (header.gestureRecognizers.count == 0) {
UITapGestureRecognizer *tapper = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(headerSelected:)];
[header addGestureRecognizer:tapper];
}
header.selected = (self.sectionForSelectedHeader == section)? 1 : 0;
return header;
}
-(void)headerSelected:(UITapGestureRecognizer *) tapper {
if ([(RDHeader *)tapper.view selected] != 1) {
self.sectionForSelectedHeader = tapper.view.tag;
}else{
self.sectionForSelectedHeader = -1;
}
[self.tableView reloadData];
}
Then in my custom header subclass, I have a method setSelected: (selected is an NSInteger property in the RDHeader class) like this:
-(void)setSelected:(NSInteger)selected {
_selected = selected;
if (selected) {
self.contentView.backgroundColor = [UIColor orangeColor];
}else{
self.contentView.backgroundColor = [UIColor yellowColor];
}
}
I make a view like imessage, just input text into the bottom text view. I use table view to do this, and the text view in the last cell. when I input long text that more than one line, I need the text view and the cell become tailer. so I need refresh cell's height. but if I use table view's reload or reload row, the content in text view will disappear and the keyboard will disappear too. Is there any way better to fix it?
May be I should use tool bar to do it easy? but I still doubt table view can do it.
The cells will resize smoothly when you call beginUpdates and endUpdates. After those calls the tableView will send tableView:heightForRowAtIndexPath: for all the cells in the table, when the tableView got all the heights for all the cells it will animate the resizing.
And you can update cells without reloading them by setting the properties of the cell directly. There is no need to involve the tableView and tableView:cellForRowAtIndexPath:
To resize the cells you would use code similar to this
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
NSString *newText = [textView.text stringByReplacingCharactersInRange:range withString:text];
CGSize size = // calculate size of new text
if ((NSInteger)size.height != (NSInteger)[self tableView:nil heightForRowAtIndexPath:nil]) {
// if new size is different to old size resize cells.
// since beginUpdate/endUpdates calls tableView:heightForRowAtIndexPath: for all cells in the table this should only be done when really necessary.
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
return YES;
}
to change the content of a cell without reloading I use something like this:
- (void)configureCell:(FancyCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
MyFancyObject *object = ...
cell.textView.text = object.text;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
FancyCell *cell = (FancyCell *)[tableView dequeueReusableCellWithIdentifier:#"CellWithTextView"];
[self configureCell:cell forRowAtIndexPath:indexPath];
return cell;
}
// whenever you want to change the cell content use something like this:
NSIndexPath *indexPath = ...
FancyCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
[self configureCell:cell forRowAtIndexPath:indexPath];
I've written a subclass of UITableViewCell to handle this functionality.
.h file:
#import <UIKit/UIKit.h>
#protocol AECELLSizeableDelegate;
#interface AECELLSizeable : UITableViewCell
#property (weak, nonatomic) id <AECELLSizeableDelegate> delegate;
#property IBOutlet UIView *viewMinimized;
#property IBOutlet UIView *viewMaximized;
#property BOOL maximized;
#property CGFloat height;
- (IBAction)clickedConfirm:(id)sender;
- (IBAction)clickedCancel:(id)sender;
- (void)minimizeForTableview: (UITableView*)tableView;
- (void)maximizeForTableview: (UITableView*)tableView;
- (void)toggleForTableview: (UITableView*)tableView;
#end
#protocol AECELLSizeableDelegate <NSObject>
- (void)sizeableCellConfirmedForCell: (AECELLSizeable*)cell;
- (void)sizeableCellCancelledForCell: (AECELLSizeable*)cell;
#end
.m file:
#import "AECELLSizeable.h"
#implementation AECELLSizeable
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (void)minimizeForTableview: (UITableView*)tableView
{
self.maximized = NO;
[self.viewMinimized setHidden:NO];
[self.viewMaximized setHidden:YES];
self.height = self.viewMinimized.frame.size.height;
[tableView beginUpdates];
[tableView endUpdates];
}
- (void)maximizeForTableview: (UITableView*)tableView
{
self.maximized = YES;
[self.viewMinimized setHidden:YES];
[self.viewMaximized setHidden:NO];
self.height = self.viewMaximized.frame.size.height;
[tableView beginUpdates];
[tableView endUpdates];
}
- (void)toggleForTableview:(UITableView *)tableView
{
if (self.maximized) {
[self minimizeForTableview:tableView];
} else {
[self maximizeForTableview:tableView];
}
}
- (void)clickedConfirm:(id)sender
{
[self.delegate sizeableCellConfirmedForCell:self];
}
- (void)clickedCancel:(id)sender
{
[self.delegate sizeableCellCancelledForCell:self];
}
#end
Example Usage:
Create a UITableViewController with a static UITableView in IB
Add a cell to the tableview that is a AECELLSizeable (or subclass of it)
Create two UIViews in this cell. One UIView will be used for the content visible while minimized, the other will be used for the content visible while maximized. Ensure these cells start at 0 on the y axis and that their height is equal to that of the height you wish to have the cell for each state.
Add any subviews to these two views you desired. Optionally add confirm and cancel UIButtons to the maximized UIView and hook up the provided IBActions to receive delegate callbacks on these events.
Set your tableview controller to conform to the AECELLSizeableDelegate and set the cell's delegate property to the tableview controller.
Create an IBOutlet in your UIViewController's interface file for the AECELLSizeable cell.
Back in IB, ensure the cell's initial height is that of the minimized version and connect the IBOutlet you just previously created.
Define the tableview's heightForRowAtIndexPath callback method in the tableview controller's implementation file as such:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 0) {
//Sizeable Cell
return self.cellSizeable.height;
} else {
return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}
}
In your tableview controller's viewDidLoad method, call minimizeForTableview on your cell to ensure it starts off minimized.
Then, call maximizeForTableview: on the cell when it is selected via the didSelectRowAtIndexPath callback from the tableview (or however else you would like to handle it) and call the minimizeForTableview: method on the cell when you receive the canceled / confirmed delegate callbacks from the cell (or, again, however else you'd like to handle it).
Check out this library. This is an implementation of message bubbles using UITableView. Keep in mind that every time a cell is displayed, cellForRow:atIndexPath is called and the cell is drawed.
EDIT
You can use heightForRowAtIndexPath
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
Messages *message = [self.messageList objectAtIndex:[indexPath row]];
CGSize stringSize = [message.text sizeWithFont:[UIFont fontWithName:#"HelveticaNeue-Light" size:13]
constrainedToSize:CGSizeMake(320, 9999) lineBreakMode:UILineBreakModeWordWrap];
return stringSize.height + 78;
}
Table view cells won't smoothly resize. Dot.
However, since your text view is in the last cell, you are lucky because you can easily simulate a row resizing. Having a text view in the middle of the table would be much more difficult.
Here is how I would do it: put your text view on top of the table view. Sync its position with the contentOffset of the tableView in scrollViewDidScroll:. And use the animatable contentInset of the tableView in order to leave room for the textView, as the user types in it. You may have to make sure your textView is not scrollable.
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