reloading tableview and touch gestures - iphone

I have a tableview which is being reloaded as new content is added, using [tableview reloadData];
Trouble is I have a UILongPressGestureRecognizer on the TableCells in the Table and because the cells / table are being reloaded quite often the LongPress doesnt always have time to work as, I'm guessing it's internal timers are being reset when the cell/table is being reloaded.

Have you tried looking at the state of your UILongPressGestureRecognizers before [tableView reloadData] is called? For example:
// Returns |YES| if a gesture recognizer began detecting a long tap gesture
- (BOOL)longTapPossible {
BOOL possible = NO;
UIGestureRecognizer *gestureRecognizer = nil;
NSArray *visibleIndexPaths = [tableView indexPathsForVisibleRows];
for (NSIndexPath *indexPath in visibleIndexPaths) {
// I suppose you have only one UILongPressGestureRecognizer per cell
gestureRecognizer = [[tableView cellForRowAtIndexPath:indexPath] gestureRecognizers]
lastObject];
possible = (gestureRecognizer.state == UIGestureRecognizerStateBegan ||
gestureRecognizer.state == UIGestureRecognizerStateChanged);
if (possible) {
break;
}
}
return possible;
}
// ... later, where you reload the tableView:
if ([self longTapPossible] == NO) {
[tableView reloadData];
}
Let me know if it works!

Don't use reloadData if you want existing cells to remain. Instead, when you get new data, use the methods for Inserting and Deleting Cells to inform the table view exactly which cells have changed. The general procedure is:
You get new data.
Call beginUpdates
Call deleteRowsAtIndexPaths:withRowAnimation: to remove cells for any old items that have been deleted in the new data.
Call insertRowsAtIndexPaths:withRowAnimation: to add new cells for any items that have been added in the new data.
If you need to selectively replace a particular cell for some reason (and can't just update the existing cell's subviews with the new data), use reloadRowsAtIndexPaths:withRowAnimation:.
Call commitUpdates. At this point, your UITableViewDataSource methods must reflect the new data (e.g. tableView:numberOfRowsInSection: should reflect the changed count, and tableView:cellForRowAtIndexPath: should use the new items).
The table view will now call your data source methods as needed to update the display. Existing rows will not be changed.

Set a BOOL like aCellIsSelected to YES when you touch the cell
and just reload tableview if aCellIsSelected is NO

Related

how to set hidden property to NO for particular cell in tableView

