My code is working fine on iOS6 but crashing in iOS7.
I comparing the text field with table view cell text-field.
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
DLog(#"-> %#", textField.text);
PhotoViewCustomCell *cell = (PhotoViewCustomCell*)[[textField superview] superview];
NSIndexPath *indexPath = [tblPhotoDetail indexPathForCell:cell];
//PhotoViewCustomCell *cell = (PhotoViewCustomCell *)[tblPhotoDetail cellForRowAtIndexPath:indexPath];
PhotoInformation *objPhotoInfo = [selectedPhotos objectAtIndex:indexPath.row];
if ([textField isEqual:cell.mytextfield])
{ =========>crashing in this line
do something
}
else
{
do something
}
}
Error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UITableViewCellScrollView mytextfield]: unrecognized selector sent to instance 0xdc119a0'
Add additional superview call to get your cell. Looks like you are getting hidden UITableViewCellScrollView which goes in hierarchy immediately above contentView
PhotoViewCustomCell *cell = (PhotoViewCustomCell*)[[[textField superview] superview] superview];
The code in Accepted Answer is not the safer one and also can't be used for iOS 6. I'd rather recommend you to use this code. :
UIView *view = [textField superview];
while (![view isKindOfClass:[PhotoViewCustomCell class]]) {
view = [view superview];
}
PhotoViewCustomCell *cell = (PhotoViewCustomCell *)view;
Related
below code is working in ios 6 , but in ios 7 it is cause of crash app at the point of "indexPathForCell". help me to resolve this problem.. there is table view in which we are clicking in a cell and moving in a second view again table view is going to be load with data...but before that app crash..
Thanks in advance
UIView *view = [self superview];
// Find TableViewCell
if(view != nil && ![view isKindOfClass:[UITableView class]]) view = [view superview];
UIView *cellView = [self superview];
// Find TableViewCell
if(cellView != nil && ![cellView isKindOfClass:[UITableViewCell class]]) cellView = [cellView superview];
if(view != nil && cellView != nil) {
UITableViewCell *cell = (UITableViewCell*)cellView.superview.superview;
UITableView *tableView = (UITableView*)view;
if([tableView style] == UITableViewStyleGrouped)
{
NSIndexPath *path = [tableView indexPathForCell:(UITableViewCell*)cell];
if(path) {
int count = [tableView numberOfRowsInSection:[path section]];
UITableViewCell cell = (UITableViewCell)cellView.superview.superview.superview;
For ios7 you need to add one more superview.
Your app is getting crashed due to this
[tableView indexPathForCell:(UITableViewCell*)cell];
because cell is nil.
You need to add a check for ios 7 and ios 6.
For ios 7 Add another superview it will solve the purpose.
I have tried several approaches posted here, but I cannot get my table full of switches to return an index value for the cell of the changed switch. I am creating the view containing the table programmatically (no xib).
TableSandboxAppDelegate.m I instantiate the view controller in didFinishLaunchingWithOptions: with:
...
TableSandboxViewController *sandboxViewController = [[TableSandboxViewController alloc]
init];
[[self window] setRootViewController:sandboxViewController];
...
TableViewController.h file reads:
#interface TableSandboxViewController : UITableViewController
{
NSMutableArray *_questionOrder;
NSMutableArray *switchStates;
}
#end
TableViewController.m cellForRowAtIndexPath: reads:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MainCell"];
UISwitch *theSwitch = nil;
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"MainCell"];
theSwitch = [[UISwitch alloc] initWithFrame:CGRectZero];
theSwitch.tag = 100;
[theSwitch addTarget:self action:#selector(switchChanged:)
forControlEvents:UIControlEventValueChanged];
[cell.contentView addSubview:theSwitch];
} else {
theSwitch = [cell.contentView viewWithTag:100];
}
if ([[switchStates objectAtIndex:indexPath.row] isEqualToString:#"ON"]) {
theSwitch.on = YES;
} else {
theSwitch.on = NO;
}
return cell;
TableViewController.m -(IBAction)switchChanged:(UISwitch *)sender reads:
UITableViewCell *theParentCell = [[sender superview] superview];
NSIndexPath *indexPathOfSwitch = [self.tableView indexPathForCell:theParentCell];
NSLog(#"Switch changed at index: %d", indexPathOfSwitch.row);
My log result is always "Switch changed at index: 0". I feel like the problem is in that CGPoint line where I've tried combinations of replacements for "sender" ([sender superview], [[sender superview]superview], etc). I don't feel like that line is pointing to the view that displays the table.
What am I doing wrong?
Note added 10/9, 9:15 EDT: my goal is to be able to handle about 100 yes/no questions in the table, so reuse is a key. I want to scroll and have the table the state of each switch, as well as be able to retrieve them when leaving the view.
Tags is an okay solution, but a little clumsy because the cells - and therefore their subviews - are continually being reused, changing their rows - and therefore the tags they need.
Instead, I generally keep one of these around:
- (NSIndexPath *)indexPathWithSubview:(UIView *)subview {
while (![subview isKindOfClass:[UITableViewCell self]] && subview) {
subview = subview.superview;
}
return [self.tableView indexPathForCell:(UITableViewCell *)subview];
}
Then when I get an IBAction:
- (IBAction)someSubviewAction:(id)sender {
NSIndexPath *indexPath = [self indexPathWithSubview:(UIView *)sender];
// carry on from here
}
You may set switch view tag to row index. Instead of theSwitch.tag = 100;
do
-(UITableViewCell*)tableView:table cellForRowAtIndexPath:indexPth
{
UISwitch *theSwitch = nil;
if (cell == nil) {
...
// as per your example
[cell.contentView addSubview:theSwitch];
} else {
theSwitch = subviewWithClass(cell.contentView, [UISwitch class]);
}
theSwitch.tag = indexPath.row;
...
}
Add this helper function to replace viewWithTag: call
UIView *subviewWithClass(UIView *contentview, Class klass)
{
for (UIView *view in contentview.subviews)
if ([view isKindOfClass:klass])
return view;
return nil;
}
Then retrieve tag, that is a row index now, in your switchChanged function
-(IBAction)switchChanged:(UISwitch *)sender {
NSLog(#"Selected Switch - %d", sender.tag);
...
}
If you use something block-based (like https://github.com/brightsoftdev/iOS-Block-Based-Bindings/blob/master/UISwitch%2BBindings.m), you don't need to worry about getting the row, because you can reference the indexPath that is passed into tableView:cellForRowAtIndexPath: in your block.
Similar to #danh, I've come up with this solution using an extention which I've used multiple times.
#interface UIView (Find)
- (id)findSuperviewOfClass:(Class)class;
- (NSIndexPath *)findIndexPath;
#end
#implementation UIView (Find)
- (id)findSuperviewOfClass:(Class)class
{
return [self isKindOfClass:class] ? self : [self.superview findSuperviewOfClass:class];
}
- (NSIndexPath *)findIndexPath
{
UITableView *tableView = [self findSuperviewOfClass:[UITableView class]];
return [tableView indexPathForCell:[self findSuperviewOfClass:[UITableViewCell class]]];
}
#end
for iOS6+ you could maintain a NSMutableArray queuedSwitches
in -tableView:cellForrowAtIndexPath: you would take a switch, if not empty and places it on the custom cell and assign it to a property. If empty you create a new one.
in -tableView:didEndDisplayingCell:forRowAtIndexPath: you would add it to quededSwitches and remove it from it cell.
This will just allocate enough switches for visible cells and reuse them.
the switches are all wired up to one action.
-(void)switchAction:(UISwitch *)switch
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:[switch superView]];
//…
}
You could create a subclass of UISwitch and add an indexPath property, then just set the indexPath in cellForRowAtIndexPath:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
SwitchCell *returnCell = [tableView dequeueReusableCellWithIdentifier:#"SwitchCell" forIndexPath:indexPath];
returnCell.switch.indexPath = indexPath;
return returnCell;
}
I have a UITableView with custom tableview cells. One of my Cell has a UITextField, I'm handling the textfield delegate methods in the custom tableviewCell class. I need to reload the tableview once the user entered a text, I had done the below thing but not worked , Any idea please help me.
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
UITableView *parentTable = (UITableView *)self.superview;
[parentTable reloadData];
}
Register the view controller to receive notifications that the data has been changed, and have it refresh the table when it receives one. Then have the parser send it out.
Registering for it is easy:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(reloadTableView:)
name:#"reloadTableView"
object:nil];
Your refresh method needs to be set up to receive these notifications, along these lines:
- (void)reloadTableView:(NSNotification *)notif {
[self.yourTableName reloadData];
}
And it's important to stop observing in your ViewDidUnload:
[[NSNotificationCenter defaultCenter] removeObserver:self];
Then in the parser you need to simply add this when it's complete:
[[NSNotificationCenter defaultCenter] postNotificationName:#"reloadTableView"
object:nil];
The view controller (and anyone else observing the notification with that name) will get the message and perform its task.
Thanks..
CustomCell.h
#property (nonatomic, copy) void(^tapHandler)(NSUInteger tag);
CustomCell.m
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
NSUInteger tag = 10; //Need to change the tag
self.tapHandler(10);
}
Controller.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
// Whatever content was previously there. Add the below line in addition
cell.tapHandler = ^(NSUInteger tag){
[tableView reloadData];
};
return cell
}
Hope it helps!
first , the superview of the textfiled is not UITableview, it is tableviewcell or tableviewcell.contentview (depends on your code)
then, you just need to set the tableview as your view controller's member ,or property,
then
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
[self.tableview reloadData];
}
Try this,
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
[self.parentTable reloadData];
}
It's better to keep a reference of table view in CustomCell
// interface
UITableView *tableView;
// propery
#property (nonatomic)UITableView * tableView;
Then
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//your stuff
cell.tableView= tableView;
}
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
UITableView *parentTable = (UITableView *)textField.superview.superview;
[parentTable reloadData];
}
Don't reload whole table view for that small operation. Just reload the Cell in which the text field is. Also didnot forget to update your data source as table view cell fetch the data from the data source while the cell is loading.
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
[tableViewDataSource addObject:textField.text];//this will fill the data source with data of textfield
CustomCell *cell = (CustomCell *)textField.superview.superview;
NSIndexPath *indexPath = [parentTable indexPathForCell:cell];
[parentTable reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
By (UITableView *)self.superview; you will not get the table view. Try below code :
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
YourCell *cell = (YourCell *)[[textField superview] superview]; // you will get cell
UITableView *table = cell.superview; // you will get table view
[table reloadData];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
tableReload++;
NSString *CellIdentifier = [NSString stringWithFormat:#"cell %d%d",indexPath.row,tableReload];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
cell.textLabel.text = #"Hello";
}
// Configure the cell.
return cell;
}
Try this,
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
[self.yourTableName reloadData];
[self.yourTableName reloadInputViews];
}
I've got a TableView that I've created in a Storyboard via Static Cells.
The idea is that a section has a single row with a Label and a Switch. When the user taps the Switch "on", the section sprouts some new rows below. Somewhat similar to Wi-Fi settings in the Settings app (which adds new sections, but I'm adding new rows).
I'm creating this TableView via Storyboard like so:
And now, the action taken when the switch value changes:
- (IBAction)hitSwitch:(id)sender {
NSArray *indexPathsArray = [NSArray arrayWithObjects:[NSIndexPath indexPathForRow:1 inSection:0], [NSIndexPath indexPathForRow:2 inSection:0], nil];
UISwitch *theSwitch = sender;
[self.tableView beginUpdates];
if (theSwitch.on) {
[self.tableView insertRowsAtIndexPaths:indexPathsArray withRowAnimation:UITableViewRowAnimationAutomatic];
} else {
[self.tableView deleteRowsAtIndexPaths:indexPathsArray withRowAnimation:UITableViewRowAnimationAutomatic];
}
[self.tableView endUpdates];
}
I'd expect the normal table view data source methods to be called to display the cells for these new rows, so I've added the following code:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (self.addRowsSwitch.on) {
return 3;
} else {
return [super tableView:tableView numberOfRowsInSection:section];
}
}
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (!self.addRowsSwitch.on) {
return [super tableView:tableView cellForRowAtIndexPath:indexPath];
} else {
UITableViewCell *newCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
return newCell;
}
}
But according to my handy debugger, tableView:numberOfRowsInSection: is called, returns a value, and then the app crashes before tableView:cellForRowAtIndexPath is called.
2012-08-02 13:16:08.564 TableViewInsertTest[3186:f803] * Terminating
app due to uncaught exception 'NSRangeException', reason: '*
-[__NSArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]'
*** First throw call stack: (0x14b2022 0xeb2cd6 0x149e644 0x43bea5 0x2481a8 0x1f3688 0x1f4c2a 0x9743e 0xa50b7 0xa50e5 0x2604 0x14b3e99
0x1814e 0x180e6 0xbeade 0xbefa7 0x212c16 0x93b85d 0x1486936 0x14863d7
0x13e9790 0x13e8d84 0x13e8c9b 0x139b7d8 0x139b88a 0x15626 0x1ebd
0x1e25)
Is there something else I need to do to make this work, or is the type of behaivor that I'm trying to implement not supported?
I have a table view where each cell has a button accessory view. The table is managed by a fetched results controller and is frequently reordered. I want to be able to press one of the buttons and obtain the index path of that button's table view cell. I've been trying to get this working for days by storing the row of the button in its tag, but when the table gets reordered, the row becomes incorrect and I keep failing at reordering the tags correctly. Any new ideas on how to keep track of the button's cell's index path?
If you feel uncomfortable relying on button.superview, this method should be a little more robust than some of the other answers here:
UIButton *button = (UIButton *)sender;
CGRect buttonFrame = [button convertRect:button.bounds toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonFrame.origin];
This stopped working with iOS 7; check out Mike Weller's answer instead
- (IBAction)clickedButton:(id)sender {
UIButton *button = (UIButton *)sender;
UITableViewCell *cell = (UITableViewCell *)button.superview;
UITableView *tableView = (UITableView *)cell.superview;
NSIndexPath *indexPath = [tableView indexPathForCell:cell];
}
Or shorter:
- (IBAction)clickedButton:(id)sender {
NSIndexPath *indexPath = [(UITableView *)sender.superview.superview indexPathForCell:(UITableViewCell *)sender.superview];
}
Both are untested!
Crawling up view hierarchies with .superview (like all of the existing answers demonstrate) is a really bad way to do things. If UITableViewCell's structure changes (which has happened before) your app will break. Seeing .superview.superview in your code should set off alarm bells.
The button and its handler should be added to a custom UITableViewCell subclass and layed out there. That's where it belongs.
The cell subclass can then delegate out the button event through a standard delegate interface, or a block. You should aim for something like this:
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MyCustomCell *cell = ...;
// ...
cell.onButtonTapped = ^{
[self buttonSelectedAtIndexPath:indexPath];
}
// OR
cell.delegate = self;
// ...
}
(Note: if you go the block route, you will need to use a __weak self reference to prevent retain cycles, but I thought that would clutter up the example).
If you take the delegate route you would then have this delegate method to implement:
- (void)cellButtonPressed:(UITableViewCell *)cell
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
// ...
}
Your code now has full access to the appropriate context when it handles the event.
Implementing this interface on your cell class should be straightforward.
I don't know why I need to call the method superview twice to get the UITableViewCell.
Update:
Thank for Qiulang, now I got it.
"That's because SDK now has added a private class called UITableViewCellContentView for UITableViewCell, which is button's superview now." – Qiulang
UIButton *button = (UIButton *)sender;
UITableViewCell *cell = (UITableViewCell *)button.superview.superview;
UITableView *curTableView = (UITableView *)cell.superview;
NSIndexPath *indexPath = [curTableView indexPathForCell:cell];
I had this same issue also and built a simple recursive method that works no matter how many views deep you triggering control is.
-(NSIndexPath*)GetIndexPathFromSender:(id)sender{
if(!sender) { return nil; }
if([sender isKindOfClass:[UITableViewCell class]])
{
UITableViewCell *cell = sender;
return [self.tableView indexPathForCell:cell];
}
return [self GetIndexPathFromSender:((UIView*)[sender superview])];
}
-(void)ButtonClicked:(id)sender{
NSIndexPath *indexPath = [self GetIndexPathFromSender:sender];
}
I have created one Method for getting indexPath, Hope this will help you.
Create Button Action (aMethod:) in cellForRowAtIndexPath
-(void) aMethod:(UIButton *)sender
{
// Calling Magic Method which will return us indexPath.
NSIndexPath *indexPath = [self getButtonIndexPath:sender];
NSLog(#"IndexPath: %li", indexPath.row);
NSLog(#"IndexRow: %li", indexPath.section);
}
// Here is the Magic Method for getting button's indexPath
-(NSIndexPath *) getButtonIndexPath:(UIButton *) button
{
CGRect buttonFrame = [button convertRect:button.bounds toView:groupTable];
return [groupTable indexPathForRowAtPoint:buttonFrame.origin];
}
Use this Perfect working for me.
CGPoint center= [sender center];
CGPoint rootViewPoint = [[sender superview] convertPoint:center toView:_tableView1];
NSIndexPath *indexPath = [_tableView1 indexPathForRowAtPoint:rootViewPoint];
NSLog(#"%#",indexPath);
SWIFT 2 UPDATE
Here's how to find out which button was tapped
#IBAction func yourButton(sender: AnyObject) {
var position: CGPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
let indexPath = self.tableView.indexPathForRowAtPoint(position)
let cell: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath!)! as
UITableViewCell
print(indexPath?.row)
print("Tap tap tap tap")
}