UIButton not showing highlight on tap in iOS7

24,060

Solution 1

In that tableview you just add this property.

tableview.delaysContentTouches = NO;

And add in cellForRowAtIndexPath after you initiate the cell you just add below code. The structure of the cell is apparently different in iOS 6 and iOS 7.
iOS 7 we have one control UITableViewCellScrollView In between UITableViewCell and content View.

for (id obj in cell.subviews)
{
    if ([NSStringFromClass([obj class]) isEqualToString:@"UITableViewCellScrollView"])
    {
        UIScrollView *scroll = (UIScrollView *) obj;
        scroll.delaysContentTouches = NO;
        break;
    }
}

Solution 2

Since iOS 8 we need to apply the same technique to UITableView subviews (table contains a hidden UITableViewWrapperView scroll view). There is no need iterate UITableViewCell subviews anymore.

for (UIView *currentView in tableView.subviews) {
    if ([currentView isKindOfClass:[UIScrollView class]]) {
        ((UIScrollView *)currentView).delaysContentTouches = NO;
        break;
    }
}

This answer should be linked with this question.

Solution 3

I tried to add this to the accepted answer but it never went through. This is a much safer way of turning off the cells delaysContentTouches property as it does not look for a specific class, but rather anything that responds to the selector.

In Cell:

for (id obj in self.subviews) {
     if ([obj respondsToSelector:@selector(setDelaysContentTouches:)]) {
          [obj setDelaysContentTouches:NO];
     }
}

In TableView:

self.tableView.delaysContentTouches = NO;

Solution 4

For a solution that works in both iOS7 and iOS8, create a custom UITableView subclass and custom UITableViewCell subclass.

Use this sample UITableView's initWithFrame:

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];

    if (self)
    {
        // iterate over all the UITableView's subviews
        for (id view in self.subviews)
        {
            // looking for a UITableViewWrapperView
            if ([NSStringFromClass([view class]) isEqualToString:@"UITableViewWrapperView"])
            {
                // this test is necessary for safety and because a "UITableViewWrapperView" is NOT a UIScrollView in iOS7
                if([view isKindOfClass:[UIScrollView class]])
                {
                    // turn OFF delaysContentTouches in the hidden subview
                    UIScrollView *scroll = (UIScrollView *) view;
                    scroll.delaysContentTouches = NO;
                }
                break;
            }
        }
    }
    return self;
}

Use this sample UITableViewCell's initWithStyle:reuseIdentifier:

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];

    if (self)
    {
        // iterate over all the UITableViewCell's subviews
        for (id view in self.subviews)
        {
            // looking for a UITableViewCellScrollView
            if ([NSStringFromClass([view class]) isEqualToString:@"UITableViewCellScrollView"])
            {
                // this test is here for safety only, also there is no UITableViewCellScrollView in iOS8
                if([view isKindOfClass:[UIScrollView class]])
                {
                    // turn OFF delaysContentTouches in the hidden subview
                    UIScrollView *scroll = (UIScrollView *) view;
                    scroll.delaysContentTouches = NO;
                }
                break;
            }
        }
    }

    return self;
}

Solution 5

What I did to solve the problem was a category of UIButton using the following code :

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];


    [NSOperationQueue.mainQueue addOperationWithBlock:^{ self.highlighted = YES; }];
}


- (void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesCancelled:touches withEvent:event];

    [self performSelector:@selector(setDefault) withObject:nil afterDelay:0.1];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesEnded:touches withEvent:event];

    [self performSelector:@selector(setDefault) withObject:nil afterDelay:0.1];
}


- (void)setDefault
{
    [NSOperationQueue.mainQueue addOperationWithBlock:^{ self.highlighted = NO; }];
}

the button reacts correctly when I press on it in a UITableViewCell, and my UITableView behaves normally as the delaysContentTouches isn't forced.

Share:
24,060
Eric
Author by

Eric

Software Engineer in San Francisco, California. Main focus on iOS development. iOS Engineer at TuneIn Technical Editor / Author at raywenderlich.com Blogging at my personal site Graduated from California Polytechnic State University, San Luis Obispo with a Bachelors in Computer Engineering. I am currently making iOS apps for a living and loving every moment of it!

Updated on July 05, 2022

