I am in a UITableViewController and I have textfields inside cells. When the user clicks on the textfield, I implement the UITextFieldDelegate and in method textFieldDidBeganEditing I determine the index of the cell and scroll to that position. It works perfectly for all cells expect for the last 4-5 cells. What am I doing wrong? Thanks.
scrollToRowAtIndexPath: method scrolls the cell to UITableViewScrollPositionTop or UITableViewScrollPositionMiddle only if the tableView's contentSize is large enough to bring the cell to those positions. If the cell you are trying to scroll to top or middle is the last cell or among the last few cells, it can not be scrolled to middle or top. In these situations you need to calculate the contentOffset manually for those cells.
Edit - Calculating the contentOffset:
In order to calculate the contentOffset use the method as specified by #Schoob. Put the following code in your textFieldDidBeginEditing method.
CGPoint origin = textField.frame.origin;
CGPoint point = [textField.superview convertPoint:origin toView:self.tableView];
float navBarHeight = self.navigationController.navigationBar.frame.size.height;
CGPoint offset = tableView.contentOffset;
// Adjust the below value as you need
offset.y += (point.y - navBarHeight);
[tableView setContentOffset:o animated:YES];
I found the key to using scrollToRowAtIndexPath was to make sure to call this after the view has been presented. I.e. pushed on to a navigationcontroller or presented modally.
My working code goes like this
Calling code
MyViewController *cont = [[MyViewController alloc] initWithMedication:medication];
cont.sectionToShow = 1;
[self.navigationController pushViewController:cont animated:YES];
[cont release];
Viewcontroller code inside viewWillAppear
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:self.sectionToShow];
[cont.tableview scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
If I did it any other way there were always edge cases where it didn't work. I guess we want to scroll late in the presentation cycle.
This is what you should be using when trying to determine the indexPath of a view/textField added on a UITableViewCell
CGPoint point = [textField.superview convertPoint:textField.frame.origin toView:self.tableView];
Then use point with
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:point];
Related
I am trying to update a custom tableview cell's progressview but the progress view wont update.
I can set the labels on the tableview cell but accessing things like progress bars or activity indicators doesnt work.
I do the following on the custom cell
cell.progressView.hidden = NO;
cell.progressView.progress = 0.5;
[cell.progressView setNeedsDisplay];
This doesnt work even though in the debugger I see the object is of the correct cell type and the progressView is allocated.
I have tried setNeedsDisplay on the cell itself as well, but no luck. What am I missing?
Calling [self tableView:tableView cellForRowAtIndexPath:indexPath]; is most likely the problem because this likely results in another cell being dequeued from the table. So you end up setting up a new cell, not the one currently being display.
Instead, do this:
CustomTableViewCell *cell = (CustomTableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
This directly asks the table for the cell. This should return the currently displayed cell if the row is visible.
Is it possible to load all cells of an UITableView when the view is loaded so that they are not loaded when I'm scrolling?
(I would show a loading screen while doing this)
Please, it's the only way at my project (sorry too complicate to explain why ^^)
EDIT:
Okay let me explain you, what I'm definite doing:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIdentifier = [NSString stringWithFormat:#"Identifier %i/%i", indexPath.row, indexPath.section];
CustomTableCell *cell = (CustomTableCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
NSDictionary *currentReading;
if (cell == nil)
{
cell = [[[CustomTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
UILabel *label;
UIView *separator;
if(indexPath.row == 0)
{
// Here I'm creating the title bar of my "table" for each section
}
else
{
int iPr = 1;
do
{
currentReading = [listData objectAtIndex:iPr-1];
iPr++;
} while (![[currentReading valueForKey:#"DeviceNo"] isEqualToString:[devicesArr objectAtIndex:indexPath.section]] ||
[readingresultsArr containsObject:[currentReading valueForKey:#"ReadingResultId"]]);
[readingresultsArr addObject:[currentReading valueForKey:#"ReadingResultId"]];
//
// ...
//
}
}
return cell;
}
My error happens in the do-while-loop:
"listData" is an array with multiple dictionaries in it.
My problem ist that when I’m scrolling my table slowly down, all is fine, but when I’m scrolling quickly to the end of the view and then I’m scrolling to the middle, I get the error that iPr is out of the array’s range. So the problem is, that the last row of the first section has already been added to the "readingresultsArr", but has not been loaded or wants to be loaded again.
That’s the reason why I want to load all cells at once.
You can cause all of the cells to be pre-allocated simply by calling:
[self tableView: self.tableView cellForRowAtIndexPath: indexPath];
for every row in your table. Put the above line in an appropriate for-loop and execute this code in viewDidAppear.
The problem however is that the tableView will not retain all of these cells. It will discard them when they are not needed.
You can get around that problem by adding an NSMutableArray to your UIViewController and then cache all the cells as they are created in cellForRowAtIndexPath. If there are dynamic updates (insertions/deletions) to your table over its lifetime, you will have to update the cache array as well.
put a uitableview on a uiscrollview
for example , you expect the height of the full list uitableview is 1000
then set the uiscrollview contentsize is 320X1000
and set the uitableview height is 1000
then all cell load their content even not visible in screen
In my case it was that I used automaticDimension for cells height and put estimatedRowHeight to small that is why tableview loaded all cells.
Some of the answers here and here suggest using automaticDimension for cells height and put mytable.estimatedRowHeight to a very low value (such as 1).
Starting with iOS 15 this approach seems not to work anymore. Hence, another way to achieve the table to "load" all cells could be by automatically scrolling to the last cell. Depending on the tables height and how many rows it can show some cells are discarded but each cell would be loaded and shown at least once.
mytable.scrollEnabled = YES;
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:cellCount - 1 inSection:0];
[mytable scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
mytable.scrollEnabled = NO;
If you want to scroll up again just scroll to the top as outlined here.
Following the comment that was made by juancazalla, I found that if you have a tableView that is using automaticDimension, loading all the cells at once can be best achieved by setting estimatedRowHeight to a low value (such as 1).
I have a UITableViewController and want to move down a cell in my table every time the accelerometer's X axis is greater then 0.5 (when this event occurs i increment a value named "TEST" ). How can i change the background of a cell that has it's indexPath.row equal to TEST ?
Here is how i try to access the method(from the accelerometer function) but it gives me an error :
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
if(acceleration.x>0.5)
TEST++;
if(TEST<0) TEST=0;
if(TEST>19) TEST=19;
NSIndexPath *temp = [[NSIndexPath alloc] initWithIndex:TEST];
[self tableView:[self tableView] didSelectRowAtIndexPath:temp];
}
I have 20 rows in my table (hence the clamp).
selectRowAtIndexPath:animated:scrollPosition:
Selects a row in the receiver identified by index path, optionally scrolling the row to a location in the receiver.
(void)selectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition
Parameters
indexPath
An index path identifying a row in the receiver.
animated
YES if you want to animate the selection and any change in position, NO if the change should be immediate.
scrollPosition
A constant that identifies a relative position in the receiving table view (top, middle, bottom) for the row when scrolling concludes. See “Table View Scroll Position”
here
NSIndexPath *temp = [[NSIndexPath alloc] initWithIndex:TEST];
[self.tableview selectRowAtIndexPath:temp animated:YES scrollPosition:UITableViewScrollPositionNone];
How about scrollToRowAtIndexPath:atScrollPosition:animated:?
My goal is to have a UITableViewCell slide off one side of the screen (like in Twitter) and then slide back on from the other side. I'm able to make the cell slide off the screen to the right, but I can't seem to figure out how to get it to slide back onto the screen from the left right after. Here's my code to slide it off to the right:
UITableViewCell *cell = [self cellForRowAtIndexPath:indexPath];
[cell.layer setAnchorPoint:CGPointMake(0, 0.5)];
[cell.layer setPosition:CGPointMake(cell.frame.size.width, cell.layer.position.y)];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position.x"];
[animation setRemovedOnCompletion:NO];
[animation setDelegate:self];
[animation setDuration:0.14];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
[cell.layer addAnimation:animation forKey:#"reveal"];
Thanks for any help!
UITableView provides methods to insert and remove rows with animation and handles all the animation for you. Try something like this:
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:myIndexPaths
withRowAnimation:UITableViewCellRowAnimationRight];
[tableView endUpdates];
And then to slide in a new cell:
[tableView beginUpdates];
[tableView insertRowsAtIndexPaths:myNewIndexPaths
withRowAnimation:UITableViewCellRowAnimationLeft];
[tableView endUpdates];
The beginUpdates/endUpdates methods cause the animations to be batched up and executed all at once.
Beware of 2 things:
With a lot of table data, the insert can take a long time (expecially if you are essentially replacing the whole table). Calling [tableView reloadData] works better in this case, but the deleteRowsAtIndexPath:withRowAnimation: can still be used for a nice visual effect.
You have to be sure that the actual data backing your table changes accordingly and correctly. That is to say, if you are removing or inserting rows form the table, then your array (or whatever) that is feeding the table must also correctly reflect how many rows are in the table at each step.
The behavior you want should be done by animating subviews of the cell, not the cell itself. The cell should stay in place in the table. Create a custom cell with the subviews you want in the contentArea and then slide those on/off as you like.
The standard UIView block based animations should work great for this as well.
how can I reload and animate just one cell/row ?
Right now i download some files. Everytime a file finished downloading, i call it's finished delegate and call [tableview reload].
But then the whole table reloads. And how can i animate the table, so that it doesn't blink in. For example a fade effect or so.
greets Max
Use the following UITableView instance method:
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
You have to specify an NSArray of NSIndexPaths that you want to reload. If you just want to reload. If you only want to reload one cell, then you can supply an NSArray that only holds one NSIndexPath. For example:
NSIndexPath* rowToReload = [NSIndexPath indexPathForRow:3 inSection:0];
NSArray* rowsToReload = [NSArray arrayWithObjects:rowToReload, nil];
[myUITableView reloadRowsAtIndexPaths:rowsToReload withRowAnimation:UITableViewRowAnimationNone];
You can see the UITableViewRowAnimation enum for all the possible ways of animating the row refresh. If you don't want any animation then you can use the value UITableViewRowAnimationNone, as in the example.
Reloading specific rows has a greater advantage than simply getting the animation effect that you'd like. You also get a huge performance boost because only the cells that you really need to be reloaded are have their data refreshed, repositioned and redrawn. Depending on the complexity of your cells, there can be quite an overhead each time you refresh a cell, so narrowing down the amount of refreshes you make is a necessary optimization that you should use wherever possible.
Swift 4 & 5
For TableView
let index = IndexPath(row: 0, section: 0)
tableView.reloadRows(at: [index], with: .automatic)
For CollectionView
let index = IndexPath(item: 2, section: 0)
collectionView.reloadItems(at: [index])
If you only need to update the text in the tableview cell..
UITableViewCell *cell = [_myTableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:1]];
cell.textLabel.text = [NSString stringWithFormat:#"%i", (int)_targetMaxDistanceSlider.value];
Swift
You can use
let selectedIndexPath = IndexPath(item:0 , section: 0)
self.tableView.reloadRows(at: [selectedIndexPath], with: .none)
in order to reload a specific cell.
Apple Document
done it with new syntax
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
To reload particular cells in a tableView at given indexPaths, you need to create an array of indexPaths and then call function reloadRowsAtIndexPaths on the tableView.
Here is the example:
Swift 5:
let indexPathsToReload = [indexPath1, indexPath2, indexPath3]
tableView.reloadRows(at: indexPathsToReload, with: .none)
Objective C:
NSArray *indexPaths = #[indexPath1, indexPath2, indexPath3];
[self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
or
NSArray *indexPaths = [NSArray arraywithobject:#"indexPath1", #"indexPath2", #"indexPath3",nil];
[self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];