我可以让 UIPresentationController 在 PresentingViewController 上启用 userInteractionEnabled 吗?

Can I make a UIPresentationController have userInteractionEnabled on PresentingViewController?

我正在 iPhone 和 iPad 的通用应用程序上构建自定义 GUI。在 iPad 上,它严重依赖 "sideViews" 来实现内容操作、detailInformation 等实用程序(想想高级 SplitView)。从视觉的角度来看,新的 UIPresentationController 非常适合让我展示这些 "sideViews"(而不是使用 dimmedView),并且实现很容易构建和维护,同时仍然与故事板很好地集成。但是我需要能够在 presentedViewController 可见时操作 presentingViewController 的内容。所以我的问题是,我可以在呈现 sideView 时在 presentingViewController 上设置 userInteractionEnabled(或类似的)吗?

好吧,看来 UIPresentationController 的想法不能将它用作高级 SplitView(或者至少这是我目前的结论)。不过,我确实设法建立了一个解决方法。如果有人找到更好的处理方法,请在评论中告诉我。

所以我所做的是将 PresentingViewController 的视图插入索引 0 处的 transitionContexts containerView(与 UIPresentationControllers containerView 相同)层次结构中。这使我能够透明地处理 PresentingViewControllers 视图中的 touchEvents。但它从其原始视图层次结构中删除了 PresentingViewControllers 视图,因此我需要在演示文稿被取消时将其移回那里。这意味着将视图放回 parentViewController 的视图(如果存在)或应用程序的 window,如果 presentingViewController 是应用程序的 rootViewController(可能还有其他情况,但现在可以这样做)。

这一切都在 UIViewControllerAnimatedTransitioning 中的 animateTransition 中完成。

这是一段代码:

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)
    }

UIPresentationController 将其容器视图作为 window 子视图插入呈现视图上方,因此 呈现视图 之外的任何触摸都会被容器视图捕获永远不要进入 呈现视图

解决方法是在容器视图上插入一个视图,该视图通过触摸传递到呈现视图。您可以将其用作调光视图或将其 backgroundColor 设置为 [UIColor clearColor] 以获得完全透明的视图。在您的演示控制器代码中设置直通视图。

@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

注意:虽然这违反了 -[UIView hitTest:withEvent:] 的精神,因为它不是 return 子视图,但实际上系统标准 UIPopoverPresentationController 是这样处理它的。如果您在那里设置 passthroughViews 属性,容器视图会以直通视图响应 hitTest:withEvent:,即使它们不是 superview/subview!因此它很可能在下一个 iOS 版本中存活下来。

模态显示不适合您的情况。最好使用自定义容器视图控制器来实现您的场景,并覆盖 showDetailViewController:sender: 方法来处理其他视图控制器的呈现。例如,您可以调整此方法以在 iPhone 和 iPad 右侧显示视图控制器模态。

以下是 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.

将 Glen Low 的上述 Objective C 解决方案应用到 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
    }
    
}