I have a UITableViewController with a segue where I'm trying to get the currently selected row:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([[segue identifier] isEqualToString:#"EditFilterElementSegue"]){
// Get the element that was clicked and pass it to the view controller for display
NSIndexPath *selectedIndexPath = [self.tableView indexPathForSelectedRow];
Element *element = [fetchedResultsController objectAtIndexPath:selectedIndexPath];
FilterElementTableViewController *vc = segue.destinationViewController;
vc.filter = filter;
vc.element = element;
}
}
The problem is indexPathForSelectedRow is returning nil. The docs say nil is returned "if the index path is invalid". If I add:
[self.tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] animated:YES scrollPosition:0];
selectedIndexPath = [self.tableView indexPathForSelectedRow];
selectedIndexPath is valid. So, I'm currently assuming the row is somehow getting unselected. Can anyone tell me how to figure out if this is the case and what might be causing it?
This code worked fine until I converted to using NSFetchedResultsController (which otherwise is working). I also use indexPathForSelectedRow in several other table view controllers where it works fine (one of which also uses NSFetchedResultsController).
I've also tried breaking in didSelectRowAtIndexPath, but the same thing happens there. (expected since it's called after prepareForSegue.)
A possible cause is that you've written something in one of the UITableViewDelegate methods -
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
}
It's a common practice to deselect the row which has just been selected.
Update:
In Swift 4.2, xcode 10:
Just comment the line
tableView.deselectRow(at: indexPath, animated: true)
in the function
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
Something that may help others - if you call reloadData() on your table view, it nilifies the indexPathForSelectedRow.
Be sure, you are not calling deselectRowAtIndexPath method before getting index path of selected Row.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES]; //add this line after setting indexPath
}
Ok In my case, I was calling from UIButton in storyboard by show segue.
And removing following line:
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
with below 2 lines worked for me:
CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
hope with my case "calling segue from UIButton on CELL" works for others also.
Ok, the bottom line is the scene in storyboard was messed up.
To figure it out I removed the segue and created a brand new empty UITableViewController class and only added enough code so I could click on a row and look at indexPathForSelectedRow. It was still nil.
I then created a new UITableViewController in storyboard and replaced the existing one. Now indexPathForSelectedRow worked.
I copied all the class code over, and the prototype cells over, and it still worked!
Out of curiosity I looked at the storyboard source for the two scenes and noticed that the scene that wasn't working was much longer than the new one. Looking at the storyboard outline it was obvious why, and surprising it worked at all. And, now I remember having trouble cutting & pasting a prototype cell. Guess I should look at the outline mode more often. See the screen shot below (the visual scenes look the same):
I also had this problem, when I replaced a UITableViewController class with a UIViewController class in code, and didn't update the storyboard i.e. I didn't replace the UITableViewController placeholder in storyboard, with a UIViewController storyboard.
Swift 4.2
You have to tell the tableView what cell is selected.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") else { return UITableViewCell() }
let data = products[indexPath.section][indexPath.row]
cell.update(data: data)
// Don't use cell.isSelected = true
if data == selectedData {
tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
}
return cell
}
In my case it was the UITableViewController had Clear on Appearance selected at the Storyboard as shown here
Related
i was trying to create a collection view with a list of cells. And when user hits a button it will random select one of the cell.
Now suppose there are only 10 cells. i.e numberOfItemsInSection delegate return 10.
The view controller is the data source for the collectionView. The collection view is called myCollectionView. And it has a property called selectedIndexPath
So ViewController:
#interface ViewController () < UICollectionViewDataSource>
#property (nonatomic, strong) NSIndexPath * selectedIndexPath;
#property (strong, nonatomic) IBOutlet UICollectionView *myCollectionView;
#end
Here's my random select code in the view controller:
-(void)chooseRandom{
NSInteger randomShadeIndex = arc4random_uniform((uint32_t)10);
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:randomShadeIndex inSection:0];
self.selectedIndexPath = indexPath;
[self.myCollectionView selectItemAtIndexPath:indexPath animated:YES scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
}
And here's my cellForItemAtIndexPath in my view controller
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
MyCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"MyCell" forIndexPath:indexPath];
cell.selected = self.selectedIndexPath == indexPath;
return cell;
}
And here's setSelected: method in MyCell:
- (void)setSelected:(BOOL)selected{
[super setSelected:selected];
if (selected){
self.backgroundColor = [UIColor redColor];
} else {
self.backgroundColor = [UIColor greenColor];
}
}
So now when i call chooseRandom from the button press. If the random cell to be selected is not visible(not in current screen), then there's a high chance that it doesn't end up having setSelected:YES called during the selectItemAtIndexPath:.(Or it gets called but sets the cell.selected to NO instead of YES). Meaning the resulting screen has none of the cell selected.
And the interesting thing is, when i tried touching the screen (without selecting any cell). It will called the setSelected: on the cell to be selected. So i think selectItemAtIndexPath: is bugged.
And this only happens, when prefetching enabled in interface builder is set to be enabled.(which is the default for ios 10).
And i've tried following ways to solve this, but none of them works:
Add [self.myCollectionView cellForItemAtIndexPath:indexPath].selected = YES; at the end of chooseRandom.
Use scrollToItemAtIndexPath along with method 1 instead of selectItemAtIndexPath:
I think either this is a bug or I ignore something completely. I've been stuck on this for hours and couldn't figure out why. Now i think it is most likely a bug for selectItemAtIndexPath with prefetching enabled is set.
Please help me and tell me if you encounter the same issue. Thanks!
EDIT:
not sure if the same question. this link has similar issue but with deselect
A UICollectionView, just like a UITableView, caches its cells. Meaning, that if you have 100 cells, and only 10 of them are visible, it will only create 10 cells and reuse them as you scroll, setting the model/data of the visible cells as you are scrolling, reusing the ones that are not visible anymore (to save memory). So the reason you are not able to select a cell that is not visible, might be because it hasn't been created.
Please check this if it helps you
Can download sample project
https://www.dropbox.com/s/oeo9p1h3e8xfpfj/CollectionView.zip?dl=0?
-(void)chooseRandom{
NSInteger randomShadeIndex = arc4random_uniform((uint32_t)50);
NSLog(#"%ld",(long)randomShadeIndex);
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:randomShadeIndex inSection:0];
self.selectedIndexPath = indexPath;
[self.myCollectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:true];
[_myCollectionView reloadData];
}
I am using xib to load the cell in the UITableViewController. There is a button on the cell by hitting which a modal pops up and when we hit something on that modal, I want to change the image in the cell. But I am not able to get the cell even if I am passing the indexPath.
I have used following code:
NSIndexPath* indexPath1 = [NSIndexPath indexPathForRow:sender inSection:0];
[self.tableView2 reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath1, nil] withRowAnimation:UITableViewRowAnimationNone];
I have written the code in the cellForRowAtIndexPath as follows:
if([[feedbackDictionary objectForKey:[NSString stringWithFormat:#"Key%li",indexPath.row]] isEqualToString:[NSString stringWithFormat:#"%li",indexPath.row]])
{
NSLog(#"Yay! Feedback");
cell.checkmark.image=[UIImage imageNamed:#"tick"];
}
else
{
NSLog(#"Oops! No feedback");
cell.checkmark.image=[UIImage imageNamed:#"orangetick"];
}
I am supposing that when reloadrows will run,it will reload that particular cell by calling cellForRowAtIndexPath. But it is of no help. The image is not changing at all.
Secondly, I tried to get the cell by using following
NSIndexPath *ip=[NSIndexPath indexPathForItem:button.tag inSection:0];
ExpandingCell *cell=(ExpandingCell*) [_tableView2 cellForRowAtIndexPath:ip];
if(cell==nil)
{
NSLog(#"No cell");
}
I am getting no cell as output. Please help.
At first, to debug it try to call [self.tableView2 reloadData]. If your image is changed, it means that you were called wrong indexPath for reloadRowsAtIndexPaths method.
Also button.tag could return wrong tag, because cells are reusable in UITableView, and it can be cell that already used before.
I have found the problem and fixed it. The problem was when modal pops up,it was loosing control over the view controller and that is why tableview is null and nothing was changed.
I have an UISearchBar in on top of my TVC. If the search is active it displays another tableView on top of the normal tableView (this is normal). Now i need to get the "searchTableView" in prepareForSegue() because I need to call:
var newIndexPath = table.indexPathForSelectedRow!
and this fails if you search something.
I also can't do the decision in didSelectRowAtIndexPath() because the segue is called to fast because it is 'linked' directly to the UITableViewCell. I also tried to create the segue from the ViewController but this also fails because the segue needs to end on the same ViewController again.
So basically I want to ask if anyone knows how to solve the error in the Code line above will doing a search.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSIndexPath *indexPath;
if (self.searchDisplayController.active)
{
indexPath = [self.searchDisplayController.searchResultsTableView indexPathForSelectedRow];
}
else
{
indexPath = [self.tableView indexPathForSelectedRow];
}
// Use the indexPath...
}
I know that dequeueReusableCellWithIdentifier:forIndexPath is called by tableView method inside the tableviewcontroller. And if I understand correctly, tableView method is called several times until all the cells are populated. But what I don't know is where do you get the value for the parameter IndexPath? I want to use dequeueReusableCellWithIdentifier:forIndexPath for a method that I created because I want to access my cell to copy some values of its properties.
NOTE:
I already populated my cell, which means that I successfully used the method tableView.
(Edit) ADDITIONAL INFO:
I'm trying to create a profile and edit profile tableviews. Inside the profile tableview, I displayed the name, address, contact#, etc., of the user. Also, I have a segue called edit profile. In the edit profile, I have textfields for each category (name, address, etc.). What I want to do is, if I edit the contents of the textfields, I should be able to display the new contents in my profile tableview. An example case would be: in the profile view I'm displaying -> name:human, address:earth (each in its own cell). Now if I go to editprofile tableview, I will edit the contents such that -> name:alien, address:mars. After that, there is a button called 'apply' to end editing of contents and go back to profile tableview. If I go back to profile view, the display should now be name:alien, address:mars and not name:human, address:earth.
Here is some code if it's any help. The code is called by a button in tableviewcontroller. "MyCell" is the class of my cell. This code is not working properly. I hope someone can help me fix this.
- (IBAction)updateCopies:(id)sender {
static NSString *ident = #"MyCell";
NSIndexPath *indexPath;
//create cell
MyCell *cell = [self.tableView dequeueReusableCellWithIdentifier:ident forIndexPath:indexPath];
//create variable for accessing cells
int row = [indexPath row];
_labelValues[row] = cell.textField.text
}
You should only use dequeueReusableCellWithIdentifier when you need to supply the table view with a cell to display. If you want to get the UITableViewCell object at a certain index, you should use cellForRowAtIndexPath.
Your problem
What you really need is a model class. You can then pass this to the edit controller, which changes the properties. Then when you return to the tableView, you can reload it and display the new properties.
What you could also do is create a delegate protocol for your edit profile controller, something like EditProfileViewControllerDelegate with something like:
protocol EditProfileViewControllerDelegate {
- (void)editProfileViewController:(EditProfileViewController *)controller didUpdateName:(NSString *)name address:(NSString *)address;
}
You can implement this delegate in your table view controller and use it to update the values when the text is changed. However, this quickly becomes unwieldy, I would not recommend it over using a proper model class.
You can get indexPath using CGPoint..You can use dequeueResusableCell for reusability of the cell..
- (IBAction)updateCopies:(id)sender {
CGPoint position = [sender convertPoint:CGPointZero
toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:position];
//create variable for accessing cells
MyCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
int row = [indexPath row];
_labelValues[row] = cell.textField.text
}
Hope it helps you..
Use this
- (IBAction)updateCopies:(id)sender {
MyCell *parentCell = (MyCell *)sender.superview;
while (![parentCell isKindOfClass:[MyCell class]]) { // iOS 7 onwards the table cell hierachy has changed.
parentCell = parentCell.superview;
}
UIView *parentView = parentCell.superview;
while (![parentView isKindOfClass:[UITableView class]]) { // iOS 7 onwards the table cell hierachy has changed.
parentView = parentView.superview;
}
UITableView *tableView = (UITableView *)parentView;
NSIndexPath *indexPath = [tableView indexPathForCell:(MyCell *)parentCell];
NSLog(#"indexPath = %#", indexPath);
}
Well I got what you want to accomplish.
Firstly, there is a delegate which is being called when you click/select a cell and go to the Edit Profile page. That delegate is
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
///
}
Make a global variable, say selectedIndexPath which holds the current cell index path which is being edited. Update this value each time when you go to edit profile page.
Like this
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
selectedIndexPath = indexPath;
// code to go to edit page...
}
Now in your updateCopies Method, do like this
- (IBAction)updateCopies:(id)sender {
//get the existing cell with the indexPath
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:#[selectedIndexPath]];
[self.tableView endUpdates];
//rest of your code goes here...
}
I am trying to execute an IBAction when a long-press is performed on a cell in a UITableView. The action involves the content of the cell so I need to get the indexPath in order to retrieve the content from a dictionary in local storage. The IBAction method is defined in the MasterViewController.m file which contains the UITableView methods and is subclassed from UITableViewController. I have tried all of the following, and they all return null instead of the indexPath for the cell.
UITableViewCell *cell = (UITableViewCell *)self;
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
I have also seen a few years-old answers to similar questions that use the position of the cell in the view, but I can't get any of those to work either.
UPDATE:
The IBAction, sendToPB, is being defined in a subclass of UITableViewController. There is a long-press gesture recognizer added to the cell in Interface Builder, with Sent Actions connected to sendToPB. The action is supposed to be copying the content of the cell to the clipboard when you long-press on the cell in the table view. All the ways I have tried so far return null for indexPath.
- (IBAction)sendToPB:(id)sender {
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
NSString *object = self.objects[indexPath.row];
UIPasteboard *pb = [UIPasteboard generalPasteboard];
NSString *pressedCellText = [[Data getAllNotes] objectForKey:object];
[pb setString: pressedCellText];
}
UPDATE:
I have found two problems with this approach. First, the long-press gesture doesn't actually select the row, which is why all of the options that used indexPathForSelectedRow don't work. Second, sender is the gesture recognizer, and not the cell or row, so using sender also produces a null value for indexPath. With these two factors in mind, how else can you detect which cell you performed the long-press on?
You can get indexPath Like This on longPressGesture!
-(void)handleLongPress:(UILongPressGestureRecognizer *)gestureRecognizer
{
CGPoint p = [gestureRecognizer locationInView:self.myTableView];
NSIndexPath *indexPath = [self.myTableView indexPathForRowAtPoint:p];
if (indexPath == nil) {
NSLog(#"long press on table view but not on a row");
}
else if (gestureRecognizer.state == UIGestureRecognizerStateBegan)
{
NSLog(#"long press on table view at row %d", indexPath.row);
}
else
{
NSLog(#"gestureRecognizer.state = %d", gestureRecognizer.state);
}
}
may Be this Link will help you a little more
Declare variable 1st in .h file as
NSIndexPath *hitIndex;
then on long press method u can get the position of cell & hence indexpath
(void)longPressMethod:(UIButton *)btn
{
CGPoint hitPoint = [btn convertPoint:CGPointZero toView:tbl_traits];
hitIndex = [tbl_traits indexPathForRowAtPoint:hitPoint];
}
You can do it using Gesture Recognizers. Hope these snippets help.......
In your .h file
#interface yourClass ()
{
UILongPressGestureRecognizer *longPressRecognizer;
}
In viewDidLoad,
longPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressDetected:)];
longPressRecognizer.minimumPressDuration = 2;
longPressRecognizer.numberOfTouchesRequired = 1;
longPressRecognizer.delegate = self;
In cellForRowAtIndexPath, just before return statement
[cell addGestureRecognizer:longPressRecognizer];
And at the end,
- (void) longPressDetected:(UILongPressGestureRecognizer *)recognizer
{
UITableViewCell *selectedCell = (UITableViewCell *)recognizer.view;
// Your required code here
}
Edit: Thanks to #RegularExpression
First be sure to set the delegate of your tableView
self.myTableView.delegate = self
I believe this is what you are looking for:
Swift
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//save the indexPath.row as an integer inside a property or pass it to your action
}
You can then save the index of from the above method or simple call your action passing that index inside the above method.
Objective-C
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
//save the indexPath.row as an integer inside a property or pass it to your action
}
If this is inside a UITableViewController subclass, then casting self (which is an instance of the UITableViewController subclass) to UITableViewCell will not return the cell selected.
There is really 2 ways of doing this:
1- The easy way: Don't use an IBAction and just implement the delegate methods:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
2- The relatively easy way: in your IBaction, you can get the selected cell by using the property:
self.tableview.indexPathForSelectedRow
This property will give you the indexpath of the selected cell.
If you attached the UILongPressGestureRecognizer to the cell by calling cell.addGestureRegognizer(_: _: _:) you should retrieve the cell from the sender by doing
let touchedView = sender.view as! UITableViewCell
Anyway, the best way to achieve this usually is by inspecting the dataSource instead of calling the UITableViewDataSource methods
You could subclass UITableViewCell and add an IVar for the index path and set that when cellForRowAtIndexPath is called for the UITableView dataSource protocol.