GBA002/Delta/Pause Menu/Presentation Controller/PausePresentationController.swift

200 lines
8.2 KiB
Swift

//
// PausePresentationController.swift
// Delta
//
// Created by Riley Testut on 12/21/15.
// Copyright © 2015 Riley Testut. All rights reserved.
//
import UIKit
import Roxas
protocol PauseInfoProviding
{
var pauseText: String? { get }
}
class PausePresentationController: UIPresentationController
{
let presentationAnimator: UIViewPropertyAnimator
private let blurringView: UIVisualEffectView
private let vibrancyView: UIVisualEffectView
private var contentView: UIView!
// Must not be weak, or else may result in crash when deallocating.
@IBOutlet private var pauseLabel: UILabel!
@IBOutlet private var pauseIconImageView: UIImageView!
@IBOutlet private var stackView: UIStackView!
override var frameOfPresentedViewInContainerView: CGRect
{
guard let containerView = self.containerView else { return super.frameOfPresentedViewInContainerView }
let frame: CGRect
let contentHeight = self.presentedViewController.preferredContentSize.height
if contentHeight == 0
{
let statusBarHeight = UIApplication.shared.statusBarFrame.height
frame = CGRect(x: 0, y: statusBarHeight, width: containerView.bounds.width, height: containerView.bounds.height - statusBarHeight)
}
else
{
frame = CGRect(x: 0, y: containerView.bounds.height - contentHeight, width: containerView.bounds.width, height: containerView.bounds.height)
}
return frame
}
init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?, presentationAnimator: UIViewPropertyAnimator)
{
self.presentationAnimator = presentationAnimator
self.blurringView = UIVisualEffectView(effect: nil)
self.vibrancyView = UIVisualEffectView(effect: nil)
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
self.contentView = Bundle.main.loadNibNamed("PausePresentationControllerContentView", owner: self, options: nil)?.first as! UIView
}
override func presentationTransitionWillBegin()
{
if let provider = self.presentedViewController as? PauseInfoProviding
{
self.pauseLabel.text = provider.pauseText
}
else if
let navigationController = self.presentedViewController as? UINavigationController,
let provider = navigationController.topViewController as? PauseInfoProviding
{
self.pauseLabel.text = provider.pauseText
}
else
{
self.pauseLabel.text = nil
}
self.blurringView.frame = self.containerView!.frame
self.blurringView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.containerView?.addSubview(self.blurringView)
self.vibrancyView.frame = self.containerView!.frame
self.vibrancyView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.containerView?.addSubview(self.vibrancyView)
self.contentView.alpha = 0.0
self.vibrancyView.contentView.addSubview(self.contentView)
self.presentationAnimator.addAnimations {
let blurEffect = UIBlurEffect(style: .dark)
self.blurringView.effect = blurEffect
self.vibrancyView.effect = UIVibrancyEffect(blurEffect: blurEffect)
self.contentView.alpha = 1.0
}
// I have absolutely no clue why animating with transition coordinator results in no animation on iOS 11.
// Spent far too long trying to fix it, so just use the presentation animator.
// self.presentingViewController.transitionCoordinator?.animate(alongsideTransition: { context in }, completion: nil)
}
override func dismissalTransitionWillBegin()
{
self.presentingViewController.transitionCoordinator?.animate(alongsideTransition: { context in
self.blurringView.effect = nil
self.vibrancyView.effect = nil
self.contentView.alpha = 0.0
}, completion: nil)
}
override func dismissalTransitionDidEnd(_ completed: Bool)
{
self.blurringView.removeFromSuperview()
self.vibrancyView.removeFromSuperview()
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
{
super.viewWillTransition(to: size, with: coordinator)
// Super super hacky, but the system for some reason tries to layout the view in a (slightly) smaller space, which sometimes breaks constraints
// To fix this, we ensure there is enough room for the constraints to be valid temporarily, and then the frame will be fixed in containerViewDidLayoutSubviews()
let currentY = self.contentView.frame.minY
self.contentView.frame = self.containerView!.bounds
self.contentView.frame.origin.y = currentY
if self.presentedView!.frame.minY == 0
{
// Temporarily offset top of presentedView by a small amount to prevent navigation bar from growing when rotating from landscape to portrait
self.presentedView?.frame.origin.y = 0.5
}
}
override func containerViewDidLayoutSubviews()
{
super.containerViewDidLayoutSubviews()
// Magical calculations. If you edit ANY of them, you have to make sure everything still lays out correctly on *all* devices
// So, I'd recommend that you not touch this :)
/* Hacky Layout Bug Workaround */
// For some reason, attempting to calculate the layout while contentView is in the view hierarchy doesn't properly follow constraint priorities exactly
// Specifically, on 5s with long pause label text, it will sometimes resize the text before the image, or it will not resize the image enough for the size class
self.contentView.removeFromSuperview()
// Temporarily match the bounds of self.containerView (accounting for the status bar)
let statusBarHeight = UIApplication.shared.statusBarFrame.height
self.contentView.frame = CGRect(x: 0, y: statusBarHeight, width: self.containerView!.bounds.width, height: self.containerView!.bounds.height - statusBarHeight)
// Layout content view
self.contentView.setNeedsLayout()
self.contentView.layoutIfNeeded()
// Add back to the view hierarchy
self.vibrancyView.contentView.addSubview(self.contentView)
/* Resume Normal Calculations */
// Ensure width is correct
self.presentedView?.bounds.size.width = self.containerView!.bounds.width
self.presentedView?.setNeedsLayout()
self.presentedView?.layoutIfNeeded()
if self.presentingViewController.transitionCoordinator == nil
{
self.presentedView?.frame = self.frameOfPresentedViewInContainerView
}
// Unhide pauseIconImageView so its height is involved with layout calculations
self.pauseIconImageView.isHidden = false
self.contentView.frame = CGRect(x: 0, y: statusBarHeight, width: self.containerView!.bounds.width, height: self.frameOfPresentedViewInContainerView.minY - statusBarHeight)
self.contentView.setNeedsLayout() // Ensures that layout will actually occur (sometimes the system thinks a layout is not needed, which messes up calculations)
self.contentView.layoutIfNeeded()
let currentScaleFactor = self.pauseLabel.currentScaleFactor
if currentScaleFactor < self.pauseLabel.minimumScaleFactor || CGFloatEqualToFloat(currentScaleFactor, self.pauseLabel.minimumScaleFactor)
{
self.pauseIconImageView.isHidden = true
}
else
{
self.pauseIconImageView.isHidden = false
}
self.contentView.setNeedsLayout() // Ensures that layout will actually occur (sometimes the system thinks a layout is not needed, which messes up calculations)
self.contentView.layoutIfNeeded()
}
}