7

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()
        }
    }

}
BaseZen
  • 8,650
  • 3
  • 35
  • 47
bitops
  • 4,022
  • 3
  • 26
  • 31
  • I don't have a specific answer for you, but one thing I would look at is the way you are animating objects with the "frame", it's probably better to animate either the constraints or the transform instead. The animation of a frame can do wacky stuff and it's not hard to just animate a transform. – Larry Pickles Aug 16 '15 at 04:36

3 Answers3

14

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.

gschandler
  • 3,208
  • 16
  • 16
  • Thanks for your post @gschandler -- your answer isn't totally clear to me. Would you mind editing for clarity? – bitops Aug 16 '15 at 05:31
  • I think I understand what you mean about the overlayView -- do you have any code snippet you could share? It's hard for me to visualize at the moment. – bitops Aug 16 '15 at 20:56
  • 1
    Also, in case it helps others - this link was shared with me on a related question: https://developer.apple.com/library/ios/qa/qa2013/qa1812.html – bitops Aug 16 '15 at 20:57
2

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.

Community
  • 1
  • 1
BaseZen
  • 8,650
  • 3
  • 35
  • 47
  • Hey, thanks for your answer. I agree that modifying the width is not a good solution. Maybe there's a different point that I am missing. Really what this question is about is replicating the very common 'slide out' menu pattern that you see in many apps. In many (not all) of these apps, the main application window is moved over to reveal the sidebar. That's the effect I'm going for here. – bitops Aug 16 '15 at 05:08
  • The "app" above is obviously not a for-real app, it's a very simplified version of the problem I'm trying to solve. – bitops Aug 16 '15 at 05:10
  • The heart of the answer, then, is animating subview frames only. Also, you're not supposed to rely on `frame` and `bounds` values in `viewDidLoad()` – BaseZen Aug 16 '15 at 05:26
  • Oh, okay, I didn't realize using frame/bounds in viewDidLoad was a bad thing. Does my comment above make sense though in terms of what I'm going for? Surely you've seen the functionality I'm describing in terms of moving the "main" view of the application over to reveal another view underneath. You see this in the YouTube app (for instance) when you click on the hamburger button. – bitops Aug 16 '15 at 05:28
  • I saw your updated answer - wow! - so these other apps really are adding their own container view as a subview so they can implement the slideout feature? That seems like an awful lot of work. – bitops Aug 16 '15 at 05:33
0

For your code, you have add containerView to self.view, however it's frame is not in self.view.bounds, so the containerView can't receive any touch, so what you should do is

  • just make sure self.view is large enough to contain the containerView, at least contains button2

or

  • just animate the containerView frame to make it in self.view
Siam
  • 162
  • 2
  • 9