How to pass UI events through views in iOS
This article will be very short, explain how to pass touch events on UI from a UIWindow/UIView instance to another one.
You might wonder if it’s worth to know? By default, in a trivial, common application, the top most view, which is determined by a fundamental mechanism implemented in UIKit called “hit-test”, will receive touch events. Hit-testing is no more an algorithm which goes through our view hierarchy and finds the deep most view that contains the touch point and should receive the event.
However, when your application has much more complicated UI, you might need to know other ways of handling touch events.
This property, which is one of basic properties of UIView, indicates a view is enabled for interaction or not. In hit-testing, a view will not receive any touch events if its state is either
userInteractionEnabled == false ,
isHidden == true , or
alpha < 0.01 . Therefore, when we want our views to be shown but will not receive events, just set
One problem of
userInteractionEnabled is it cascades down the subviews. It means when a view is disabled for interaction, all of its subviews are also disabled. Sometimes we need to show a view which contains some subviews inside, we need those subviews can be touched, but also need our superview acts as a background view, do not receive any events. Here’s one way to do that:
Here, I declare a subclass of UIView and override the default hitTest behavior. Then I can use this class whenever I want a view to be a passthrough view. Below is an example I can tap the underneath UISwitch.
UIWindow is just a special UIView with extended functions. But it’s a very crucial part of an iOS application. It’s good to learn how it’s used.
In simply, a window is an object that contains UI elements on that. Every app must have at least 1 window which is usually initialized programmatically in code or by setting
Is Initial View Controller in Storyboard.
It’s recommended that an app should have only 1 window, but there’re some cases we need more than that. For instance, when need another window to display things on an external screen. Or when using ReplayKit to broadcast our screen, the library captures our Main Window, if we want to show somethings not to be captured like stop button or capturing status, we must show them on another window.
Then we might need a way to pass touch events through windows. Fortunately, a UIWindow is just a UIView, so that we can declare like this:
For now, we can create a PassthroughWindow, or a PassthroughView and some subviews on that. However, what if the UI is not simple anymore: We need to display an window with a complex navigation controller on that (Well, it actually happened in my project).
Well, events are passed through the PassthroughView, but they will not be passed to PassthroughWindow because they will be stopped at middle views like
BubblesViewController, UIViewControllerWrapperView, ... . We implemented PassthroughView but we can’t change these views’ class to PassthroughView or it would be very hard and frustrated to do. Here’s a trick to overcome this problem:
Whenever I hit-test a PassthroughView, I delegate the hit-test result to my PassthroughWindow. Of course, this special subclass will work only in this case, you may change the logic in your cases.
Where to go from here
There’s a very helpful WWDC session about touch handling I recommend you to watch: https://developer.apple.com/videos/play/wwdc2014/235/
Apart from hit testing, I also suggest you read about Responder Chain — an interesting way to alternate event responders.