This code will restore a cell selection after refresh the UITableView using reloadData:
NSIndexPath *selectedIndexPath = [self.tableView indexPathForSelectedRow];
[self.tableView reloadData];
[self.tableView selectRowAtIndexPath:selectedIndexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
Of course this solution doesn't works, when I add new cells at the top of my TableView. How i can keep selection, when adding cells at the top?
That really depends of your data source. The most common example for simple cases, if it's an array, then you can "remember" the object every time you select a row (tableView:didSelectRowAtIndexPath:)
myObjecyt = [myArray objectAtIndex:indexPath.row];
, and after [table reloadData];, you select the table index:
NSUInteger index = [myArray indexOfObject:myObject];
if (index != NSNotFound) {
NSINdexPath *selectedIndexPath = [NSIndexPath indexPathForRow:index inSection:0];
[self.tableView selectRowAtIndexPath:selectedIndexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
}
But, as I said, it really depends on lot of factors, this is just an example for the basic setup, where the table is populated with data from myArray, without rearranging and omitting items (and has only one section).
You could simply keep track of the number of cells you are adding at the top and increase the index of the row you are selecting with selectRowAtIndexPath: based on that.
Alternatively, if you have any sort of UI element (UIButton, UILabel etc.) then you can set the tag of that element in didSelectRowAtIndexPath: and then access the cell that has the element with that tag (after reloading) using something like UITableViewCell *selectCell = (UITableViewCell *)[[self.view viewWithTag:100] superview];
As #Frane Poljak said, the user tap on a tableView indicates that the object contained in that row is what interests to the user. You have to keep the object as reference to select the cell that contains it again.
Let's say you have an array of Object, you can do this
enum Value
{
case NotSelected
case Selected(object)
}
private var selectionState : Value = .NotSelected
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
selectionState = Selected(dataSource[indexPath.row])
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
[...]
switch selectionState {
case .Selected(let object as! Object):
if dataSource[indexPath.row] == object { cell.selected = true }
else { cell.selected = false }
default : cell.selected = false
}
}
Try this:
-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if([indexPath row] == ((NSIndexPath*)[[tableView indexPathsForVisibleRows] lastObject]).row){
//end of loading
int anyIndex = <any number of cell>
NSIndexPath *anyIndexPath = [[NSIndexPath alloc] indexPathForRow:anyIndex inSection:0];
UITableViewCell *anyCell = [table cellForRowAtIndexPath: firstIndexPath]
anyCell.selectionStyle = UITableViewCellSelectionStyle.Default
}
}
This should work.
Related
I have a setting screen that is a UITableView with rows of settings. When user open that screen I load stored settings and filled to UITextField etc... Everything was fine.
But there are some of the checkmark settings, I've been trying to check this in programmatically way but is not work, here is my code:
-(void)viewDidAppear:(BOOL)animated{
[self LoadCurrentRecord];
if(_previousValid)
{
NSIndexPath *regionFromData = [NSIndexPath indexPathForRow:_regionAutoCheck inSection:3];
[self.tableView cellForRowAtIndexPath:regionFromData].accessoryType = UITableViewCellAccessoryCheckmark;
}
[self.tableView reloadData];
}
In fact, I can see data can load by check to this category but I didn't see checkmark icon.
Any idea?
You must need to set it in this method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell; // Create Cell
if(_previousValid && indexPath.row == _regionAutoCheck && indexPath.section == 3 )
{
// SET CHECKMARK
} else {
// SET NONE
}
}
Here is how you should work with table- and collectionViews:
Have a collection of dedicated data-elements for each of which a cell will be used to display its data. In your case this would be some kind of Region object.
Implement the table/collectionview datasource methods that return number of cells in section and number of sections based on that collection.
Make sure you configure each cell ONLY in -tableView:cellForRowAtIndexPath: / -collectionView:cellForItemAtIndexPath:, just like PKT's answer. This cell might be reused, so assume any changes you made to this type of cell you need to update here.
When data changes, create an indexPath for the object that changes based on its position in the collection from 1. Then call '-reloadRowAtIndexPath:'/ -reloadItemAtIndexPath for that object. This will cause the tableView/collectionView to call -tableViewcellForRowAtIndexPath:/-`collectionView:cellForItemAtIndexPath:' again. As this method now contains the logic for configuring the cell based on your data-object, everything is in sync.
I can't stress enough how important 1. is. Simplest example is: each section is an array of data objects. If you have multiple sections, you can add each array to another array:
// one section:
NSArray *myData =
#[
#[dataItem1, dataItem2, dataItem3]
#[dataItem4, dataItem5, dataItem6]
];
- (NSUInteger) numberOfSectionsInTableView: (UITableView *) tableView
{
return myData.count;
}
- (NSUInteger) tableView: (UITableView *) tableView numnberOfRowsInSection: (NSUInteger) section
{
NSArray *dataForSection = myData[section];
return dataForSection.count;
}
- (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath:
{
NSArray *dataForSection = myData[indexPath.section];
MyObject *dataObject = dataForSection[indexPath.row];
NSString * cellID = #"myCellID";
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier: cellID];
if (nil == cell)
{
// create a cell
// ...
}
// configure the cell based on the data object
if (dataObject.isBlue)
{
cell.contentView.backgroundColor = [UIColor blueColor];
}
else
{
// N.B.! don't forget the else clause, as cells are reused, so this
// cell might be recycled from a cell that was used to display
// the data of a blue data object before.
cell.contentView.backgroundColor = [UIColor blueColor];
}
return cell;
}
Finally, I've found a really simple solution for this case.
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexPath.row == _regionAutoCheck && indexPath.section == 3)
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else{
cell.accessoryType = UITableViewCellAccessoryNone; // for other cases remove it
}
}
UPDATE
After that, add [self.tableView reloadData]; into -(void)viewDidAppear:(BOOL)animated and you will see it works.
That's all.
Thanks all for help
I am trying to update a specific dynamic cells font size from a -(void) method or some way without using cellForRowAtIndexPath. Is this possible?
Thanks
If you know the position of the index, as follows:
First create indexPath by row at position:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:0];
Then access to cell:
MyCell *cell = (MyCell *)[self.tableView cellForRowAtIndexPath:indexPath];
Call
[self.view.tableView reloadRowsAtIndexPaths:#[your_index_path] withRowAnimation:UITableViewRowAnimationAutomatic];
inside your method. that will cause row update
You can not update a cell without having a call to: cellForRowAtIndexPath.
When you want to update font size in a cell, then reload the cell. In your cellForRowAtIndexPath method add a conditional statement to check the indexPath of the cell you want a different font size and edit different font size. Do not forget to add else condition so that when the cell reloads it will make the font size normal for other cells.
However if you want somehow. Then you can use global reference variable to the cell. But this will only work if the cell is displaying on screen.
Here is the example code:
#interface TableViewController ()
{
UITableViewCell *aCell;
}
#end
#implementation TableViewController
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *theCell = [tableView dequeueReusableCellWithIdentifier:#"TableViewCellIdentifier" forIndexPath:indexPath];
if(indexPath.row == SPECIFIC_ROW_NUMBER){
aCell = theCell;
theCell.tag = SPECIFIC_ROW_NUMBER;
}else if (theCell.tag == SPECIFIC_ROW_NUMBER){
// If the cell is dequeued
aCell = nil;
theCell.tag = 99999; // some unused row number
}
}
- (void)someMethod {
if(theCell != nil){
aCell.titleLabel.font = NEW_FONT;
}
}
#end
In one of my methods I want to iterate between the cells I have and perform changes on them, something like:
for (UITableViewCell *cell in ___________) {
cell.accessoryType = accessoryType = UITableViewCellAccessoryCheckmark;
}
so is there a property that will complete the ____________ ?
tnx!
There are only visible cells array self.tableView.visibleCells
for (UITableViewCell *cell in self.tableView.visibleCells) {
cell.accessoryType = accessoryType = UITableViewCellAccessoryCheckmark;
}
The UITableView itself doesn't know much about its content. That's the job of it's dataSource delegate. See documentation for UITableViewDataSource
Rather than thinking of the problem in a linear fashion. Think of the problem as event driven. If you want to modify the cell before it is displayed, you can check out tableView:willDisplayCell:forRowAtIndexPath: on the UITableViewDelegate
edit: given that you are looking to set the accesoryType you should probably try using tableView:cellForRowAtIndexPath:. Since that's where you're either dequeueing a reusable cell or manually alloc] init]'ing one .
Have you tried: yourTableView.visibleCells
Chikabuz is right.
BUT you should avoid making changes to the cells like this. Instead you should tell the tableView that your cells are updated and provide the changes in - [id<UITableViewDataSource> tableView:cellForRowAtIndexPath:] like so:
- (IBAction)toggleSelecting:(id)sender {
[[self tableView] setEditing:![[self tableView] isEditing] animated:YES];
// or with custom use with own BOOL property:
// [self setEditing:![self editing]];
[[self tableView] reloadRowsAtIndexPaths:[[self tableView] indexPathsForVisibleRows] withRowAnimation:UITableViewRowAnimationFade];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// cell setup here...
// or with custom use with own BOOL property:
// if ([self editing]) {
if ([[tableView] isEditing]) {
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
}
return cell;
}
Probably you can't get all cells in one array. But you can try to use the following:
let cellsArray: []
func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//Your data source methods
let cell = ......
....
cellsArray.append(cell)
}
This way you can get array of cells after view loads. I think there is no any other way to do that.
I'm having a problem in animating the addition or removal of a row in a UITableView which has a different height than other rows.
The following gifs demonstrats the issue with rows of the default height (44pts) and an larger row (100pts) being inserted and removed. The one on the left is a screen recording from the simulator (the new cell ending up covering row five is a different issue) and the one on the right is a mockup of what it should do.
In my case, I have a bunch of rows, each 60pts in height. When a button in the cell is tapped, an "edit" cell will slide out from underneath, pushing lower cells down. This edit cell is 180pts high. When I call insertRowsAtIndexPaths:withRowAnimation: or deleteRowsAtIndexPaths:withRowAnimation:, the animation assumes the wrong height of 60pts, instead of the 180pts it should be
This means that in the case of UITableViewRowAnimationTop the new cell appears at -60pts from the position it will end up at, and slides down to its new position; about a third of the animation it should be doing. Meanwhile, the row below animates smoothly from its starting position to 180pts downward, exactly as it should.
Has anyone worked out an actual solution to this? some way to tell the new row what hight it's supposed to be for the animation?
Below is the code I am using to hide and show the edit row. I'm using a TLSwipeForOptionsCell to trigger the edit, but it's easily replicated using for example tableView:didSelectRowAtIndexPath:
-(void)hideEditFields{
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:#[[NSIndexPath indexPathForItem:editFormVisibleForRow+1 inSection:0]] withRowAnimation:UITableViewRowAnimationTop];
editFormVisibleForRow = -1;
[self.tableView endUpdates];
}
-(void)cellDidSelectMore:(TLSwipeForOptionsCell *)cell{
NSIndexPath* indexPath = [self.tableView indexPathForCell:cell];
// do nothing if this is the currently selected row
if(editFormVisibleForRow != indexPath.row){
if(editFormVisibleForRow >= 0){
[self hideEditFields];
// update the index path, as the cell positions (may) have changed
indexPath = [self.tableView indexPathForCell:cell];
}
[self.tableView beginUpdates];
editFormVisibleForRow = indexPath.row;
[self.tableView insertRowsAtIndexPaths:#[
[NSIndexPath indexPathForItem:editFormVisibleForRow+1 inSection:0]
] withRowAnimation:UITableViewRowAnimationTop];
[self.tableView endUpdates];
}
}
-(NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return _dataSource.count + (editFormVisibleForRow >= 0 ? 1 : 0);
}
-(CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
int row = indexPath.row;
if(editFormVisibleForRow >= 0 && row > editFormVisibleForRow && row <= editFormVisibleForRow + 1){
return 180.0f;
}
else return 60.0;
}
Poking around a bit, it seems like this is a common issue with no clear answer. Most of the similar questions I've found here on SO are unanswered or offer workarounds specific to the asker's situation. (examples: Problem with RowAnimation, Custom UITableViewCell height yields improper animation, UITableView animation glitch when deleting and inserting cells with varying heights).
Also, instead of trying to make one triple-sized edit row, I tried making three smaller rows and animating them, but this was not suitable because they all appeared at once. I also tried animating them one after the other but the easing made it look odd, with an obvious 3-step animation occurring, instead of the whole edit view sliding out of view in one motion.
Edit: I've just noticed that if I call reloadRowsAtIndexPaths:withRowAnimation: UITableViewRowAnimationNone for the row above the one I'm trying to animate, it changes the behaviour of the animation; namely the animation assumes the height is 0pts, as demonstrated in the following animation. It's closer to what I want, but still not right, as the animation speed is wrong and it leaves a gap (in my app this means the background
colour pokes through)
The solution is pretty straight forward. You need to insert the cell with a height of 0, then change the height to the expected size and then call beginUpdates and endUpdates.
Here is some pseudo code.
var cellHeight: CGFloat = 0
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let dynamicHeightIndex = 2
if indexPath.row == dynamicHeightIndex {
return cellHeight
} else {
return tableView.rowHeight
}
}
func insertCell() {
// First update the data source before inserting the row
tableView.insertRows(at: [someIndexPath], with: .none)
cellHeight = 200
tableView.beginUpdates()
tableView.endUpdates()
}
To remove the cell, you'll need to wait until the updates animation completes before removing from the table view.
In iOS 11 you have the func performBatchUpdates(_:completion:) which provides a completion block. For previous versions you can try using the CATransaction completion.
cellHeight = 0
CATransaction.begin()
CATransaction.setCompletionBlock({
self.tableView.deleteRows(at: [someIndexPath], with: .none)
})
tableView.beginUpdates()
tableView.endUpdates()
CATransaction.commit()
This, using didSelectRowAtIndexPath worked for me:
#interface TableController ()
#property (strong,nonatomic) NSArray *theData;
#property (strong,nonatomic) NSIndexPath *pathToEditCell;
#end
#implementation TableController
- (void)viewDidLoad {
[super viewDidLoad];
self.theData = #[#"One",#"Two",#"Three",#"Four",#"Five",#"Six",#"Seven",#"Eight",#"Nine"];
[self.tableView reloadData];
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return ([indexPath isEqual:self.pathToEditCell])? 100: 44;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return (self.pathToEditCell == nil)? self.theData.count: self.theData.count + 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([indexPath isEqual:self.pathToEditCell]) {
RDEditCell *cell = [tableView dequeueReusableCellWithIdentifier:#"EditCell" forIndexPath:indexPath];
return cell;
}else{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
cell.textLabel.text = self.theData[indexPath.row];
return cell;
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
if (self.pathToEditCell == nil) { // first time selecting a row
self.pathToEditCell = [NSIndexPath indexPathForRow:indexPath.row +1 inSection:indexPath.section];
[self.tableView insertRowsAtIndexPaths:#[self.pathToEditCell] withRowAnimation:UITableViewRowAnimationAutomatic];
}else if ([self.pathToEditCell isEqual:indexPath]){ // deletes the edit cell if you click on it
self.pathToEditCell = nil;
[self.tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}else{ // close the old edit cell and adds another if you click on another cell while the edit cell is on screen
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:#[self.pathToEditCell] withRowAnimation:UITableViewRowAnimationFade];
self.pathToEditCell = indexPath;
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
}
}
For the deletions, I like the looks of the "fade" option for the animation, but "top" also was ok.
I am using [tableview reloadData]; to reload the data in my UItableView, however when I use this I loose my highlight on my UItableVewCell.
I would like to know the best way to reinstate this highlight.
I set a tempIndexPath when the user selects the cell they edit the information then I call reloadData, then inside cellForRowAtIndexPath I use this code to re-highlight the cell however its not working.
if ([tempIndexPath isEqual:indexPath]) {
cell.selected = YES;
}
This code keeps the highlighted selection, and is safe with resorting/inserts/repositioning since it keeps a reference to the underlying object from the model, instead of the index path. It also scrolls to the selection, which is helpful when the updated model causes the selection to be moved out of the current scroll position's frame.
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//Save the selected object at this row for maintaining the highlight later in reloadData
_selectedItem = [_items objectAtIndex:indexPath.row];
}
- (void) reloadData
{
[_itemsTable reloadData];
//Reselect and scroll to selection
int numItems = _items.count;
for (int i = 0; i < numItems; i++) {
NSDictionary *dict = [_numItems objectAtIndex:i];
if (dict == _selectedItem) {
//This is more reliable than calling the indexPathForSelectedRow on the UITableView,
// since the selection is cleared after calling reloadData
NSIndexPath *selectedIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
[_itemsTable scrollToRowAtIndexPath:selectedIndexPath atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
[_itemsTable selectRowAtIndexPath:selectedIndexPath animated:FALSE scrollPosition:UITableViewScrollPositionNone];
break;
}
}
}
Save the selected row, reload your table view's data and select the row again.
NSIndexPath* selectedIndexPath = [tableView indexPathForSelectedRow];
[tableView reloadData];
[tableView selectRowAtIndexPath:selectedIndexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
You should know that this is dangerous, because if new items were added to the table view before the selected row, the selection will not be the correct cell. You should calculate how many rows were inserted before the row and adjust the selectedIndexPath accordingly.