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.
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()
}
}
}
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With