34

I have an app with many different buttons arranged in a calculator like, square / rectangular format. It is actually extremely similar to the default iOS calculator. There are approximately 6 rows with 4 columns each of buttons.

Problem

The problem I am having involves the buttons in the bottom row (approximately the bottom 10th of the screen on an iPhone 4). They do not behave normally when pressed in the sense that when pressed, they have to be pressed and held (for roughly just under a second) to register a "button press". This is opposed to the standard short tap.

No other buttons besides this bottom row behave in this fashion.

Additionally, if these buttons are tapped on their upper edge, they behave normally, responding as soon as they are touched. This leads me to believe that the buttons themselves are not the problems but there is some problem with the layout of my views.

It should be also noted that this problem is only present on physical devices. On the simulator, the buttons behave normally.

Context

The view containing these buttons is not the root view controller of the app. Instead it is transitioned to as so (nothing fancy here):

[self presentViewController:navController animated:YES completion:nil];

Where self is the root view controller

The view controller I am having problems with is contained within a navigation controller and is presented modally by the root view controller which you can see above.

What I have tried so far

  • Turning auto layout on and off: same problem

  • Rearranging hierarchy of views: I moved the problematic buttons on top of and behind all other views with the same result: same problem

  • Multiple devices (iPhone 4, 4s, 5): same problem (although buttons respond normally on both 3.5 inch and 4 inch simulators)

  • Testing other apps (when buttons in this region are pressed on other apps, they behave normally)

Additional Information

  • Everything is laid out in Interface Builder for the problematic view controller
  • All of the buttons are system buttons with standard settings and are all exactly the same besides their text.
  • All of the elements of the screen (buttons, labels, etc. ) are subviews of the "view"
  • The buttons are flush against each other and should not overlap more than one or two pixels.
  • The problematic buttons have dimensions: 80 width X 44 height.
  • The problematic buttons are flush against the bottom of the screen
  • In addition to the buttons, there is one UIImage and several labels however these are at the top of the screen and do not overlap with any of the buttons in any way.
Qantas 94 Heavy
  • 15,750
  • 31
  • 68
  • 83
Iowa15
  • 3,027
  • 6
  • 28
  • 35
  • 2
    Isn't the problem that you have to wait for the Control Center's gesture recognizer to decide that we are not about to slide the control center up? – matt Apr 13 '14 at 18:29
  • Oh you're right! That makes sense. Is there a way to fix the problem then? – Iowa15 Apr 13 '14 at 18:30
  • 1
    Are you sure they are not being tapped? There is a system bug where it _looks_ like they are not being tapped but the tap still works. – matt Apr 13 '14 at 18:37
  • @matt, would be cool if you wrote out your 1st comment into a proper answer because I'd imagine a lot of people may have this same issue and wouldn't think of that conflict... – Lyndsey Scott Apr 13 '14 at 18:40
  • @matt You're right. It doesn't look like they are being tapped because I have to method set up to respond to a tap. If you put that as an answer, I will accept it – Iowa15 Apr 13 '14 at 18:42

9 Answers9

34

The cause for this issue is that Apple seems to place a GestureRecognizer at the bottom of the screen that delays touches in any other view. After fiddling around with gesture recognizers on the App's windows I came up with a solution that incorporates a subclass of UIButton:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    BOOL inside = [super pointInside: point withEvent: event];

    if (inside && !self.isHighlighted && event.type == UIEventTypeTouches)
    {
        self.highlighted = YES;
    }

    return inside;
}

The given method is getting called although touchesBegan: is called delayed. A check if the view is at the bottom of the screen may be suitable to prevent any side effects that may occur with this fix.

Quxflux
  • 3,133
  • 2
  • 26
  • 46
  • This method is not called any my subclass of UIButton – Robert J. Clegg Jul 28 '14 at 07:01
  • Did you check if the buttons frame lies within the bounds of its superviews? – Quxflux Jul 28 '14 at 07:13
  • 1
    Just a technical point: ```!self.highlighted``` should be ```!self.isHighlighted``` in an ```if``` statement. Optionally you could also add ```event.type == UIEventTypeTouches``` to prevent highlighting in other events' cases. – Michi Jun 06 '15 at 23:58
  • 4
    @Lukas Great answer, but I noticed that if the user actually completes the swipe to show control center, the button will get stuck in highlighted mode. To fix it, when setting the button to highlighted I make it listen to `UIApplicationWillResignActiveNotification`, if it happens I set highlighted to NO manually. – Tiago Lira Jun 30 '15 at 15:12
  • This is a very nice fix! :) – Trenskow Jul 22 '15 at 08:46
  • Well done Lukas and Tiago Lira. Both fixes work well (although, sometimes, the button will still remain highlighted when 'willResignActiveNotification' is received.) – Womble Jul 31 '19 at 01:38
  • A gotcha: this will interfere with 'isExclusiveTouch' on the Button. – Womble Aug 13 '20 at 02:21
18

This sounds like an interaction between the buttons and the UIScreenEdgePanGestureRecognizer (or whatever it is) that is responsible for detecting that the user wants to bring up the system's Control Center.

