I want a superView respond to the slide event, but seems tableViewCell will catch this event first and make the cell enter the editing mode. Anyone has a solution?
I use a UITableViewController as a childViewController, I want the parentViewController to respond to the slide event.
if you do not want the table view cell to respond to the swipe at all then add a swipe gesture to the view.
UISwipeGestureRecognizer * guesture = [[UISwipeGestureRecognizer alloc]initWithTarget: action:#selector()];
then add it to your view.
[self.view addGestureRecogniser:guesture];
[guesture release];
Override tableView delegate method
-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
return ([tableView isEditing]) ? UITableViewCellEditingStyleDelete : UITableViewCellEditingStyleNone;
}
I've added a transparent UIButton to the UITableViewCell before and it catches the touch event before the UITableViewCell receives the touch event, so it might work for swipes as well. Just add the UISwipeGesture to the button subview to call your method.
Related
I have a UITableView with cells having some UITableViewRowActions. Our mockup uses a smaller font and a narrower UITableViewRowAction button. Is it possible to change the font and/or size of an UITableViewRowAction in any way?
Apple's documentation states it's impossible, and therefore I'm asking whether there is another way around.
UITableViewCell is made up of a backgroundView and a contentView, which lays over the backgroundView what you can do is add UIButtons as subViews to your backgroundView and add a UIPanGestureRecognizer over the cell. So when you pan the cell horizontally only contentView moves, UIButtons get exposed. So in cellForRowAtIndexPath
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *myCell;
myCell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:#"YOUR_IDENTIFIER"];
//adding button to a view that is added as the backgroundview
UIView *cellBackgroundUtilityView=[[UIView alloc]initWithFrame:myCell.frame];
[cellBackgroundUtilityView addSubview:<your button>]
[myCell setBackgroundView: cellBackgroundUtilityView]
//adding a gesture recognizer
UIPanGestureRecognizer *panningCell=[[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(yourPanningTableViewCellMethod:)];
[panningCell setDelegate:self];
[myCell addGestureRecognizer:panningCell];
}
when the gesture recognizer will be active this method will be called
-(void) yourPanningTableViewCellMethod:(id)sender
{
// gesture recognizer is active
UIPanGestureRecognizer* recognizer=(UIPanGestureRecognizer *) sender;
UITableViewCell *tableCell=(UITableViewCell *)recognizer.view;
//moving the contentView horizontally according to pan
[tableCell.contentView setFrame:CGRectMake([recognizer translationInView:self.view].x, tableCell.contentView.frame.origin.y, tableCell.frame.size.width, tableCell.frame.size.height)];
}
when button's selector is called
-(void)yourBackgroundViewButtonWasPressed
{
// button was pressed
// move content view of the cell back to origin
[tableCell.contentView setFrame:CGRectMake(0.0, tableCell.contentView.frame.origin.y, tableCell.frame.size.width, tableCell.frame.size.height)];
//do your stuff
}
My table view is in editing mode allowing my cells to be moved around. Is it possible to also add swipe to delete, NOT using the red-circle-delete button.
I want to be able to show just the move cell image, allowing moving, and also have a swipe to delete.
Is this possible?
This is what I currently have going on.
-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 1) {
return UITableViewCellEditingStyleDelete;
}
else
return UITableViewCellEditingStyleNone;
}
-(BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 1)
return YES;
else
return NO;
}
- (BOOL)tableView:(UITableView *)tableview shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath {
return NO;
}
It is definitely possible, though not as simple as you'd probably like. There are three parts to this:
The "swipe" gesture
The UI animation
The delete action
The "swipe" gesture should probably not be a UISwipeGestureRecognizer. UIPanGestureRecognizer will update as the user moves their finger around and matches what the user expects.
The animation should have two parts: update and complete. Update should simply pan the cell view inside of the cell to follow along with the user's finger. Once the gesture has reached a point of "completion" then animate the cell view going off screen.
Lastly, the delete action should be familiar if you've used the UITableViewDelegate methods. If not, here's a link to a helpful answer on the subject.
I would actually implement these out of order. First, programmatically test deleting a row. When that works, add an UIPanGestureRecognizer to call the delete row. Then move the cell view around with the pan gesture. Finally, animate the completion of the cell view moving around.
I have my table view that displays cells with some content and a custom accessory button.
When i tap a cell, a 5s work is launched. I don't want the user to tap a cell while the background work is not finished, so i do:
self.tableView.allowsSelection = NO;
It works fine, the user can't tap a cell.
I used to do:
self.tableView.userInteractionEnabled = NO;
But i want to scroll my table view while working in background. The issue is that i also can tap on the cell's accessory button.
How to avoid that not losing the table view scroll?
I could do:
- (void)didSelectAccessoryButton:(UIButton *)pButton onEvent:(id)event{
if(self.tableView.allowsSelection){
//Usual accessory button code
}
}
But the accessory button highlight would still be there, meaning at some point i'd need to do in cellForRowAtIndexPath:
[accessoryButton setShowsTouchWhenHighlighted:NO];
I just wish the allowsSelection = NO avoids the tap on accessory button too... So what's the better way ?
The best way i've found is to combine this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
//...
tableView.allowsSelection = NO;
[tableView reloadData];
//...
}
with this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
//...
accessoryButton.userInteractionEnabled = tableView.allowsSelection;
//or cell.accessoryView.userInteractionEnabled = tableView.allowsSelection;
//
}
And when the job is done, self.tableView.allowsSelection = YES;.
What bothers me with this solution is the need to check inside cellForRowAtIndexPath as to do so i also need to reload data; but in the end it's just 3 lines.
Thank you #virus for the simple "userInteractionEnabled to accessoryButton" idea.
I have a UICollectionView that has elements that can be dragged and dropped around the screen. I use a UILongPressGestureRecognizer to handle the dragging. I attach this recognizer to the collection view cells in my collectionView:cellForItemAtIndexPath: method. However, the recognizer's view property occasionally returns a UIView instead of a UICollectionViewCell. I require some of the methods/properties that are only on UICollectionViewCell and my app crashes when a UIView is returned instead.
Why would the recognizer that is attached to a cell return a plain UIView?
Attaching the recognizer
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
EXSupplyCollectionViewCell *cell = (EXSupplyCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
UILongPressGestureRecognizer *longPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:cell action:nil];
longPressRecognizer.delegate = self;
[cell addGestureRecognizer:longPressRecognizer];
return cell;
}
Handling the gesture
I use a method with a switch statement to dispatch the different states of the long press.
- (void)longGestureAction:(UILongPressGestureRecognizer *)gesture {
UICollectionViewCell *cell = (UICollectionViewCell *)[gesture view];
switch ([gesture state]) {
case UIGestureRecognizerStateBegan:
[self longGestureActionBeganOn:cell withGesture:gesture];
break;
//snip
default:
break;
}
}
When longGestureActionBeganOn:withGesture is called if cell is actually a UICollectionViewCell the rest of the gesture executes perfectly. If it isn't then it breaks when it attempts to determine the index path for what should be a cell.
First occurrence of break
- (void)longGestureActionBeganOn:(UICollectionViewCell *)cell withGesture:(UILongPressGestureRecognizer *)gesture
{
NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell]; // unrecognized selector is sent to the cell here if it is a UIView
[self.collectionView setScrollEnabled:NO];
if (indexPath != nil) {
// snip
}
}
I also use other properties specific to UICollectionViewCell for other states of the gesture. Is there some way to guarantee that the recognizer will always give me back the view that I assigned it to?
Views like UICollectionView and UITableView will reuse their cells. If you blindly add a gestureRecognizer in collectionView:cellForItemAtIndexPath: you will add a new one each time the cell is reloaded. If you scroll around a bit you will end up with dozens of gestureRecognizers on each cell.
In theory this should not cause any problems besides that the action of the gestureRecognizer is called multiple times. But Apple uses heavy performance optimization on cell reuse, so it might be possible that something messes up something.
The preferred way to solve the problem is to add the gestureRecognizer to the collectionView instead.
Another way would be to check if there is already a gestureRecognizer on the cell and only add a new one if there is none. Or you use the solution you found and remove the gestureRecognizer in prepareForReuse of the cell.
When you use the latter methods you should check that you remove (or test for) the right one. You don't want to remove gestureRecognizers the system added for you. (I'm not sure if iOS currently uses this, but to make your app proof for the future you might want to stick to this best practice.)
I had a similar problem related to Long-Touch.
What I ended up doing is override the UICollectionViewCell.PrepareForReuse and cancel the UIGestureRecognizers attached to my view. So everytime my cell got recycled a long press event would be canceled.
See this answer
I want to make UITableViewCell to behave like real button.
Until know I have been using the
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath`
trick, but this is not optimal because it doesn't behave like a button on tap/drag/release.
If you tap a cell row and drag your finger over the cell, it will not get selected when you release your finger (but a button would launch its action in the same case).
Is there any simple way of making a UITableViewCell to behave like a real button without resorting to insert an actual UIButton inside the cell?
You can just create table view cells with a button in them, set the buttons tag to the row so you can workout which row the button belongs to when you receive the button event. Just make sure you reset the buttons tag when you return a reused table view cell instead of creating a new one.
Subclass the UITableViewCell and use the following method:
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated {
[super setHighlighted:highlighted animated:animated];
if (highlighted) {
_backgroundImageView.image = [UIImage imageNamed:#"img_h"];
}
else {
_backgroundImageView.image = [UIImage imageNamed:#"img"];
}
}
Where img is a plain image and img_h is a highlighted version of that image.
One way is to create a UIButtton of size of your cell and added it to the cell.
Or else you could simply add a UITapGestureRecognizer to your UITableViewCell and that will do the work for you.