更改高度时 UINavigationController 会切断内容吗? (动图)

UINavigationController cuts off content when height is changed? (gif)

我已将 UINavigationController 放入可扩展 "Drawer"。 我的目标是让导航堆栈中的每个 viewController 都有自己的 "preferred" 高度。

假设 VC1 需要很高。从 VC2 导航回 VC1 时,我希望它的高度动画为高。动画逻辑似乎在工作,即使是滑动交互也是如此。

但是由于某些原因,navigationController 中的 viewController 是 "cut off"。他们的约束是正确的,但他们没有更新(?)。或者部分内容根本不会呈现,直到我再次触摸视图。底部的隐形区域甚至可以接受触摸。

看看:

预期的结果是 contentViewControllers(第一个和第二个)总是延伸到屏幕底部。他们被限制这样做,所以问题是他们不会 "render"(?) 在过渡期间。

在 UINavigationController 的委托中,我执行以下操作:

func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
    transitionCoordinator?.animate(alongsideTransition: { (context) in
            drawer.heightConstraint.constant = targetHeight
            drawer.superview?.layoutIfNeeded()
        }, completion: { (context) in
            print("done")
        })
}

身高变化完美。但导航中的内容不符合要求。 navigationController 被限制为前导、尾随、底部和一个存储的 heightConstraint,它改变了它的常量。

一旦我 touch/drag navigationController/content 它立即 "renders the unrendered",一切都很好。为什么会这样?

检查视图层次结构时,它看起来像这样:

NavigationController 达到了它需要的高度,但内容与过渡开始时整个抽屉的高度相同,并且在我触摸它之前不会更新。 为什么?

编辑:如果您想看一下,我已将代码推送到 my GitHub。但请注意,还有其他几个问题(动画等),请不要介意。我只想知道为什么导航不会呈现 "future" 高度。

您可以通过为您的 navigationController 自定义 animationController 并在自定义 UIViewControllerAnimatedTransitioning[ 的 animateTransition 函数中为 destinationView 设置适当的框架来解决上述问题。 =25=]执行。结果就像下面的 gif 图片中的那样。

您的自定义 UIViewControllerAnimatedTransitioning 可能如下所示。

final class TransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    let presenting: Bool

    init(presenting: Bool) {
        self.presenting = presenting
    }

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return TimeInterval(UINavigationController.hideShowBarDuration)
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let fromView = transitionContext.view(forKey: .from) else { return }
        guard let toView = transitionContext.view(forKey: .to) else { return }

        let duration = transitionDuration(using: transitionContext)

        let container = transitionContext.containerView
        if presenting {
            container.addSubview(toView)
        } else {
            container.insertSubview(toView, belowSubview: fromView)
        }

        let toViewFrame = toView.frame
        toView.frame = CGRect(x: presenting ? toView.frame.width : -toView.frame.width, y: toView.frame.origin.y, width: toView.frame.width, height: toView.frame.height)

        UIView.animate(withDuration: duration, animations: {
            toView.frame = toViewFrame
            fromView.frame = CGRect(x: self.presenting ? -fromView.frame.width : fromView.frame.width, y: fromView.frame.origin.y, width: fromView.frame.width, height: fromView.frame.height)
        }) { (finished) in
            container.addSubview(toView)
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        }
    }
}

并且,在您的导航控制器中提供如下自定义 animationController。

func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
   switch operation {
   case .push:
      return TransitionAnimator(presenting: true)
   case .pop:
      return TransitionAnimator(presenting: false)
   default:
      return nil
   }
}

PS :- 我也在你的 github 回购中提出了一个拉取请求。

您可以自己更新控制器高度:

首先你需要保留对控制器的引用:

class ContainedNavigationController: UINavigationController, Contained, UINavigationControllerDelegate {

    private var controllers = [UIViewController]()
    func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { controllers = viewControllers }

/* Rest of the class */

然后你可以相应地更新他们的身高。 不要忘记添加新控制器

    private func updateHeights(to height: CGFloat, willShow controller: UIViewController) {
        controller.view.frame.size.height = height
        _ = controllers.map { [=11=].view.frame.size.height = height }
    }

您可以像这样在您的代码中使用它:

    func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
        
        if let superHeight = view.superview?.superview?.bounds.size.height, let drawer = (view.superview as? Drawer), let targetHeight = (viewController as? Contained)?.currentNotch.height(availableHeight: superHeight){
            
            transitionCoordinator?.animate(alongsideTransition: { (context) in
                drawer.heightConstraint.constant = targetHeight
                drawer.superview?.layoutIfNeeded()
                
                self.updateHeights(to: targetHeight, willShow: viewController) // <- Here
            }, completion: { (context) in
                self.updateHeights(to: targetHeight, willShow: viewController) // <- Here
            })
        }
    }

结果:

也许这不是最干净的代码,但我只是想解决问题并给你想法


更新

到目前为止,当您从边缘拖动时看到的阴影是一个名为 UIParallaxDimmingView 的视图。我也为那个尺寸添加了一个修复程序。所以不再有视觉问题:

    private func updateHeights(to height: CGFloat, willShow controller: UIViewController) {
        controller.view.frame.size.height = height
        _ = controllers.map { [=13=].view.frame.size.height = height }

        guard controllers.contains(controller) else { return }
        _ = controller.view.superview?.superview?.subviews.map {
            guard "_UIParallaxDimmingView" == String(describing: type(of: [=13=])) else { return }
            [=13=].frame.size.height = height
        }
    }

我添加了来自 my fork 的拉取请求。

您可以像下面这样设置尺寸: 参考 -> https://github.com/satishVekariya/Drawer/tree/boom-diggy-boom

class FirstViewController: UIViewController, Contained {
   // your code 

   override func updateViewConstraints() {
       super.updateViewConstraints()
       if let parentView = parent?.view {
          view.frame.size.height = parentView.frame.height
       }
   }   
}

你的导航vc:

class ContainedNavigationController:UINavigationController, Contained, UINavigationControllerDelegate{
   ///Your code

   func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
       viewController.updateViewConstraints()
           // Your code start
           // end
   }

   func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
       viewController.updateViewConstraints()
   }

}

您可以设置高度限制并调用 layoutIfNeeded 方法而无需过渡块。

下面是相同的代码片段:

func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {

    if let superHeight = view.superview?.superview?.bounds.size.height, let drawer = (view.superview as? Drawer), let targetHeight = (viewController as? Contained)?.currentNotch.height(availableHeight: superHeight){
        drawer.heightConstraint.constant = targetHeight
        drawer.layoutIfNeeded()
    }
}

@sti 如果有帮助请告诉我,请投赞成票