iOS - Delayed "Touch Down" event for UIButton in UITableViewCell
I have a custom UITableViewCell, which is initialized by the following:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:self options:nil];
self = [nibArray objectAtIndex:0];
[self setSelectionStyle:UITableViewCellSelectionStyleNone];
[self.downButton setBackgroundImage:[UIImage imageNamed:@"button"] forState:UIControlStateNormal];
[self.downButton setBackgroundImage:[UIImage imageNamed:@"buttonSelected"] forState:UIControlStateHighlighted];
}
return self;
}
The button appears properly, with the appropriate background image, but the highlighted image does not instantly appear when the button is pressed/clicked. Instead, you have to hold it down for a second or two before the change occurs. Releasing the button, on the other hand, does have an instant change back to the original background image.
Trying to mitigate the tardy change when switching to the highlighted image, I put the change in the following method:
- (IBAction)downDown:(id)sender {
[self.downButton setBackgroundColor:[UIColor redColor]];
}
The method above is set for "Touch Down" (opposed to the more common "Touch Up Inside"), and I have removed the setBackgroundImage:forState:
for the highlighted state. Same problem as mentioned above. The color does eventually change to red, but only after clicking and holding on the button for a second or two.
I have a method for the button that is called when "Touch Up Inside" occurs, and that method executes without issue - regardless of whether I quickly tap the button, or click and hold on it for a length of time before releasing.
So why the delay for the "Touch Down" or UIControlStateHighlighted
? I'm trying to provide instant feedback to the user to show that the button has been pressed.
I can provide more code if needed, but these are the only bits that have anything to do with the background appearance.
This is caused by the UIScrollView
property delaysContentTouches
.
It used to be sufficient to just set that property to NO
for the UITableView
itself, but that will only work for subviews of the table that are not encased in another UIScrollView
.
UITableViewCells
contain an internal scroll view in iOS 7 so you will need to change the value of this property on the cell level for all cells with buttons in them.
Here is what you need to do:
1.in viewDidLoad
or somewhere similar once your UITableView
has been initialized, put this in:
self.tableView.delaysContentTouches = NO;
2.for iOS 7 support, in the initialization method for your UITableViewCell
(initWithStyle:reuseIdentifier:
or initWithCoder:
for NIBs), put this in at the end:
for (UIView *currentView in self.subviews)
{
if([currentView isKindOfClass:[UIScrollView class]])
{
((UIScrollView *)currentView).delaysContentTouches = NO;
break;
}
}
This is unfortunately not a 100% permanent solution as Apple can change the view hierarchy inside cells again in the future (perhaps moving the scroll view another layer down or something which would require you to nest another loop in there), but until they surface the class or at least the property to developers somehow, this is the best we've got.
Swift:
Firstly, turn off delays in UITableView
after it is loaded successfully, for example, inside viewDidLoad()
method:
someTableView.delaysContentTouches = false
Then turn off delays in the scroll views contained inside the UITableView
:
for case let scrollView as UIScrollView in someTableView.subviews {
scrollView.delaysContentTouches = false
}
Note for iOS7: You might have to disable the delays in UITableViewCell
. (Check Dima's answer). You might also find some other tips here.
i solve this problem in my code by subclassing UIButton,
Objective C
@interface TBButton : UIButton
@end
@implementation TBButton
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.highlighted = true;
[super touchesBegan:touches withEvent:event];
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.highlighted = false;
[super touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.highlighted = false;
[super touchesCancelled:touches withEvent:event];
}
@end
swift
class TBButton: UIButton {
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
highlighted = true
super.touchesBegan(touches, withEvent: event)
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
highlighted = false
super.touchesEnded(touches, withEvent: event)
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
highlighted = false
super.touchesCancelled(touches, withEvent: event)
}
}
swift 3
class TBButton: UIButton {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
isHighlighted = true
super.touchesBegan(touches, with: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
isHighlighted = false
super.touchesEnded(touches, with: event)
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
isHighlighted = false
super.touchesCancelled(touches, with: event)
}
}
Swift 3 version of Alex's answer:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
isHighlighted = true
super.touchesBegan(touches, with: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
isHighlighted = false
super.touchesEnded(touches, with: event)
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
isHighlighted = false
super.touchesCancelled(touches, with: event)
}