Can I make a UIPresentationController have userInteractionEnabled on PresentingViewController?
Asked Answered
F

4

10

I am building a custom GUI on a universal app for iPhone and iPad. On iPad it relies heavily on "sideViews" for utilities like content manipulation, detailInformation and the like (think of an advanced SplitView). From a visual point of view the new UIPresentationController is spot on for letting me present these "sideViews" (and not using a dimmedView) and the implementation has been simple to build and maintain, while still integrate nicely with the storyboard. But I need to be able to manipulate the content of the presentingViewController while the presentedViewController is visible. So my question is, can I set userInteractionEnabled (or similar) on the presentingViewController while presenting the sideViews?

Former answered 13/2, 2015 at 13:16 Comment(0)
C
22

UIPresentationController inserts its container view as a window subview above the presenting view, thus any touches outside the presented view get trapped by the container view and never make it to the presenting view.

The fix is to insert a view on the container view that passes through touches to the presenting view. You can use this as a dimming view or set its backgroundColor to [UIColor clearColor] for a fully transparent view. Set the passthrough views in your presentation controller code.

@interface IVPasserView : UIView

@property (strong, nonatomic) NSArray* passthroughViews;

@end

@implementation IVPasserView

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView* hit = [super hitTest:point withEvent:event];
    if (hit == self)
        for (UIView* passthroughView in _passthroughViews)
        {
            hit = [passthroughView hitTest:[self convertPoint:point toView:passthroughView]
                                 withEvent:event];
            if (hit)
                break;
        }
    return hit;
}

@end

Note: while this violates the spirit of -[UIView hitTest:withEvent:] in that it doesn't return a subview, this is in fact how the system standard UIPopoverPresentationController handles it. If you set the passthroughViews property there, the container view responds to hitTest:withEvent: with a passthrough view, even though they are not superview/subview! Thus it's likely to survive the next iOS release.

Calliope answered 8/7, 2015 at 11:33 Comment(3)
Interesting solution. I definitely needs to give this a try.Former
This is a good solution for exactly the problem in the question title.Cheatham
Great solution for forwarding touch events to other views.Arithmomancy
K
4

Modal presentation isn't suited well for your situation. It is better to implement your scenario with custom container view controller and override showDetailViewController:sender: method to handle presentation of additional view controllers. You can adapt this method to show view controller modal on iPhone and on the right on iPad for example.

Here is an excerpt from Apple Documentation:

Presenting Versus Showing a View Controller

The UIViewController class offers two ways to display a view controller:

The showViewController:sender: and showDetailViewController:sender: methods offer the most adaptive and flexible way to display view controllers. These methods let the presenting view controller decide how best to handle the presentation. For example, a container view controller might incorporate the view controller as a child instead of presenting it modally. The default behavior presents the view controller modally. The presentViewController:animated:completion: method always displays the view controller modally. The view controller that calls this method might not ultimately handle the presentation but the presentation is always modal. This method adapts the presentation style for horizontally compact environments. The showViewController:sender: and showDetailViewController:sender: methods are the preferred way to initiate presentations. A view controller can call them without knowing anything about the rest of the view controller hierarchy or the current view controller’s position in that hierarchy. These methods also make it easier to reuse view controllers in different parts of your app without writing conditional code paths.

Keyek answered 25/10, 2016 at 20:50 Comment(1)
Hey Alexander! Thanks for your answer, and my apologies for late response. This surely seems like an interesting solution. I will see if it solves my problem and if so give you credit for it. Upvote for now for a good idea.Former
F
1

Okay, so it seems that the idea of the UIPresentationController is NOT to be able to use it as an advanced SplitView (or at least that's my current conclusion). I did managed to build a workaround though. If anyone finds a better way of handling this, please let me know in the comments.

So what I do is I insert PresentingViewController's view in the transitionContexts containerView (same as the UIPresentationControllers containerView) hierarchy at Index 0. This makes me able to transparently handle touchEvents in the PresentingViewControllers view. But it removes the PresentingViewControllers view from its original view hierarchy so I need to move it back there when the presentation is dismissed. It means putting the view back to the parentViewController's view if present, or the window of the app, if presentingViewController is the rootViewController of the app (there might be other scenarios too, but this will do for now).

This is all done in the animateTransition in the UIViewControllerAnimatedTransitioning.

Here's the piece of code:

UIView.animateWithDuration(transitionDuration(transitionContext),
        delay: 0.0,
        usingSpringWithDamping: 1.0,
        initialSpringVelocity: 0.5,
        options: UIViewAnimationOptions.BeginFromCurrentState|UIViewAnimationOptions.AllowUserInteraction,
        animations: { () -> Void in
            animatingView.frame = finalFrame
        }) { (finished:Bool) -> Void in
            if !self.isPresentation {
                if let parentViewController = backgroundVC.parentViewController {
                    parentViewController.view.addSubview(backgroundVC.view)
                }
                else if let window = (UIApplication.sharedApplication().delegate as! AppDelegate).window {
                    window.addSubview(backgroundVC.view)
                }
                fromView.removeFromSuperview()
            }
            else {
                containerView.insertSubview(backgroundVC.view, atIndex: 0)
            }
            transitionContext.completeTransition(true)
    }
Former answered 16/2, 2015 at 6:50 Comment(1)
You are right that presentation controllers aren’t well suited for making custom split views. For that, view controller containment is probably a better option.Cheatham
A
0

Adapting Glen Low's above great Objective C solution to Swift.

// View that supports forwarding hit test touches to the provided array of views
class TouchForwardView: UIView {
    
    // Array of views that we want to forward touches to
    var touchForwardTargetViews = [UIView]()
    
    // Variable to quickly disable/enable touch forwarding functionality
    var touchForwardingEnabled = true
    
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        // Get the hit test view
        let hitTestView = super.hitTest(point, with: event)
        
        // Make sure the hit test view is self and that touch forwarding is enabled
        if hitTestView == self && touchForwardingEnabled {
            // Iterate the hit test target views
            for targetView in touchForwardTargetViews {
                // Convert hit test point to the target view
                let convertedPoint = convert(point, to: targetView)
                // Verify that the target view can receive the touch
                if let hitTargetView = targetView.hitTest(convertedPoint, with: event) {
                    // Forward the touch to the target view
                    return hitTargetView
                }
            }
        }
        
        // Return the original hit test view - this is the super value - our implmentation didn't affect the hit test pass
        return hitTestView
    }
    
}
Arithmomancy answered 9/11, 2021 at 22:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.