Is there a public API for card view UI that can be seen across iOS 10?

后端 未结 4 914
盖世英雄少女心
盖世英雄少女心 2020-12-07 09:06

The Music app in iOS 10 adopts a new card-like appearance: Now Playing screen slides up, while the view below in the hierarchy zooms out, protruding slightly at the top of t

4条回答
  •  南方客
    南方客 (楼主)
    2020-12-07 09:16

    You can build the segue in interface builder. Selecting modal segue from ViewController to CardViewController.

    For your CardViewController :

    import UIKit
    
    class CardViewController: UIViewController {
    
      required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.commonInit()
      }
    
      override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: Bundle!)  {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    
        self.commonInit()
      }
    
      func commonInit() {
        self.modalPresentationStyle = .custom
        self.transitioningDelegate = self
      }
    
      override func viewDidLoad() {
        super.viewDidLoad()
    
        roundViews()
      }
    
      func roundViews() {
        view.layer.cornerRadius = 8
        view.clipsToBounds = true
      }
    
    }
    

    then add this extension:

    extension CardViewController: UIViewControllerTransitioningDelegate {
    
      func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
        if presented == self {
          return CardPresentationController(presentedViewController: presented, presenting: presenting)
        }
        return nil
      }
    
      func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        if presented == self {
          return CardAnimationController(isPresenting: true)
        } else {
          return nil
        }
      }
    
      func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        if dismissed == self {
          return CardAnimationController(isPresenting: false)
        } else {
          return nil
        }
      }
    
    }
    

    Finally, you will need 2 more classes:

    import UIKit
    
    class CardPresentationController: UIPresentationController {
    
      lazy var dimmingView :UIView = {
        let view = UIView(frame: self.containerView!.bounds)
        view.backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.3)
        view.layer.cornerRadius = 8
        view.clipsToBounds = true
        return view
      }()
    
      override func presentationTransitionWillBegin() {
    
        guard
          let containerView = containerView,
          let presentedView = presentedView
          else {
            return
        }
    
        // Add the dimming view and the presented view to the heirarchy
        dimmingView.frame = containerView.bounds
        containerView.addSubview(dimmingView)
        containerView.addSubview(presentedView)
    
        // Fade in the dimming view alongside the transition
        if let transitionCoordinator = self.presentingViewController.transitionCoordinator {
          transitionCoordinator.animate(alongsideTransition: {(context: UIViewControllerTransitionCoordinatorContext!) -> Void in
            self.dimmingView.alpha = 1.0
          }, completion:nil)
        }
      }
    
      override func presentationTransitionDidEnd(_ completed: Bool)  {
        // If the presentation didn't complete, remove the dimming view
        if !completed {
          self.dimmingView.removeFromSuperview()
        }
      }
    
      override func dismissalTransitionWillBegin()  {
        // Fade out the dimming view alongside the transition
        if let transitionCoordinator = self.presentingViewController.transitionCoordinator {
          transitionCoordinator.animate(alongsideTransition: {(context: UIViewControllerTransitionCoordinatorContext!) -> Void in
            self.dimmingView.alpha  = 0.0
          }, completion:nil)
        }
      }
    
      override func dismissalTransitionDidEnd(_ completed: Bool) {
        // If the dismissal completed, remove the dimming view
        if completed {
          self.dimmingView.removeFromSuperview()
        }
      }
    
      override var frameOfPresentedViewInContainerView : CGRect {
    
        // We don't want the presented view to fill the whole container view, so inset it's frame
        let frame = self.containerView!.bounds;
        var presentedViewFrame = CGRect.zero
        presentedViewFrame.size = CGSize(width: frame.size.width, height: frame.size.height - 40)
        presentedViewFrame.origin = CGPoint(x: 0, y: 40)
    
        return presentedViewFrame
      }
    
      override func viewWillTransition(to size: CGSize, with transitionCoordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: transitionCoordinator)
    
        guard
          let containerView = containerView
          else {
            return
        }
    
        transitionCoordinator.animate(alongsideTransition: {(context: UIViewControllerTransitionCoordinatorContext!) -> Void in
          self.dimmingView.frame = containerView.bounds
        }, completion:nil)
      }
    
    }
    

    and:

    import UIKit
    
    
    class CardAnimationController: NSObject {
    
      let isPresenting :Bool
      let duration :TimeInterval = 0.5
    
      init(isPresenting: Bool) {
        self.isPresenting = isPresenting
    
        super.init()
      }
    }
    
    // MARK: - UIViewControllerAnimatedTransitioning
    
    extension CardAnimationController: UIViewControllerAnimatedTransitioning {
    
      func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return self.duration
      }
    
      func animateTransition(using transitionContext: UIViewControllerContextTransitioning)  {
        let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)
        let fromView = fromVC?.view
        let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
        let toView = toVC?.view
    
        let containerView = transitionContext.containerView
    
        if isPresenting {
          containerView.addSubview(toView!)
        }
    
        let bottomVC = isPresenting ? fromVC : toVC
        let bottomPresentingView = bottomVC?.view
    
        let topVC = isPresenting ? toVC : fromVC
        let topPresentedView = topVC?.view
        var topPresentedFrame = transitionContext.finalFrame(for: topVC!)
        let topDismissedFrame = topPresentedFrame
        topPresentedFrame.origin.y -= topDismissedFrame.size.height
        let topInitialFrame = topDismissedFrame
        let topFinalFrame = isPresenting ? topPresentedFrame : topDismissedFrame
        topPresentedView?.frame = topInitialFrame
    
        UIView.animate(withDuration: self.transitionDuration(using: transitionContext),
                       delay: 0,
                       usingSpringWithDamping: 300.0,
                       initialSpringVelocity: 5.0,
                       options: [.allowUserInteraction, .beginFromCurrentState], //[.Alert, .Badge]
          animations: {
            topPresentedView?.frame = topFinalFrame
            let scalingFactor : CGFloat = self.isPresenting ? 0.92 : 1.0
            bottomPresentingView?.transform = CGAffineTransform.identity.scaledBy(x: scalingFactor, y: scalingFactor)
    
        }, completion: {
          (value: Bool) in
          if !self.isPresenting {
            fromView?.removeFromSuperview()
          }
        })
    
    
        if isPresenting {
          animatePresentationWithTransitionContext(transitionContext)
        } else {
          animateDismissalWithTransitionContext(transitionContext)
        }
      }
    
      func animatePresentationWithTransitionContext(_ transitionContext: UIViewControllerContextTransitioning) {
    
        let containerView = transitionContext.containerView
        guard
          let presentedController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to),
          let presentedControllerView = transitionContext.view(forKey: UITransitionContextViewKey.to)
          else {
            return
        }
    
        // Position the presented view off the top of the container view
        presentedControllerView.frame = transitionContext.finalFrame(for: presentedController)
        presentedControllerView.center.y += containerView.bounds.size.height
    
        containerView.addSubview(presentedControllerView)
    
        // Animate the presented view to it's final position
        UIView.animate(withDuration: self.duration, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: .allowUserInteraction, animations: {
          presentedControllerView.center.y -= containerView.bounds.size.height
        }, completion: {(completed: Bool) -> Void in
          transitionContext.completeTransition(completed)
        })
      }
    
      func animateDismissalWithTransitionContext(_ transitionContext: UIViewControllerContextTransitioning) {
    
        let containerView = transitionContext.containerView
        guard
          let presentedControllerView = transitionContext.view(forKey: UITransitionContextViewKey.from)
          else {
            return
        }
    
        // Animate the presented view off the bottom of the view
        UIView.animate(withDuration: self.duration, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: .allowUserInteraction, animations: {
          presentedControllerView.center.y += containerView.bounds.size.height
        }, completion: {(completed: Bool) -> Void in
          transitionContext.completeTransition(completed)
        })
      }
    }
    

    Finally, in order to animate the CardViewController closing, hook your closing button to FirstResponder selecting dismiss and add this method to ViewController:

    func dismiss(_ segue: UIStoryboardSegue) {
        self.dismiss(animated: true, completion: nil)
    }
    

提交回复
热议问题