[英]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. iOS 10 中的音乐应用采用了新的卡片式外观:正在播放屏幕向上滑动,而层次结构中的下方视图缩小,在屏幕顶部略微突出。
Here is the example from Mail compose window:以下是邮件撰写窗口中的示例:
This metaphor can also be seen in Overcast, the popular podcast player:这个比喻也可以在流行的播客播放器 Overcast 中看到:
Is there a function in UIKit for achieving this card-like appearance? UIKit 中是否有实现这种卡片式外观的功能?
You can build the segue in interface builder.您可以在界面构建器中构建转场。 Selecting modal segue from ViewController
to CardViewController
.选择从ViewController
到CardViewController
模态转CardViewController
。
For your CardViewController
:对于您的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:最后,您还需要 2 个类:
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
:最后,为了动画CardViewController
关闭,将关闭按钮挂钩到FirstResponder
选择dismiss
并将此方法添加到ViewController
:
func dismiss(_ segue: UIStoryboardSegue) {
self.dismiss(animated: true, completion: nil)
}
Apple show how to do this using UIViewPropertyAnimator
in WWDC 2017 Session 230: Advanced Animations with UIKit Apple 在WWDC 2017 Session 230: Advanced Animations with UIKit 中展示了如何使用UIViewPropertyAnimator
做到这一点
The basic idea is that you add a child view controller, and position it mostly off-screen.基本思想是您添加一个子视图控制器,并将其大部分放置在屏幕外。 When tapped/panned you animate the child view controller's frame.当点击/平移时,您会为子视图控制器的框架设置动画。
import UIKit
class CardViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
}
}
class ViewController: UIViewController {
private let cardViewController = CardViewController()
private var cardHiddenConstraint: NSLayoutConstraint!
private var cardVisibleConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
addChild(cardViewController)
let cardViewControllerView = cardViewController.view!
cardViewControllerView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(cardViewControllerView)
cardHiddenConstraint = cardViewControllerView.topAnchor.constraint(equalTo: view.bottomAnchor, constant: -50)
cardVisibleConstraint = cardViewControllerView.topAnchor.constraint(equalTo: view.topAnchor, constant: 50)
let cardViewControllerViewConstraints = [
cardViewControllerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
cardViewControllerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
cardHiddenConstraint!,
cardViewControllerView.heightAnchor.constraint(equalTo: view.heightAnchor)
]
NSLayoutConstraint.activate(cardViewControllerViewConstraints)
cardViewController.didMove(toParent: self)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:)))
cardViewController.view.addGestureRecognizer(tapGestureRecognizer)
}
@objc private func handleTapGesture(_ gestureRecognizer: UITapGestureRecognizer) {
let frameAnimator = UIViewPropertyAnimator(duration: 0.3, dampingRatio: 1) {
if self.cardHiddenConstraint.isActive {
self.cardHiddenConstraint.isActive = false
self.cardVisibleConstraint.isActive = true
} else {
self.cardVisibleConstraint.isActive = false
self.cardHiddenConstraint.isActive = true
}
self.view.layoutIfNeeded()
}
frameAnimator.startAnimation()
}
}
Ok, I'll try to give you a compact solution with a minimum of code.好的,我会尽量用最少的代码给你一个紧凑的解决方案。
Fast solution.快速解决。 You need to present a controller modally with modalPresentationStyle
- property set to .overCurrentContext
.您需要以模态方式呈现控制器, modalPresentationStyle
- 属性设置为.overCurrentContext
。 You can set the value before preset(controller:...)
-method get called or in prepare(for:...)
-one if it's a segue transition.您可以在调用preset(controller:...)
-method 之前或在prepare(for:...)
-one 之前设置值,如果它是 segue 转换。 For sliding up use modalTransitionStyle
set to .coverVertical
.对于向上滑动使用modalTransitionStyle
设置为.coverVertical
。
To "zoom out" source view just update its bounds in viewWill(Diss)appear
-methods.要“缩小”源视图,只需在viewWill(Diss)appear
-methods 中更新其边界。 In most of cases this will work.在大多数情况下,这会起作用。
Don't forget to set your modal controller background view transparent so the underlying view still be visible.不要忘记将模态控制器背景视图设置为透明,以便底层视图仍然可见。
Sliding up/down smoothly.平稳地向上/向下滑动。 You need to setup a transition between the controllers in a proper way.您需要以适当的方式设置控制器之间的转换。 If you look closer to Apple music app, you'll see a way to hide top controller with slide down gesture.如果您仔细观察 Apple 音乐应用程序,您会看到一种通过向下滑动手势隐藏顶部控制器的方法。 You can customise your view (dis)appearance too.您也可以自定义您的视图(dis)外观。 Take a look at this article .看看这篇文章。 It uses UIKit
-methods only.它仅使用UIKit
方法。 Unfortunately this way requires lots of code, but you can use 3rd party libraries to setup transitions.不幸的是,这种方式需要大量代码,但您可以使用 3rd 方库来设置转换。 Like this one .像这个。
如果您愿意添加第三方依赖项,请尝试SPStorkController ,它是最新的(撰写本文时为 Swift 4.2)并且只需最少的配置即可使用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.