58

I'm running into a small issue in my app.

I essentially have a series of UIButtons added as subviews in a UIScrollView which is part of a nib. Every time I tap on a button there is a noticeable delay before the button is highlighted. I essentially have to hold it for about half a second before the button dims and appears selected.

I'm assuming this is because the UIScrollView needs to determine if the touch is a scroll or if it's a touch that is meant for a subview.

Anyways, I'm a little unsure on how to proceed. I simply want the button to appear selected as soon as I tap it.

Any help is appreciated!

Edit:

I've tried setting delaysContentTouches to NO but scrolling becomes almost impossible since a majority of my scrollView is filled with UIButtons.

twernt
  • 20,271
  • 5
  • 32
  • 41
Jeff
  • 2,818
  • 3
  • 29
  • 31
  • Consider checking out this answer instead of the accepted one: http://stackoverflow.com/a/19656611/378024 – gblazex Feb 16 '16 at 21:18

7 Answers7

61

Jeff's solution wasn't quite working for me, but this similar one does: http://charlesharley.com/2013/programming/uibutton-in-uitableviewcell-has-no-highlight-state

In addition to overriding touchesShouldCancelInContentView in your scroll view subclass, you still need to set delaysContentTouches to false. Lastly, you need to return true rather than false for your buttons. Here's a modified example from the above link. As commenters suggested, it checks for any subclass of UIControl rather than UIButton specifically so that this behavior applies to any type of control.

Objective-C:

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        self.delaysContentTouches = false;
    }

    return self;
}

- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
    if ([view isKindOfClass:UIControl.class]) {
        return true;
    }

    return [super touchesShouldCancelInContentView:view];
}

Swift 4:

override func touchesShouldCancel(in view: UIView) -> Bool {
    if view is UIControl {
        return true
    }
    return super.touchesShouldCancel(in: view)
}
jlong64
  • 928
  • 10
  • 13
  • 3
    this works for me and not the accepted answer. Thanks. – Jibran K Feb 09 '14 at 00:06
  • 3
    Excellent solution! If you're using interface builder just select the scrollView and uncheck 'delays content touches' in the attributes inspector. – Tim Apr 10 '14 at 06:06
  • 2
    Tested all solutions posted here on iOS 8. Only this one prevents touch delays on buttons and doesn't interfere with scrolling. – Mihai Damian Apr 01 '15 at 13:00
  • 2
    **This is THE solution**. You can get springboard-like effect (touch animation starts early without any delay, but cancels if you still decide to scroll). – gblazex Feb 16 '16 at 21:17
  • 3
    if view is UIControl is even better – Aliaksandr Bialiauski Dec 12 '16 at 16:38
  • 1
    You could probably check the `UIControl` instead of the `UIButton` to make sure your "Button" like subclasses of `UIControl` behave the same. – Jakub Truhlář Sep 28 '18 at 09:52
  • Thanks for the suggestion Alexander and Jakub, I agree. I updated the examples accordingly. – jlong64 Sep 30 '18 at 03:04
  • for those of you who came here for an UICollectionViewController. You should make a UICollectionView subclass file and write the code in it. – EFE Feb 21 '19 at 14:17
  • Has anyone encountered the problem if a UIswitch is placed in a table? it seems that in this case the method is not called at all – zslavman Jan 22 '22 at 23:24
  • delaysContentTouches is great and for some reason ChatGPT didn't know this. Hmmmm... nice! – Dan Rosenstark Jun 06 '23 at 00:39
40

Ok I've solved this by subclassing UIScrollView and overriding touchesShouldCancelInContentView

Now my UIButton that was tagged as 99 highlights properly and my scrollview is scrolling!

myCustomScrollView.h:

@interface myCustomScrollView : UIScrollView  {

}

@end

and myCustomScrollView.m:

@implementation myCustomScrollView

    - (BOOL)touchesShouldCancelInContentView:(UIView *)view
    {
        NSLog(@"touchesShouldCancelInContentView");

        if (view.tag == 99)
            return NO;
        else 
            return YES;
    }
etolstoy
  • 1,798
  • 21
  • 33
Jeff
  • 2,818
  • 3
  • 29
  • 31
  • 6
    Wow, I just came across this answer and it completely solved the issue I was having. Wish I could give you +1000 points! Thank you so much. – Jeremy Fuller Dec 04 '10 at 22:41
  • doesn't work in my case, but it very close to your.(tested in simulator only). Maybe this solution time is up. – Dima Deplov Jul 26 '13 at 17:57
30

Try to set UIScrollView delaysContentTouches property to NO.

Vladimir
  • 170,431
  • 36
  • 387
  • 313
  • 1
    Sorry I probably should've been more clear. If I set delaysContentTouches to NO the touch responds immediately. However, scrolling pretty much becomes impossible since touches are interpreted as button presses rather than actual scrolling. – Jeff Sep 04 '10 at 14:41
  • 1
    @Jeff You can use `canCancelContentTouches` to allow scrolling even if the touch is initially treated as a button touch, so long as the targets of the touch support cancellation. The buttons will still highlight initially when touched, but as soon as you start to scroll will unhighlight. – devios1 Sep 06 '14 at 17:20
  • @chaiguy I am looking for this behaviour, exactly like you described, but no luck so far. If I set `delaysContentTouches = NO` and `canCancelContentTouches = YES` I am not able to scroll when touching UIButton at all. – Darrarski Oct 08 '14 at 22:31
  • @Darrarski Sorry I was thinking of a `UITableViewCell` not a `UIButton`. It appears the button doesn't support cancelled touches because it allows you to drag off the button to cancel it, overriding the scroll view ever receiving the drag. I'm not sure how to achieve this with a `UIButton`, but you could probably do it by subclassing `UIButton`. – devios1 Oct 08 '14 at 22:54
3

Storyboard solution: Select the scroll view, open the "Attributes Inspector" and uncheck "Delays Content Touches"

enter image description here

José
  • 3,112
  • 1
  • 29
  • 42
2

Swift 3 :

scrollView.delaysContentTouches = false
Oubaida AlQuraan
  • 1,706
  • 1
  • 18
  • 19
2

In Swift 3:

import UIKit

class ScrollViewWithButtons: UIScrollView {

    override init(frame: CGRect) {
        super.init(frame: frame)
        myInit()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        myInit()
    }

    private func myInit() {
        self.delaysContentTouches = false
    }

    override func touchesShouldCancel(in view: UIView) -> Bool {
        if view is UIButton {
            return true
        }
        return super.touchesShouldCancel(in: view)
    }
}

You can then use this ScrollViewWithButtons in IB or in code.

Simon Reggiani
  • 550
  • 10
  • 18
0

None of the existing solutions worked for me. Maybe my situation is more unique.

I have many UIButtons within a UIScrollView. When a UIButton is pressed a new UIViewController is presented to the user. If a button is pressed and held long enough, the button will show its depressed state. My client was complaining that if you tap too quickly, no depressed state is shown.

My solution: Inside the UIButtons' tap method, where I load the new UIViewController and present it on screen, I use

[self performSelector:@selector(loadNextScreenWithOptions:) 
           withObject:options 
           afterDelay:0.]

This schedules the loading of the next UIViewController on the next event loop. Allowing time for the UIButton to redraw. The UIButton now shows its depressed state before loading the next UIViewController.

Alex Cio
  • 6,014
  • 5
  • 44
  • 74
Brad G
  • 2,528
  • 1
  • 22
  • 23