I am new to iphone.I have a small doubt that is,I have a table view with 66 rows initially i placed a progress view to all rows but in viewdidload i set it to hidden for all those like below in tableViewClass(ShowProgressViewCont)
cell.progressView.hidden = YES;
here (cell) is the the reference of the CustomCell class.In this class i declare a progress view and setter and getter properties also be set here in this class
here in tableview there is a download button in each cell.If we click on that download button(for example in 66th cell).we have to remove the hidden property to the progress view for that particular 66th cell only.The remaining 65 cells should have the progress view in hidden only like that for all cells.
If any body know this please help me....
Are you familiar with the concept of table cells being reused?
viewDidLoad is not an appropriate place to manipulate the content of a single cell. It may work fine if the table is so small that all of its cells fit on the screen (in both orientations).
When there are more cells in the table than beeing displayed on the screen, then those cell that became invisible recently is being reused and displayed again.
So if there are 6 cells on the screen at a time then table cell no. 7 (sometimes 8) will be identical with cell no. 1.
cellForRowAtIndexPath would be a better place to hide/unhide certain views of a cell.
If it is a custom cell already then the cell's layoutSubViews could be appropriate, too. However, only in the tableViewController's cellForRowAtIndexPath you will have easy access to both, the table's data and the associated cell.
cellForRowAtIndexPath is called every time when a cell is about to become visible. Use the default reusing-mechanism to ensure that a proper cell object will be reused. It is pretty much straight forward.
Get the cell at index 65 and then cast it to your custom cell
UITableViewCell *cell = [tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:64 inSection:0]];
YourCustomCell *customCell = (YourCustomCell *)cell;
customCell.progressView.hidden = NO;
First set the row number in your download button in CellForRowAtInedexPath
downloadButton.tag = indexPath.row
[downloadButton addTarget:self action:#selector(actionDownload:) forControlEvents:UIControlEventTouchUpInside];
in actionDownload, make the IndexPath from button.tag and get the cell from "cellForRowAtIndexPath:",
finally update via reloadRowsAtIndexPaths: withRowAnimation:
Hide your progress view in your custom cell means make the default property of every progress view is hidden ,and then your button click method .
CutomeCell *cell = (CutomeCell *)[[button superview] superview];
[cell.progressView setHidden:NO];
NSIndexPath *rowToReload = [[self tableView] indexPathForCell:cell];
NSArray* rowsToReload = [NSArray arrayWithObjects:rowToReload, nil];
[UITableView reloadRowsAtIndexPaths:rowsToReload withRowAnimation:UITableViewRowAnimationNone];
may this help you ...
your model should be tracking the progress for every button clicked. for purposes of this answer, let's say the model is held in a property called modelInformationArray (which would contain an array of objects that relate to each cell in the table)
when the download button is clicked for a cell, modelInformationArray is modified in such a way that its object knows a download is being processed for it.
that object reports the downloading processing in a member called downloadingProcessingStarted. there are many other ways to do that part.
to get to the answer to your question … to then unhide the progress view, you would do something as follows in your UITableViewDataSource implementation of tableView:cellForRowAtIndexPath: .
- (UITableViewCell*)tableView:tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath {
YourCustomCell* cell = ...
if ([[self.modelInformationArray objectAtIndex:indexPath.row] downloadingProcessingStarted])
cell.progressView.hidden = NO;
}

Why do my table view cells disappear when reloaded using reloadRowsAtIndexPaths?

I have a bare-bones sample project here:
http://dl.dropbox.com/u/7834263/ExpandingCells.zip
In this project, a UITableView has a custom UITableViewCell. In each cell are 3 UIViews containing a label.
The goal is to expand the cell when tapped, then collapse it when tapped again. The cell itself must change it’s height to expose the subviews. Inserting or removing rows is unacceptable.
The demo project works almost as expected. In fact, in iOS 4.3 it works perfect. Under iOS 5, however, when the rows collapse, the previous cells magically disappear.
To re-create the problem, run the project in the simulator or device with iOS 5 and tap the first cell to expand it. Then tap the cell again to collapse it. Finally, tap the cell directly underneath it. The previous one disappears.
Continuing the tapping for each cell in the section will cause all the cells to disappear, to where the entire section is missing.
I’ve also tried using reloadData instead of the current setup, but that ruins the animations and feels a bit like a hack anyway. reloadRowsAtIndexPaths should work, but the question is why doesn’t it?
See images of what's happening below:
Table appears:
Cell expands:
Cell collapses:
Cell disappears (when tapping the cell underneath):
Keep repeating until the entire section disappears:
EDIT:
Overriding the alpha is a hack, but works. Here is another 'hack' that fixes it as well but WHY does it fix it?
JVViewController.m line 125:
if( previousIndexPath_ != nil )
{
if( [previousIndexPath_ compare:indexPath] == NSOrderedSame ) currentCellSameAsPreviousCell = YES;
JVCell *previousCell = (JVCell*)[self cellForIndexPath:previousIndexPath_];
BOOL expanded = [previousCell expanded];
if( expanded )
{
[previousCell setExpanded:NO];
[indicesToReload addObject:[previousIndexPath_ copy]];
}
else if( currentCellSameAsPreviousCell )
{
[previousCell setExpanded:YES];
[indicesToReload addObject:[previousIndexPath_ copy]];
}
//[indicesToReload addObject:[previousIndexPath_ copy]];
}
EDIT 2:
Made a few minor changes to demo project, worth checking out and reviewing JVViewController didSelectRowAtIndexPath method.
Your problem is in setExpanded: in JVCell.m, you are directly editing the frame of the target cell in that method.
- (void)setExpanded:(BOOL)expanded
{
expanded_ = expanded;
CGFloat newHeight = heightCollapsed_;
if( expanded_ ) newHeight = heightExpanded_;
CGRect frame = self.frame;
frame.size.height = newHeight;
self.frame = frame;
}
Update it to:
- (void)setExpanded:(BOOL)expanded
{
expanded_ = expanded;
}
Then remove the call to -reloadRowsAtIndexPaths:withRowAnimation: at line 163 of JVViewController.m and it will animate as expected.
-reloadRowsAtIndexPaths:withRowAnimation: expects different cells to be returned for the provided indexPaths. Since you are only adjusting sizes -beginUpdates & -endUpdates is sufficient to layout the table view cells again.
May be I am missing a point, but why dont you just use:
UITableViewRowAnimationNone
I mean instead of :
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];
use
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationNone];
To animate the height changes of a tableView just call.
[tableView beginUpdates];
[tableView endUpdates];
Don't call reloadRowsAtIndexPaths:
See Can you animate a height change on a UITableViewCell when selected?
The cell that is fading out is the previous cell that is not changing size. As the documentation of reloadRowsAtIndexPaths:withRowAnimation: states:
The table animates that new cell in as it animates the old row out.
What happens is the opacity is set to 1 then immediately set to 0 and so it fades out.
If both the previous and the new cell change size then it works as intended. This is because the begin/end Updates notice the height changes and create new animations on those cells overriding the reloadRowsAtIndexPaths:withRowAnimation: ones.
Your problem is due to abusing reloadRowsAtIndexPaths:withRowAnimation: to resize the cells when it's intended for loading new cells.
But you don't need reloadRowsAtIndexPaths:withRowAnimation: at all. Just change the expanded state of the cells and do the begin/end updates. That will handle all the animation for you.
As a side note I found the blue selection a little annoying, in JVCell set the selectedBackgroundView to the same image as the backgroundView (or create a new image that has the correct look of a selected cell).
EDIT:
Move the statement adding previousIndexPath_ to indicesToReload to the if statement (at line 132) so that it is only added if the previous cell was expanded and needs to resize.
if( expanded ) {
[previousCell setExpanded:NO];
[indicesToReload addObject:[previousIndexPath_ copy]];
}
This removes the case where the previous collapsed cell would disappear.
Another option would be to set previousIndexPath_ to nil when the current cell is collapsed and only set it when a cell expands.
This still feels like a hack. Doing both the reloadRows and the begin/end Updates causes the tableView to reload everything twice but both seem to be needed to animate correctly. I suppose if the table is not too large this won't be a performance problem.
Short, pragmatic answer: Changing UITableViewRowAnimationAutomatic to UITableViewRowAnimationTop solves the issue. No more disappearing rows! (tested on iOS 5.1)
Another short, pragmatic answer, since UITableViewRowAnimationTop is said to cause its own issues: Create a new cell view instead of modifying the existing one's frame. In a real app the data displayed in the cell view is supposed to be in the Model part of the app anyway, so if properly designed it shouldn't be a problem to create another cell view which displays the same data only in a different manner (frame in our case).
Some more thoughts regarding animating the reload of the same cell:
UITableViewRowAnimationAutomatic seems to resolve to UITableViewRowAnimationFade in some cases, which is when you see the cells fading away and disappearing. The new cell is supposed to fade in while the old one fades out. But here the old cell and the new one are one and the same - So, could this even work? In the Core Animation level, Is it possible to fade out a view AND fade it in at the same time? Sounds dubious. So the result is that you just see the fade out. This could be considered an Apple bug, since an expected behavior could be that if the same view has changed, the alpha property wouldn't be animated (since it can't animate both to 0 and to 1 at the same time), but instead just the frame, color etc. would be animated.
Note the problem is just in the animation's display - if you scroll away and back, everything will appear correctly.
In iOS 4.3 the Automatic mode might have been resolved to something other than Fade which is why things work there (as you write they do) - I didn't dig into that.
I don't know why iOS chooses the Fade mode when it does. But one of the cases it does is when your code asks reloads a previously tapped cell, which is collapsed, and is different than the current tapped cell. Note the previously tapped cell is always reloaded, this line in your code is always called:
[indicesToReload addObject:[previousIndexPath_ copy]];
This explains the magic disappearing cells scenario you have described.
By the way, the beginUpdates/endUpdates seem like a hack to me. This pair of calls is just supposed to contain animations, and there aren't any animations you are adding in addition to the rows you already asked to reload. All it did in this case is magically cause the Automatic mode to not choose Fade in some cases - But this just obscured the problem.
A final note: I played around with the Top mode and found it can also cause problems. For example plugging the following code makes cells disappear funkily:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
}
Not sure if there is a real issue here (similar to the one with fading a view in and out at the same time), or maybe an Apple bug.
I just downloaded your project & found this section of code in didSelectRowAtIndexPath delegate where reloadRowsAtIndexPaths is used.
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView beginUpdates];
[tableView endUpdates];
instead of the above why don't you try this?
[tableView beginUpdates];
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView endUpdates];
The reason i am suggesting this is that I believe reloadRowsAtIndexPaths:... only works when wrapped inbetween calls to:
- (void)beginUpdates;
- (void)endUpdates;
Outside of that, behavior is undefined and as you've discovered, fairly unreliable. Quoting relevant part of "Table View Programming Guide for iPhone OS":
To animate a batch insertion and deletion of rows and sections, call the insertion and deletion methods within an animation block defined by successive calls to beginUpdates and endUpdates. If you don’t call the insertion and deletion methods within this block, row and section indexes may be invalid. beginUpdates...endUpdates blocks are not nestable.
At the conclusion of a block—that is, after endUpdates returns—the table view queries its data source and delegate as usual for row and section data. Thus the collection objects backing the table view should be updated to reflect the new or removed rows or sections.
The reloadSections:withRowAnimation: and reloadRowsAtIndexPaths:withRowAnimation: methods, which were introduced in iPhone OS 3.0, are related to the methods discussed above. They allow you to request the table view to reload the data for specific sections and rows instead of loading the entire visible table view by calling reloadData.
There could be other valid reason but let me mull on this a bit, since i have your code too I could muck around with it. Hopefully we should figure it out...
When you use this method, you have to be sure that you are on the main thread.
Refreshing a UITableViewCell as follow should do the trick :
- (void) refreshTableViewCell:(NSNumber *)row
{
if (![[NSThread currentThread] isMainThread])
{
[self performSelector:_cmd onThread:[NSThread mainThread] withObject:row waitUntilDone:NO];
return;
}
/*Refresh your cell here
...
*/
}
I think you should not retain the previous indexPath when the cell is not expanded,
try by modifying you did select method like the below, its working fine..
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
BOOL currentCellSameAsPreviousCell = NO;
NSMutableArray *indicesToReload = [NSMutableArray array];
if(previousIndexPath_ != nil)
{
if( [previousIndexPath_ compare:indexPath] == NSOrderedSame ) currentCellSameAsPreviousCell = YES;
JVCell *previousCell = (JVCell*)[self cellForIndexPath:previousIndexPath_];
BOOL expanded = [previousCell expanded];
if(expanded)
{
[previousCell setExpanded:NO];
}
else if (currentCellSameAsPreviousCell)
{
[previousCell setExpanded:YES];
}
[indicesToReload addObject:[previousIndexPath_ copy]];
if (expanded)
previousIndexPath_ = nil;
else
previousIndexPath_ = [indexPath copy];
}
if(currentCellSameAsPreviousCell == NO)
{
JVCell *currentCell = (JVCell*)[self cellForIndexPath:indexPath];
BOOL expanded = [currentCell expanded];
if(expanded)
{
[currentCell setExpanded:NO];
previousIndexPath_ = nil;
}
else
{
[currentCell setExpanded:YES];
previousIndexPath_ = [indexPath copy];
}
// moving this line to inside the if statement blocks above instead of outside the loop works, but why?
[indicesToReload addObject:[indexPath copy]];
}
// commenting out this line makes the animations work, but the table view background is visible between the cells
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];
// using reloadData completely ruins the animations
[tableView beginUpdates];
[tableView endUpdates];
}
This problem is caused by returning cached cells in cellForRowAtIndexPath.
The reloadRowsAtIndexPaths is expecting to get fresh new cells from cellForRowAtIndexPath.
If you do that you will be ok ... no workarounds required.
From Apple doc:
"Reloading a row causes the table view to ask its data source for a new cell for that row."
I had a similar issue where I wanted to expand a cell when a switch is activated to display and extra label and button in the cell that is normally hidden when the cell is at its default height (44). I tried various versions of reloadRowsAtPath to no avail. Finally I decided to keep it simpler by adding a condition at heightForRowAtIndexPath like so:
override func tableView(tableView: UITableView,heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if ( indexPath.row == 2){
resetIndexPath.append(indexPath)
if resetPassword.on {
// whatever height you want to set the row to
return 125
}
}
return 44
}
And in whatever code you want to trigger the expanding of the cell just insert tableview.reloadData(). In my case it was when a switch was turned on to indicate the desire to reset a password.
#IBAction func resetPasswordSwitch(sender: AnyObject) {
tableView.reloadData()
}
There is no lag with this approach, no visible way to see that the table reloaded and the expansion of the sell is done gradually like you'd expect. Hope this helps someone.
#Javy, i noticed some weird behavior while testing your app.
While running on iPhone 5.0 simulator the previousIndexpth_ variable is of
class NSArray (it looks like.) here is the debugger output
(lldb) po previousIndexPath_
(NSIndexPath *) $5 = 0x06a67bf0 <__NSArrayI 0x6a67bf0>(
<JVSectionData: 0x6a61230>,
<JVSectionData: 0x6a64920>,
<JVSectionData: 0x6a66260>
)
(lldb) po [previousIndexPath_ class]
(id) $7 = 0x0145cb64 __NSArrayI
Whereas in iPhone 4.3 simulator it is of type NSIndexPath.
lldb) po [previousIndexPath_ class]
(id) $5 = 0x009605c8 NSIndexPath
(lldb) po previousIndexPath_
(NSIndexPath *) $6 = 0x04b5a570 <NSIndexPath 0x4b5a570> 2 indexes [0, 0]
Are you aware of this issue? Not sure whether this will help but thought of letting you know.
Try this hope it will help u Cell Expansion

