Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
192 views
in Technique[技术] by (71.8m points)

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

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 the screen.

music app card interface

Here is the example from Mail compose window:

mail compose card interface

This metaphor can also be seen in Overcast, the popular podcast player:

overcast card interface

Is there a function in UIKit for achieving this card-like appearance?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...