IOS 10のミュージックアプリでは、新しいカードのような外観が採用されています。[再生中]画面が上にスライドし、階層の下のビューがズームアウトして画面の上部にわずかに突き出ます。
メール作成ウィンドウの例を次に示します。
この比phorは、人気のポッドキャストプレーヤーであるOvercastでも見ることができます。
UIKitには、このカードのような外観を実現する機能がありますか?
インターフェイスビルダーでセグエをビルドできます。 ViewController
から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
}
}
次に、この拡張機能を追加します。
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
}
}
}
最後に、さらに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)
}
}
そして:
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)
})
}
}
最後に、CardViewController
クロージングをアニメートするには、クロージングボタンをFirstResponder
にフックしてdismiss
を選択し、このメソッドをViewController
に追加します。
func dismiss(_ segue: UIStoryboardSegue) {
self.dismiss(animated: true, completion: nil)
}
Appleは UIViewPropertyAnimator
in WWDC 2017 Session 230:Advanced Animations with UIKit を使用してこれを行う方法を示します
ここにこのテクニックのデモがあります: https://github.com/peteog/CardUI
基本的な考え方は、子View Controllerを追加し、ほとんど画面外に配置することです。タップ/パンすると、子View Controllerのフレームをアニメーション化します。
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()
}
}
わかりました、最小限のコードでコンパクトなソリューションを提供しようとします。
高速ソリューション。コントローラをmodalPresentationStyle
- property を.overCurrentContext
にモーダルで提示する必要があります。preset(controller:...)
-methodが呼び出される前に値を設定できます。 prepare(for:...)
-1で、それがセグエ遷移の場合は1スライドアップするには、modalTransitionStyle
を.coverVertical
に設定して使用します。
ソースビューを「ズームアウト」するには、viewWill(Diss)appear
-methodsの境界を更新するだけです。ほとんどの場合、これは機能します。
モーダルコントローラーの背景ビューを透明に設定して、下にあるビューが表示されることを忘れないでください。
スムーズに上下にスライドします。適切な方法でコントローラー間で 移行 を設定する必要があります。Apple音楽アプリでは、スライドダウンジェスチャでトップコントローラーを非表示にする方法が表示されます。ビュー(dis)の外観もカスタマイズできます。 この記事 を参照してください。UIKit
- methodsのみを使用します。大量のコードが必要ですが、サードパーティのライブラリを使用してトランジションをセットアップできます。 this one 。
サードパーティの依存関係を追加して満足している場合は、 SPStorkController を試してください。これは最新で(これを書いている時点ではSwift 4.2)、最小限の構成で動作します。