UITableViewCell selector setSelected:animated: gets called many times?

I've discovered a strange behavior with setSelected:animated: in my custom UITableViewCell class. I discovered that this function gets called multiple times if I click on a cell in my table. I am wondering if this is normal behavior or a bug in my code.
To help with debugging, I've modified the setSelected:animated: function in my custom UITableViewCell class implementation as such:
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state.
if (selected)
NSLog(#"Yes %X", &self);
else
NSLog(#"No %X", &self);
}
If I click on a cell in the simulator, here is what I get in the console:
2011-03-22 22:05:26.963 marketPulse[3294:207] Yes BFFFDDD0
2011-03-22 22:05:26.964 marketPulse[3294:207] Yes BFFFDE30
You would think that I would get only 1 entry, since I only clicked on 1 cell.
And if I click on a different cell after that:
2011-03-22 22:07:11.014 marketPulse[3294:207] No BFFFD890
2011-03-22 22:07:11.016 marketPulse[3294:207] No BFFFDD00
2011-03-22 22:07:11.017 marketPulse[3294:207] Yes BFFFDDD0
2011-03-22 22:07:11.017 marketPulse[3294:207] Yes BFFFDE30
If I click on the same cell 2 times in a row, I get more than 2 Yes:
2011-03-22 22:08:41.067 marketPulse[3294:207] Yes BFFFDDD0
2011-03-22 22:08:41.068 marketPulse[3294:207] Yes BFFFDE30
2011-03-22 22:08:41.069 marketPulse[3294:207] Yes BFFFDE30
The more times I click the same cell, the more Yes I will get, and if I click on a different cell after that, I'll get a lot of No
I put a breakpoint before the NSLog, and looking at the debugger, it seems that all the repeated calls are coming from the same object.
Here is a part of my tableView:cellForRowAtIndexPath: function so you can see how my cells are being treated:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *ContentCellIdentifier = #"newsTableCellContent";
UITableViewCell *cell;
//index of cell data in tableData
NSUInteger index = indexPath.row / 2;
...
//content of story
else if( [indexPath row] % 2 == 1 ) {
cell = [tableView dequeueReusableCellWithIdentifier:ContentCellIdentifier];
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle]
loadNibNamed:#"newsTableCells"
owner:nil options:nil];
for (id currentObject in topLevelObjects) {
if ( [currentObject isKindOfClass:[newsTableCellContent class]] ) {
cell = currentObject;
break;
}
}
}
((newsTableCellContent *)cell).content.text = [[tableData objectAtIndex:index] description];
}
return cell;
}
Everything works fine so its hard to tell if the repeat calls to setSelected:animated: are intentional or not. If this is normal operation, I can make do with another method, but I would just like to know if this is suppose to happen or not.
Thanks
What's going on is simply that the UITableView keeps track of which cells are selected in the table.
Since cells are reused when you scroll through a large table view, the table view has to keep the list of selected cells separate. Not only that, but whenever it reuses a cell it has to set its selected property, because it may be using an old, invalid selected state from a previous incarnation.
When you tap a cell, several things happen: the previously selected cell is deselected (using setSelected:). The new cell is highlighted. It's de-highlighted (at least if you tap, instead of holding your finger down), and the setSelected: method is called because the new cell was selected. That's one.
The second call is a delayed perform call, possibly from a point where the table view didn't yet know what the final state of the table would be. This call goes to _selectAllSelectedRows, which, as the name suggests, calls 'setSelected:animated:' on all selected rows. That's the second call. The reason for this is most likely to address potential issues due to the the table view being in a "transition", but who knows.
Whether it's a bug or not is up for interpretation. A fix for the duplicate calls is to simply do:
if (self.selected == selected) return;
right before the call to super (you do not have to call super if self.selected == selected).
This is a normal behavior if you're using iPad. (it is only called once on iPhone).
In order to stop getting multiple "setSelected:YES" or multiple "setSelected:NO", all you have to do is this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Now, 1 click on any cell gives you:
1 entry of setSelected:YES animated:NO
1 entry of tableView: didSelectRowAtIndexPath:
1 entry of setSelected:NO animated:YES
So, calls are now stable regardless of what you do.
Ideally you should not be calling setSelected from anywhere in your code.
UIKit will take care of calling it.
If you want to show a cell/row as selected in cellForRowAtIndexPath method simply call
tableView.selectRowAtIndexPath(indexPath, animated: true, scrollPosition: .None)
for that specific indexPath.
Again never ever call setSelected explicitly unless you really mean to.
It should definitely be called when table is scrolled. Cells are reused, that means, if you scroll cells in invisible areas will be reused and reinitialized, including the call to setSelected, which is basically a lightweight property setter.
If you really want to see what's happening, add a NSLog to tableView:cellForRowAtIndexPath: which will log indexPath and the returned cell.
The entire log should give you a good understanding what happens inside and why.
I suppose it will be something like this (Clicked on IndexPath 1:1)
Give me cell on 1:0 (previously selected cell).
Deselect 1:0
Give me cell on 1:0 again (updated after deselection)
Deselect 1:0 (update selected flag on this cell and trigger animation)
Give me cell on 1:1
Select 1:1
Give me cell on 1:1 again (updated after selection)
Select 1:1 (update selected flag on this cell and trigger animation)
Clicking on a selected cell again is only slightly different - instead of triggering unselecting, it triggers another update.