Comments

  • Eric
    Eric almost 2 years

    I've looked at a ton of posts on similar things, but none of them quite match or fix this issue. Since iOS 7, whenever I add a UIButton to a UITableViewCell or even to the footerview it works "fine", meaning it receives the target action, but it doesn't show the little highlight that normally happens as you tap a UIButton. It makes the UI look funky not showing the button react to touch.

    I'm pretty sure this counts as a bug in iOS7, but has anyone found a solution or could help me find one :)

    Edit: I forgot to mention that it will highlight if I long hold on the button, but not a quick tap like it does if just added to a standard view.

    Code:

    Creating the button:

    UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
        button.titleLabel.font = [UIFont systemFontOfSize:14];
        button.titleLabel.textColor = [UIColor blueColor];
        [button setTitle:@"Testing" forState:UIControlStateNormal];
        [button addTarget:self action:@selector(buttonPressed:) forControlEvents: UIControlEventTouchDown];
        button.frame = CGRectMake(0, 0, self.view.frame.size.width/2, 40);
    

    Things I've Tested:

    //Removing gesture recognizers on UITableView in case they were getting in the way.

    for (UIGestureRecognizer *recognizer in self.tableView.gestureRecognizers) {
       recognizer.enabled = NO;
    }
    

    //Removing gestures from the Cell

    for (UIGestureRecognizer *recognizer in self.contentView.gestureRecognizers) {
           recognizer.enabled = NO;
        }
    

    //This shows the little light touch, but this isn't the desired look

    button.showsTouchWhenHighlighted = YES;
    
  • Eric
    Eric over 10 years
    You are a genius, thank you! I edited your subview loop to make it a little more future proof. Posted the edit, so once it gets approved will hopefully help others out
  • subramani
    subramani over 10 years
    sure,thanks for posting this. I hope this will be helpful for others also.
  • Fred Collins
    Fred Collins over 10 years
    Thanks man. I tried to write the condition ([obj isKindOfClass:[UITableViewCellScrollView class]]) but it raises an error saying that it doesn't know about a class named UITableViewCellScrollView. Why?
  • Ryan Romanchuk
    Ryan Romanchuk over 10 years
    Brilliant, been struggling with this one for a while now.
  • Ryan Romanchuk
    Ryan Romanchuk over 10 years
    @subramani i just noticed in one of my tableviews scrolling has stopped, i need to investigate why, but the only difference is that this cell has multiple buttons. Any ideas what could be happening? @Eric?
  • Ryan Romanchuk
    Ryan Romanchuk over 10 years
    @subramani actually I think it's related to having multiple scrollviews
  • Eric
    Eric over 10 years
    @RyanRomanchuk I've used it with multiple buttons. I know that this does stop scrolling if the start of the scroll (where you tap down) is a button. It's because now the touch gets sent to the button and not the scroll view. I still do this because I think showing the button highlight is more important than making the scroll work even if they start the scroll on the button. That may just be in my case though
  • Ryan Romanchuk
    Ryan Romanchuk over 10 years
    @Eric strange..my entire scrollview doesn't scroll anymore (regardless of where I'm tapping), but only on one specific controller. The only difference with this view controller is that it has multiple scrollviews.
  • Eric
    Eric over 10 years
    @RyanRomanchuk Are you nesting scrollviews, or are there just multiple side by side. If you are nesting, maybe touches aren't being passed to the subscrollViews
  • Ryan Romanchuk
    Ryan Romanchuk over 10 years
    @Eric there is a scrollview positioned above the uitableview acting as a photo browser/parallax, i haven't investigated much, seems bizarre that it's causing problems, but it's the only variable i can think of at the moment
  • sonxurxo
    sonxurxo over 10 years
    @subramani You are my today's favorite person :) Thank you! BTW, sadly SO is full of other wrong answers to the same question...
  • Eric
    Eric about 10 years
    So basically setting one cells ts_delaysContentTouches sets the entire tableViews delaysContentTouches correct? So this wouldn't allow dynamic cell setting, but rather setting the whole tableview as a whole?
  • TomSwift
    TomSwift about 10 years
    @Eric - no. I consciously decided not to also have it set the tableview delaysContentTouches. I feel that is a side effect that is unexpected since it would be changing a view upstream in the hierarchy. You still need to set tableView.delaysContentTouches = NO.
  • Marc Etcheverry
    Marc Etcheverry about 10 years
    If you are nesting scrollviews you might want to subclass the scrollview and override the built in check for UIControl. - (BOOL)touchesShouldCancelInContentView:(UIView *)view { if ([self isRunningiOS7OrLater]) { return YES; } return [super touchesShouldCancelInContentView:view]; }
  • Mika
    Mika about 10 years
    This is better than the accepted answer. One point I would add is that it's better to add this right before the cell is returned rather than after it's instantiated because setting up the cell can reset setDelaysContentTouches
  • user
    user about 10 years
    +1 Mikael, that made the difference for me. +1 for the answer because respondsToSelector is a much better solution than class comparison.
  • bmeulmeester
    bmeulmeester almost 10 years
    This answer also helped me fix a bug with a regular UIScrollView. Thanks! 😁
  • Quentin
    Quentin over 9 years
    Unfortunately this code will not work in iOS8 as the UITableViewCellScrollView has been removed from the UITableViewCell view hierarchy.
  • Quentin
    Quentin over 9 years
    See my answer: stackoverflow.com/a/26049216/194191 for a solution that works in iOS7 and iOS8
  • Gank
    Gank over 9 years
    How to write the function initWithFrame? PriceDetailTableView.m:66:18: No visible @interface for 'UITableViewController' declares the selector 'initWithFrame:'. My code:- (id) initWithFrame:(CGRect)frame style:(UITableViewStyle)style { self = [super initWithFrame:frame ]; self = [super initWithStyle:style]; return self; }
  • Gank
    Gank over 9 years
    - (id) initWithFrame:(CGRect)frame style:(UITableViewStyle)style { self=[super initWithFrame:frame style:style]; return self; }PriceDetailTableView.m:80:17: No visible @interface for 'UITableViewController' declares the selector 'initWithFrame:style:'
  • Gank
    Gank over 9 years
    and UITableViewCell's initWithFrame will not enter in
  • Quentin
    Quentin over 9 years
    @Gank: I updated the post for a little more clarity. You need to implement - (id)initWithFrame:(CGRect)frame in your PriceDetailTableView and - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier in your PriceDetailTableViewCell. Does that make more sense?
  • Donnit
    Donnit over 9 years
    You made my day! Works like a charm!
  • Rusik
    Rusik about 9 years
    Category? You sure? :)
  • SmileBot
    SmileBot about 9 years
    Actually, this is the cleanest answer on the page IMO and works as well in ios 7 as any of the others, and works properly in ios 8. That is, I'm getting ios 7 to only show the flash when the cell is selected as with all the other answers. Am I missing something?
  • SmileBot
    SmileBot about 9 years
    Ok, I got it, I see that you have to give the button a background color in ios 7 to get the flash but not in ios 8. Got it. Great answer!! Thanx.
  • Nailer
    Nailer about 9 years
    Works as a charm to put in a subclass of UIButton. Much love <3
  • thomasdao
    thomasdao almost 9 years
    How to write it in Swift? I cannot do self = super.init(style: style, reuseIdentifier: reuseIdentifier) in Swift: Cannot assign to 'self' in a method. Also, view.class is not working
  • lenhhoxung
    lenhhoxung almost 9 years
    True solution for storyboard and ios 8 and above
  • lenhhoxung
    lenhhoxung almost 9 years
    Good solution, particularly if we are using UITableView on another view, not in a UIViewController (that case, delaysContentTouches doesn't work).
  • Abhishek
    Abhishek almost 9 years
    Write content of If (self) {} in awakeFromNib If you are using storyboard in both objective C and swift
  • Evren Bingøl
    Evren Bingøl almost 9 years
    This is a great solution. All i found was examples based on view being a subclass of UIScrollView or TableView. I did not have anyof them as a parent class. Nothing else worked. this is great.
  • B-Man
    B-Man over 8 years
    Worked for me. Thanks yo! :) Perfect Solution
  • surfrider
    surfrider over 8 years
    Thanks! It works, but it's a weird solution. I can't believe Apple didn't take care of this behaviour without inspecting of subviews.
  • pnizzle
    pnizzle about 8 years
    Not sure why no one has tried this, but has worked for me. Question is why would the tableview, which is a scrollview subclass itself, have another scrollview inside of it. Apple :|
  • Leverin
    Leverin about 8 years
    Why is this gem hidden here in the bottom?
  • antoine
    antoine about 8 years
    That solution works fine if you don't use the button selected state. The exact parameters for swift 2 are "touches: Set<UITouch>, withEvent event: UIEvent?".
  • RadioLog
    RadioLog over 5 years
    Faced the same issue on iOS11. There only tableView.delaysContentTouches = NO was needed to fix the problem. Looks like they FINALLY fixed that (it seems to be present on iOS10 though)
  • iadcialim24
    iadcialim24 over 3 years
    Im facing this in iOS14