Caveats of Storyboards and Container Views

I’ve been working on a new release of Songbook Simple these past 2 days, and decided to make the App iPhone compatible.

The thing is, if you have ever used Songbook Simple, the iPad and iPhone versions of the app would have a significantly different UI, in that the iPad app has a text view always visible with basically a navigation controller that can fly in and out (call this the menu controller), whereas the iPhone app is basically all completely navigation controller based.

That said, I thought the best way to make the code reusable and straightforward would be to use Storyboards. It’s taken me a long time to accept them because I have found them a bit clunky (especially for iPad), but now that they’ve been around for a while, I’m finding it’s a tool in the toolbox that isn’t going away; AND they exist to ultimately make our job easier and quicker.

When building the UI for the iPad I thought I’d use Container Views with embed segues. This works quite well. I ran into one big problem and wanted to share a little trick I have used in other situations as well.

Basically, Container Views are their own UIView object, and when you embed a view controller in a storyboard, the View Controller’s .view object gets added as a subview to this container view. So now you have a potentially unexpected view in your hierarchy.

The problem in SongbookSimple is that this container view is static on screen (taking up the right 1/3 of the screen), and the menu controller animates in and out of the screen, but ultimately as a subview of this container view.

So what, right? Well the view underneath this container view is the text view I use to display song text. What resulted was that the container view was swallowing all the touches and the text view wouldn’t respond to input.

Thankfully in Interface Builder you can just specify the Class used for the container view, which is UIView by default. I created a custom view class that overrides one method, and here’s the trick:

@interface HSPassthroughRootView : UIView
@end

@implementation HSPassthroughRootView

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView *hitView = [super hitTest:point withEvent:event];
    
    // basically, if it's not a subview, we want to allow the touch to just pass through the view
    if (hitView == self) {
        return nil;
    }
    return hitView;
}

@end

What does this mean? hitTest is a low-level method that is involved in deciding whether that view should process touches. By returning nil, you’re effectively saying “No, this touch doesn’t affect me. Let it pass through me to somewhere lower in the view hierarchy”.

That’s it. This method also comes in handy when you want a UIView to respond to touches, but perhaps only if it’s inside a certain shape, such as a circle, or a star, or any shape you define with a UIBezierPath.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s