Wipe a delete button out of table view cell

when pressing a row delete button on a table view, I do some validation, and if the user chooses to cancel the operation it all should rollback. Not only want to keep that row (what is happening), but also make disappear the delete button leaving only the "-" round button. How can I do that?
once again, thank you.
Assuming you are implementing your validations in tableView:commitEditingStyle:forRowAtIndexPath: method of your UITableViewDatasource protocol object, you should be able to set the editingAccessoryType and editingAccessoryView on the cell.
//After validation fails....
UITableViewCell *aCell;
aCell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
// validations are done and you need to ignore the delete
if ( aCell.showingDeleteConfirmation ){
aCell.editingAccessoryView = nil;
aCell.editingAccessoryType = UITableViewCellAccessoryNone;
}
If you want, you can wrap the changes in an animation block to animate the change.
Alternatively, you could toggle the editing state of the cell.
//After validation fails....
UITableViewCell *aCell;
aCell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
if ( aCell.showingDeleteConfirmation ){
aCell.editing = NO;
aCell.editingAccessoryView = nil;
aCell.editing = YES;
}

iPhone - How to determine in which cell a button was pressed in a custom UITableViewCell

I currently have a UITableView that is populated with a custom UITableViewCell that is in a separate nib. In the cell, there are two buttons that are wired to actions in the custom cell class. When I click one of the buttons, I can call the proper method, but I need to know which row the button was pressed in. The tag property for each button is coming as 0. I don't need to know when the entire cell is selected, just when a particular button is pressed, so I need to know which row it is so I can update the proper object.
Much easier solution is to define your button callback with (id)sender and use that to dig out the table row index. Here's some sample code:
- (IBAction)buttonWasPressed:(id)sender
{
NSIndexPath *indexPath =
[self.myTableView
indexPathForCell:(UITableViewCell *)[[sender superview] superview]];
NSUInteger row = indexPath.row;
// Do something with row index
}
Most likely you used that same row index to create/fill the cell, so it should be trivial to identify what your button should now do. No need to play with tags and try to keep them in order!
Clarification: if you currently use -(IBAction)buttonWasPressed; just redefine it as -(IBAction)buttonWasPressed:(id)sender; The additional callback argument is there, no need to do anything extra to get it. Also remember to reconnect your button to new callback in Interface Builder!
You could use the tag property on the button to specify which row the button was created in, if you're not using tags for anything else.
For a implementation that is not dependent on tags or the view hierarchy do the following
- (void)btnPressed:(id)sender event:(id)event
{
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchPoint = [touch locationInView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:touchPoint];
}
I have the same scenario. To achieve this, I derived a custom cell. I added two properties, section and row. I also added an owner, which would be my derived TableViewController class. When the cells are being asked for, I set the section/row based on the indexPath, along with the owner.
cell.section = indexPath.section
cell.row = indexPath.row
cell.owner = self
The next thing that I did was when I created the buttons, I associate the button events with the cell rather than with the tableViewController. The event handler can read the section and row entry and send the appropriate message (or event) to the TableViewController. This greatly simplifies house keeping and maintenance by leveraging existing methods and housekeeping and keeping the cell as self contained as possible. Since the system keeps track of cells already, why do it twice!
Even easier:
-(IBAction) buttonPressed {
NSIndexPath *myIndexPath = [(UITableView *)self.superview indexPathForCell: self];
// do whatever you need to do with the information
}
Here's a Swift example...
The UIControl class is the superclass of various iOS widgets, including UIButton, because UIControl provides the target/action mechanism that sends out the event notifications. Therefore a generic way to handle this is as follows:
func actionHandler(control: UIControl)
var indexPath = tableView.indexPathForCell(control.superview!.superview! as UITableViewCell)!
var row = indexPath.row
}
Here's an example of setting up a button control to deliver the action. Alternatively, create an #IBAction and create the action visually with Interface Builder.
button.addTarget(self, action: "actionHandler:", forControlEvents: .TouchUpInside)
You can downcast UIControl parameter to UIButton, UIStepper, etc... as necessary. For example:
var button = control as UIButton
The superview of control is the UITableViewCell's contentView, whose subviews are the UIViews displayed in the cell (UIControl is a subclass of UIView). The superview of the content cell is the UITableViewCell itself. That's why this is a reliable mechanism and the superviews can be traversed with impunity.
You can access the buttons superview to get the UITableViewCell that contains your button, but if you just need the row number, you can use the tag property like the previous post deacribes.
There are multiple methods to fix the problem.
You can use the "tag" property
Give the value indexPath.row as the tag value for the button.
btn.tag = indexPath.row;
Then in the button function, you can easily access the tag value and it will be the index for the clicked button.
-(void)btnClicked:(id)sender
{
int index = [sender tag];
}
You can use the layer property
Add the indexPath as the value in the layer dictionary.
[[btn layer] setValue:indexPath forKey:#"indexPath"];
This indexPath is accessible from the button action function.
-(void)btnClicked:(id)sender
{
NSIndexPath *indexPath = [[sender layer] valueForKey:#"indexPath"];
int index = indexPath.row;
}
With this method you can pass multiple values to the button function just by adding new objects in the dictionary with different keys.
[btnFavroite setAccessibilityValue:[NSString stringWithFormat:#"%d",indexPath.row]];
[btnFavroite setAccessibilityLabel:btnFavroite.titleLabel.text];
[btnFavroite addTarget:self action:#selector(btnFavClick:) forControlEvents:UIControlEventTouchUpInside];
-(void)btnFavClick:(id)sender{
UIButton *btn=(UIButton *)sender;
int index=[btn.accessibilityValue integerValue]]
}
this solution also works in IBAction connected using storyboard cell prototype
- (IBAction)viewMapPostsMarker:(UIButton*)sender{
// button > cellContentView > cellScrollView > cell
UITableViewCell *cell = (UITableViewCell *) sender.superview.superview.superview;
NSIndexPath *index = [self.mapPostsView indexPathForCell:cell];
NSLog(#" cell at index %d",index.row);
}