Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIView subview not receiving touches

Tags:

ios

swift

uiview

I am trying to understand why my subview is not receiving touches.

Below is a gif showing the behavior I am encountering: when I click the cyan button on the right, nothing happens (the background of the view on the left is supposed to change color). When I click the green button on the left, it correctly receives the touch event, the attached TouchDown action fires and the color of the view changes.

enter image description here

This "app" is obviously not a real app and is just meant to illustrate the behavior I'm encountering. Really what I'm after is learning how to implement the common "slide out menu" pattern that you see in many apps; usually with an accompanying hamburger button.

In many of these apps, it looks like the main application content is moved aside to reveal the menu, "hiding" underneath the app, as it were. I am trying to replicate that same behavior here.

I am pasting my ViewController code in here. The code you see below is everything I have.

import UIKit

class ViewController: UIViewController {

    var originalX : CGFloat!
    var originalY : CGFloat!

    var containerView = UIView()

    var button : UIButton!
    var button2 : UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()

        originalX      = self.view.frame.origin.x
        originalY      = self.view.frame.origin.y

        button = UIButton(frame: CGRectMake(200, 25, 100, 100))
        button.backgroundColor = UIColor.greenColor()
        button.addTarget(self, action: "foo", forControlEvents: .TouchDown)

        view.addSubview(button)

        containerView.frame = CGRectMake(self.view.frame.width, originalY, 150, self.view.frame.height)
        containerView.userInteractionEnabled = true
        containerView.backgroundColor = UIColor.magentaColor()
        containerView.clipsToBounds = false

        button2 = UIButton(frame: CGRectMake(25, 25, 100, 100))
        button2.backgroundColor = UIColor.cyanColor()
        button2.addTarget(self, action: "bar", forControlEvents: .TouchDown)

        view.addSubview(containerView)
        containerView.addSubview(button2)
    }

    @IBAction func left() {
        UIView.animateWithDuration(0.3) {
            self.view.frame = CGRectMake(self.originalX - 150, self.originalY, self.view.frame.width, self.view.frame.height)
        }
    }

    @IBAction func right() {
        UIView.animateWithDuration(0.3) {
            self.view.frame = CGRectMake(self.originalX, self.originalY, self.view.frame.width, self.view.frame.height)
        }
    }

    func foo() {
        UIView.animateWithDuration(1.0) {
            self.view.backgroundColor = UIColor.redColor()
        }
    }

    func bar() {
        UIView.animateWithDuration(1.0) {
            self.view.backgroundColor = UIColor.blueColor()
        }
    }

}
like image 228
bitops Avatar asked Aug 16 '15 04:08

bitops


2 Answers

containerView is a subview of view, but containerView's origin is right of the right edge of its parent view (view), because of the line:

containerView.frame = CGRectMake(self.view.frame.width, originalY, 150, self.view.frame.height)

Any portion of subview that is outside of its superview's bounds won't receive touch events. Touch events will only be passed along and recognized by portions of the subview that are within the bounds of its superview This is why your cyan button won't "fire" when you tap it.

One way to solve it is instead of manipulating the view's frame, use a secondary subview (call it overlayView) to overlay and cover the containerView, and manipulate its frame. Make sure at least some portion both views are within the bounds of the main view so they may receive touch events.

like image 82
gschandler Avatar answered Nov 10 '22 14:11

gschandler


You need to keep the main view large enough to receive events. Use self.view.frame.width + 150 as the 3rd argument to CGRectMake in left().

However, this is an odd and fragile solution to whatever you're trying to do. You should not be shifting the frame of the main view. That has to remain covering the entire UIWindow, nothing more, nothing less. You want to animate subviews only, and consider if you're really using a PageViewController idiom, a CollectionView idiom, a UIScrollView idiom, or a SpriteKit idiom. And think about why you want to move your central control buttons, and your central label, making an unusably 'jumpy' interface.

One fragility is doing layout calculations in viewDidLoad(), which is too early in the ViewController lifecycle. See: wrong frame size in viewDidLoad

The right way is a good bit more work: constrain a custom UIView subclass to fill your main view, and then if you must do non-Storyboard in-code layout calculations, do that within an overridden layoutSubviews() method of that custom subclass -- at which point correct geometry is available. (However adding subviews to a custom UIView subclass in code should not be done in layoutSubviews()` but instead at construction or nib-awakening time.)

Things get a lot easier if your main view needn't move. If you are read-fluent in Objective-C, try: http://www.teehanlax.com/blog/custom-uiviewcontroller-transitions/

Then you're getting real separation of concerns, where your slideover settings really are self-contained. To quote:

Here’s the interaction we’re going to create. It’s nothing special – just a view appearing from the right edge of the screen. What is special is that we’re actually presenting a view controller, even though the presenting view controller remains visible.

like image 2
BaseZen Avatar answered Nov 10 '22 15:11

BaseZen