There are actually two potential issues here:

  • There can be an interaction (i.e. conflict) between the possibility of a gesture directed at your app and a gesture directed at the system. If you have gesture recognizers, you might have to use delegate methods to mediate between them and the system's gesture recognizers.

  • There is a well-established bug where a tap near the screen edge (i.e. in the screen edge gesture recognizer's "zone") works but it causes the button to misbehave physically, i.e. it doesn't look as if it's been tapped even though logging shows that it has (see my answer here: https://stackoverflow.com/a/22000692/341994).

Community
  • 1
  • 1
matt
  • 515,959
  • 87
  • 875
  • 1,141
9

answer of Lukas in swift and deselect highlighted

extension UIButton {

  public override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {

    var inside = super.pointInside(point, withEvent: event)

    if inside != highlighted && event?.type == .Touches {
        highlighted = inside
    }

    return inside

  }

}
Trenskow
  • 3,783
  • 1
  • 29
  • 35
ar_dsw
  • 91
  • 1
  • 2
5

Swift 3 Solution

extension UIControl {

    open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        let inside = super.point(inside: point, with: event)
        if inside != isHighlighted && event?.type == .touches {
            isHighlighted = inside
        }
        return inside
    }
}
Randy
  • 2,270
  • 1
  • 15
  • 24
4

I've written a full solution in swift based on Luka's answer. Just make any conflicting buttons conform to this class and the problem will be gone:

class BorderBugFixButton : UIButton {

    override func awakeFromNib() {
        super.awakeFromNib()
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "unHighlight", name: UIApplicationWillResignActiveNotification, object: nil)
    }

    deinit {
        NSNotificationCenter.defaultCenter().removeObserver(self)
    }

    override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
        let inside = super.pointInside(point, withEvent: event)
        if inside != highlighted && event?.type == .Touches {
            highlighted = inside
        }
        return inside
    }

    internal func unHighlight() {
        highlighted = false
    }

}

P.S.: For those of you that don't like Storyboards/Xib's, just migrate the implementation of awakeFromNib() to init()

Community
  • 1
  • 1
Danny Bravo
  • 4,534
  • 1
  • 25
  • 43
  • Hello, this solution doesn't work for me. Any idea why ? I've tried all the different solution from this thread – Makaille Jul 20 '17 at 15:09
  • Post a question with the code you've written and I'm happy to take a look at it. – Danny Bravo Aug 05 '17 at 10:02
  • Hello Danny, not sure that is usefull cause my code is exactly the same than your post. I've tried the code from the next reply and nothing changed my button still not working correctly on border screen – Makaille Aug 08 '17 at 15:33
0

I've run into this numerous times when working with updating older Storyboards to iOS 7+, usually when the ViewController in question has a form of a UIScrollView. Double check these 2 settings in your Storyboard on the ViewController Object (Not the view, the one with the yellow circle). When I unchecked the 'Extend Edges' under top & bottom bars, the scrollView's frame was adjusted down by 64 points (height of Nav & Status bars).

UIButton not working on bottom of screen After setting the space between NavBar.bottom and scrollView.top back to 0, the button started working. This might be due to the fact the scrollView.frame.bottom was 64 pixels above the bottom of the window, so touches in that area were disregarded because they were technically out of the scrollView's frame but still displayed visually for some reason.

self.name
  • 2,341
  • 2
  • 17
  • 18
0

iOS 9.3, Xcode 7.3

I would suggest that you should make a category to the UIButton class that implements Lukas' answer. For instructions on how to create a category see this post: How do I create a category in Xcode 6 or higher?

Give it an appropriate name with the traditional "+" sign, i.e. if you name it "BottomOfScreen", then the resulting file name will be "UIButton+BottomOfScreen".

If you are using objective-c, then you will get a *.h and *.m files with he new category.

*.h

#import <UIKit/UIKit.h>

@interface UIButton (BottomOfScreen)

@end

*.m

#import "UIButton+BottomOfScreen.h"

@implementation UIButton (BottomOfScreen)

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    BOOL inside = [super pointInside:point withEvent:event];

    if (inside && !self.isHighlighted && (event.type == UIEventTypeTouches))
    {
        self.highlighted = true;
    }
    else
    {
        nil;
    }

    return inside;
}

@end
Community
  • 1
  • 1
serge-k
  • 3,394
  • 2
  • 24
  • 55
0

I was able to fix this issue by disabling delaysTouchesBegan in viewWillAppear

self.navigationController?.interactivePopGestureRecognizer?.delaysTouchesBegan = false
SamB
  • 2,621
  • 4
  • 34
  • 39
0

I've prepared this answer in the hopes that someone else might find it helpful.

My problem was a little more difficult to discover the cause, but way easier to resolve. The button in question was in a custom sideBarView and XIB with four other buttons that I had programmatically initialized and loaded into the viewController. The top four worked fine. Only the bottom didn't seem to work...

CAUSE: The CGRect defined programmatically for the custom sideBarView was actually smaller in height than the XIB needed. However, since the sideBarView wasn't clipToBounds, it showed it the lowest button, but any taps on it were registered as taps on the view below and not as taps on the lowest button.

To discover this, I checked the 3D viewer, the order of the objects in the XIB and even took comparison snaps of each of the buttons in the simulator with Color-blended layers selected and with breakpoints on didTap... it wasn't until I shortened the spaces between the constraints between each button and discovered that nothing but only the top of the lowest button would accept the tap, which gave me the clue that it was height limitation somewhere (like the initialization code).

Peter Brockmann
  • 3,594
  • 3
  • 28
  • 28