How to pass UI events through views in iOS

Introduction

Tony Nguyen
3 min readOct 9, 2017

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.

userInteractionEnabled

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 userInteractionEnabled to false.

PassthroughView

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.

PassthroughWindow

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.

An object that provides the backdrop for your app’s user interface and provides important event-handling behaviors. (Apple’s documentation).

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.

--

--

Tony Nguyen

iOS Engineer. Master student at University of Victoria.