具有弯曲边缘和透明状态栏的短 UINavigationBar

Short UINavigationBar with curved edges and transparent status bar

如何使用导航控制器实现这种效果。

我只有一个 VC 嵌入在 UINavigationController 中,它是否以模态方式显示在另一个 VC 之上。到目前为止一切顺利,但我想实现如下所示的外观,其中导航栏较短,边缘弯曲,状态栏部分透明,露出底部 viewcontroller 的状态栏。

到目前为止,这是我在情节提要中的内容。

到目前为止,我已经能够通过子类化 UINavigationController 为导航栏实现圆角边缘。但是我不知道如何使导航栏从顶部变短:

class ComposeNavigController: UINavigationController {

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        self.definesPresentationContext = true
        self.providesPresentationContextTransitionStyle = true
        self.modalPresentationCapturesStatusBarAppearance = false
        self.modalPresentationStyle = .overFullScreen


    }

    override func viewDidLoad() {
        super.viewDidLoad()
            view.clipsToBounds = true
            view.layer.cornerRadius = 16.0
            view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
            view.layer.shadowOffset = .zero
            view.layer.shadowColor = UIColor.black.cgColor
            view.layer.shadowRadius = 16.0
            view.layer.shadowOpacity = 0.5
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        view.layer.shadowPath = UIBezierPath(rect: view.bounds).cgPath
    }

}

效果很好,边缘弯曲。

好吧,除了问题中的 UINavigationController subclass 之外,这就是我必须做的事情。我不得不利用 UIViewControllerAnimatedTransitioning 委托来实现我想要的。

我创建了两个 class subclassed NSObject 并符合上述协议。

第一个用于 present 过渡-

import UIKit

class ComposePresentTransitionController: NSObject, UIViewControllerAnimatedTransitioning {

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.6
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

        let fromViewController = transitionContext.viewController(forKey: .from)!
        let toViewController = transitionContext.viewController(forKey: .to)!
        let containerView = transitionContext.containerView

        let screenBounds = UIScreen.main.bounds
        let topOffset: CGFloat = UIApplication.shared.statusBarFrame.height

        var finalFrame = transitionContext.finalFrame(for: toViewController)
        finalFrame.origin.y += topOffset
        finalFrame.size.height -= topOffset

        toViewController.view.frame = CGRect(x: 0.0, y: screenBounds.size.height,
                                             width: finalFrame.size.width,
                                             height: finalFrame.size.height)
        containerView.addSubview(toViewController.view)

        UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 1.0, options: .curveEaseOut, animations: {
            toViewController.view.frame = finalFrame
            fromViewController.view.alpha = 0.3
        }) { (finished) in
            transitionContext.completeTransition(finished)
        }

        UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.0, options: .curveEaseOut,
                       animations: {

        }) { (finished) in

        }
    }
}

第二个 dismiss 转换-

import UIKit

class ComposeDismissTransitionController: NSObject, UIViewControllerAnimatedTransitioning {

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.3
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

        let fromViewController = transitionContext.viewController(forKey: .from)!
        let toViewController = transitionContext.viewController(forKey: .to)!

        let screenBounds = UIScreen.main.bounds
        var finalFrame = fromViewController.view.frame
        finalFrame.origin.y = screenBounds.size.height

        UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.0, options: .curveEaseIn,
                       animations: {
                        fromViewController.view.frame = finalFrame
                        toViewController.view.alpha = 1.0
        }) { (finished) in
            fromViewController.view.removeFromSuperview()
            transitionContext.completeTransition(finished)
        }
    }
}

然后,从将要呈现的视图控制器中,这个 viewcontroller 需要遵守 UIViewControllerTransitioningDelegate 协议并实现 animationController:forPresentedanimationController:forDismissed 方法,就像这样-

extension PresentingVC: UIViewControllerTransitioningDelegate {

    func animationController(forPresented presented: UIViewController,
                             presenting: UIViewController,
                             source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return ComposePresentTransitionController()
    }

    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return ComposeDismissTransitionController()
    }
}

完成后,PresentingVC 需要将 UIViewControllerTransitioningDelegate 设置为 self,这是在 prepareForSegue-

中完成的
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        super.prepare(for: segue, sender: sender)
        segue.destination.transitioningDelegate = self
    }

如果你像我一样讨厌 segue,那么你也可以通过代码中的 IBAction 按钮来完成此操作。

@IBAction func composeTap(_ sender: Any) {
        let composeVC = self.storyboard?.instantiateViewController(withIdentifier: "compose") as! ComposeNavigController //this should be the navigation controller itself not the embedded viewcontroller
        composeVC.transitioningDelegate = self
        self.present(composeVC, animated: true, completion: nil)
    }

就是这样...现在只要点击按钮以模态方式显示撰写屏幕,它就会以我想要的效果模态动画。我添加了一点弹跳效果只是为了好玩:)