GBA001/Delta/Game Selection/Segues/GamesStoryboardSegue.swift
Riley Testut 63c932561e Fixes incorrect GamesStoryboardSegue animation on iOS 13
The additional toolbars to extend the edges beyond the navigation controller's bounds weren't positioned correctly on iOS 13.
2019-10-10 19:26:13 -07:00

201 lines
9.8 KiB
Swift

//
// GamesStoryboardSegue.swift
// Delta
//
// Created by Riley Testut on 8/7/16.
// Copyright © 2016 Riley Testut. All rights reserved.
//
import UIKit
class GamesStoryboardSegue: UIStoryboardSegue
{
private let animator: UIViewPropertyAnimator
private var isPresenting: Bool = true
override init(identifier: String?, source: UIViewController, destination: UIViewController)
{
let timingParameters = UISpringTimingParameters(mass: 3.0, stiffness: 750, damping: 65, initialVelocity: CGVector(dx: 0, dy: 0))
self.animator = UIViewPropertyAnimator(duration: 0, timingParameters: timingParameters)
super.init(identifier: identifier, source: source, destination: destination)
}
override func perform()
{
self.destination.transitioningDelegate = self
self.destination.modalPresentationStyle = .custom
self.destination.modalPresentationCapturesStatusBarAppearance = true
super.perform()
}
}
extension GamesStoryboardSegue: UIViewControllerTransitioningDelegate
{
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?
{
self.isPresenting = true
return self
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?
{
self.isPresenting = false
return self
}
func presentationController(forPresented presentedViewController: UIViewController, presenting presentingViewController: UIViewController?, source: UIViewController) -> UIPresentationController?
{
let presentationController = GamesPresentationController(presentedViewController: presentedViewController, presenting: presentingViewController, animator: self.animator)
return presentationController
}
}
extension GamesStoryboardSegue: UIViewControllerAnimatedTransitioning
{
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
{
return self.animator.duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning)
{
if self.isPresenting
{
self.animatePresentationTransition(using: transitionContext)
}
else
{
self.animateDismissalTransition(using: transitionContext)
}
}
func animatePresentationTransition(using transitionContext: UIViewControllerContextTransitioning)
{
transitionContext.sourceViewController.beginAppearanceTransition(false, animated: true)
transitionContext.destinationView.clipsToBounds = false
transitionContext.destinationView.frame = transitionContext.destinationViewFinalFrame!
transitionContext.destinationView.transform = CGAffineTransform(scaleX: 2.0, y: 2.0)
transitionContext.containerView.addSubview(transitionContext.destinationView)
let snapshotView = transitionContext.sourceView.snapshotView(afterScreenUpdates: false)!
snapshotView.frame = transitionContext.sourceViewInitialFrame!
snapshotView.alpha = 1.0
transitionContext.containerView.addSubview(snapshotView)
// We add extra padding around the existing navigation bar and toolbar so they never appear to be detached from the edges of the screen during the overshooting of the spring animation
var topPaddingToolbar: UIToolbar? = nil
var bottomPaddingToolbar: UIToolbar? = nil
// Must be wrapped in no-animation block to prevent iOS 11 search bar from not appearing.
UIView.performWithoutAnimation {
// Ensures navigation controller toolbar (if visible) has been added to view heirachy, allowing us to add constraints
transitionContext.containerView.layoutIfNeeded()
if let navigationController = transitionContext.destinationViewController as? UINavigationController
{
let padding: CGFloat = 44
let topViewController = navigationController.viewControllers[0]
if !navigationController.isNavigationBarHidden
{
let topToolbar = UIToolbar(frame: CGRect.zero)
topToolbar.translatesAutoresizingMaskIntoConstraints = false
topToolbar.barStyle = navigationController.toolbar.barStyle
transitionContext.destinationView.insertSubview(topToolbar, at: 1)
if #available(iOS 13, *)
{
let appearance = UIToolbarAppearance(barAppearance: navigationController.navigationBar.standardAppearance)
topToolbar.standardAppearance = appearance
topToolbar.topAnchor.constraint(equalTo: topViewController.view.topAnchor, constant: -padding).isActive = true
topToolbar.leftAnchor.constraint(equalTo: topViewController.view.leftAnchor, constant: -padding).isActive = true
topToolbar.rightAnchor.constraint(equalTo: topViewController.view.rightAnchor, constant: padding).isActive = true
topToolbar.bottomAnchor.constraint(equalTo: topViewController.view.safeAreaLayoutGuide.topAnchor).isActive = true
}
else
{
topToolbar.topAnchor.constraint(equalTo: navigationController.navigationBar.topAnchor, constant: -padding).isActive = true
topToolbar.leftAnchor.constraint(equalTo: navigationController.navigationBar.leftAnchor, constant: -padding).isActive = true
topToolbar.rightAnchor.constraint(equalTo: navigationController.navigationBar.rightAnchor, constant: padding).isActive = true
// There is no easy way to determine the extra height necessary at this point of the transition, so hard code for now.
let additionalSearchBarHeight = 44 as CGFloat
topToolbar.heightAnchor.constraint(equalToConstant: navigationController.topViewController!.view.safeAreaInsets.top + additionalSearchBarHeight).isActive = true
}
topPaddingToolbar = topToolbar
}
if !navigationController.isToolbarHidden
{
let bottomToolbar = UIToolbar(frame: CGRect.zero)
bottomToolbar.translatesAutoresizingMaskIntoConstraints = false
bottomToolbar.barStyle = navigationController.toolbar.barStyle
transitionContext.destinationView.insertSubview(bottomToolbar, belowSubview: navigationController.navigationBar)
if #available(iOS 13, *)
{
let appearance = UIToolbarAppearance(barAppearance: navigationController.toolbar.standardAppearance)
bottomToolbar.standardAppearance = appearance
bottomToolbar.topAnchor.constraint(equalTo: topViewController.view.safeAreaLayoutGuide.bottomAnchor).isActive = true
bottomToolbar.bottomAnchor.constraint(equalTo: topViewController.view.bottomAnchor, constant: padding).isActive = true
bottomToolbar.leftAnchor.constraint(equalTo: topViewController.view.leftAnchor, constant: -padding).isActive = true
bottomToolbar.rightAnchor.constraint(equalTo: topViewController.view.rightAnchor, constant: padding).isActive = true
}
else
{
bottomToolbar.topAnchor.constraint(equalTo: navigationController.toolbar.topAnchor).isActive = true
bottomToolbar.bottomAnchor.constraint(equalTo: navigationController.toolbar.bottomAnchor, constant: padding).isActive = true
bottomToolbar.leftAnchor.constraint(equalTo: navigationController.toolbar.leftAnchor, constant: -padding).isActive = true
bottomToolbar.rightAnchor.constraint(equalTo: navigationController.toolbar.rightAnchor, constant: padding).isActive = true
}
bottomPaddingToolbar = bottomToolbar
}
}
}
self.animator.addAnimations {
snapshotView.alpha = 0.0
transitionContext.destinationView.transform = CGAffineTransform.identity
}
self.animator.addCompletion { (position) in
transitionContext.completeTransition(position == .end)
snapshotView.removeFromSuperview()
topPaddingToolbar?.removeFromSuperview()
bottomPaddingToolbar?.removeFromSuperview()
transitionContext.sourceViewController.endAppearanceTransition()
}
self.animator.startAnimation()
}
func animateDismissalTransition(using transitionContext: UIViewControllerContextTransitioning)
{
transitionContext.destinationViewController.beginAppearanceTransition(true, animated: true)
self.animator.addAnimations {
transitionContext.sourceView.alpha = 0.0
transitionContext.sourceView.transform = CGAffineTransform(scaleX: 2.0, y: 2.0)
}
self.animator.addCompletion { (position) in
transitionContext.completeTransition(position == .end)
transitionContext.destinationViewController.endAppearanceTransition()
}
self.animator.startAnimation()
}
}