123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 |
- //
- // Created by Tom Baranes on 16/07/16.
- // Copyright © 2016 Jake Lin. All rights reserved.
- //
- import UIKit
- /// `AnimatablePresentationController` is a subclass of `UIPresentationController` with `PresentationConfiguration` to specify the dimming view and modal view for presentation.
- public class AnimatablePresentationController: UIPresentationController {
- // MARK: Properties
- fileprivate let presentationConfiguration: PresentationConfiguration
- fileprivate var dimmingView = AnimatableView()
- fileprivate var presentationBackgroundView = PresentationBackgroundView()
- fileprivate var containerFrame: CGRect {
- return presentationConfiguration.contextFrameForPresentation ?? containerView?.bounds ?? .zero
- }
- // MARK: Init
- init(presentedViewController: UIViewController,
- presentingViewController: UIViewController?,
- presentationConfiguration: PresentationConfiguration) {
- self.presentationConfiguration = presentationConfiguration
- super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
- configureDimmingView()
- configurePresentedView()
- configureObservers()
- }
- deinit {
- NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
- NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
- }
- // MARK: Actions
- @objc
- func dimmingViewTapped(gesture: UIGestureRecognizer) {
- if gesture.state == .ended && presentationConfiguration.dismissOnTap {
- presentingViewController.dismiss(animated: true, completion: nil)
- }
- }
- }
- // MARK: - Setup
- private extension AnimatablePresentationController {
- func configureDimmingView() {
- var tap = UITapGestureRecognizer(target: self, action: #selector(dimmingViewTapped))
- dimmingView.addGestureRecognizer(tap)
- tap = UITapGestureRecognizer(target: self, action: #selector(dimmingViewTapped))
- presentationBackgroundView.addGestureRecognizer(tap)
- if let blurEffectStyle = presentationConfiguration.blurEffectStyle {
- dimmingView.blurEffectStyle = blurEffectStyle
- dimmingView.blurOpacity = presentationConfiguration.blurOpacity
- } else {
- dimmingView.fillColor = presentationConfiguration.backgroundColor.withAlphaComponent(presentationConfiguration.opacity)
- }
- }
- func configurePresentedView() {
- if presentationConfiguration.cornerRadius > 0 {
- presentedViewController.view.layer.cornerRadius = presentationConfiguration.cornerRadius
- presentedViewController.view.layer.masksToBounds = true
- }
- // Set up shadow
- presentedViewController.view.layer.shadowOffset.width = presentationConfiguration.shadowOffset.x
- presentedViewController.view.layer.shadowOffset.height = presentationConfiguration.shadowOffset.y
- presentedViewController.view.layer.shadowOpacity = Float(presentationConfiguration.shadowOpacity)
- if let shadowColor = presentationConfiguration.shadowColor, presentedViewController.view.layer.shadowRadius > 0 {
- presentedViewController.view.layer.shadowRadius = presentationConfiguration.shadowRadius
- presentedViewController.view.layer.shadowColor = shadowColor.cgColor
- presentedViewController.view.layer.masksToBounds = false
- }
- }
- }
- // MARK: - Notifications
- extension AnimatablePresentationController {
- fileprivate func configureObservers() {
- guard presentationConfiguration.keyboardTranslation != .none else {
- return
- }
- NotificationCenter.default.addObserver(
- self,
- selector: #selector(keyboardWillShow(notification:)),
- name: UIResponder.keyboardWillShowNotification,
- object: nil
- )
- NotificationCenter.default.addObserver(
- self,
- selector: #selector(keyboardWillHide(notification:)),
- name: UIResponder.keyboardWillHideNotification,
- object: nil
- )
- }
- @objc
- func keyboardWillShow(notification: NSNotification) {
- if let keyboardFrame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
- let presentedFrame = frameOfPresentedViewInContainerView
- let duration = (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as AnyObject).doubleValue ?? 0.5
- let translatedFrame = presentationConfiguration.keyboardTranslation.translationFrame(keyboardFrame: keyboardFrame,
- presentedFrame: presentedFrame)
- let curve = UIView.AnimationOptions(rawValue: UInt(duration))
- UIView.animate(withDuration: duration, delay: 0, options: curve, animations: {
- self.presentedView?.frame = translatedFrame
- }, completion: nil)
- }
- }
- @objc
- func keyboardWillHide(notification: NSNotification) {
- let duration = (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as AnyObject).doubleValue ?? 0.5
- let curve = UIView.AnimationOptions(rawValue: UInt(duration))
- UIView.animate(withDuration: duration, delay: 0, options: curve, animations: {
- self.presentedView!.frame = self.frameOfPresentedViewInContainerView
- }, completion: nil)
- }
- }
- // MARK: - Size & origin helpers
- private extension AnimatablePresentationController {
- func modalCenter(for modalSize: CGSize) -> CGPoint? {
- return presentationConfiguration.modalPosition.modalCenter(in: containerFrame, modalSize: modalSize)
- }
- var modalOrigin: CGPoint? {
- return presentationConfiguration.modalPosition.calculateOrigin()
- }
- func calculateOrigin(center: CGPoint, size: CGSize) -> CGPoint {
- let x: CGFloat = center.x - size.width / 2
- let y: CGFloat = center.y - size.height / 2
- return CGPoint(x: x, y: y)
- }
- }
- // MARK: - UIPresentationController
- public extension AnimatablePresentationController {
- // MARK: Presentation
- override var frameOfPresentedViewInContainerView: CGRect {
- let containerBounds = containerFrame
- var presentedViewFrame = CGRect.zero
- let sizeForChildContentContainer = size(forChildContentContainer: presentedViewController, withParentContainerSize: containerBounds.size)
- let origin: CGPoint
- if let center = modalCenter(for: sizeForChildContentContainer) {
- origin = calculateOrigin(center: center, size: sizeForChildContentContainer)
- } else {
- origin = modalOrigin ?? .zero
- }
- presentedViewFrame.size = sizeForChildContentContainer
- presentedViewFrame.origin = origin
- return presentedViewFrame
- }
- override func size(forChildContentContainer container: UIContentContainer, withParentContainerSize parentSize: CGSize) -> CGSize {
- let preferredContentSize = container.preferredContentSize
- let widthSize = presentationConfiguration.modalSize.0
- let heightSize = presentationConfiguration.modalSize.1
- let width: CGFloat
- if preferredContentSize.width != 0, case .preferred = widthSize {
- width = preferredContentSize.width
- } else {
- width = CGFloat(widthSize.width(parentSize: parentSize))
- }
- let height: CGFloat
- if preferredContentSize.height != 0, case .preferred = heightSize {
- height = preferredContentSize.height
- } else {
- height = CGFloat(heightSize.height(parentSize: parentSize))
- }
- return CGSize(width: width, height: height)
- }
- override func containerViewWillLayoutSubviews() {
- dimmingView.frame = containerFrame
- presentedView?.frame = frameOfPresentedViewInContainerView
- }
- // MARK: Animation
- override func presentationTransitionWillBegin() {
- presentationBackgroundView.frame = containerView?.bounds ?? .zero
- presentationBackgroundView.passthroughViews = presentingViewController.view.subviews
- containerView?.insertSubview(presentationBackgroundView, at: 0)
- dimmingView.frame = containerFrame
- dimmingView.alpha = 0.0
- containerView?.insertSubview(dimmingView, at: 1)
- if let coordinator = presentedViewController.transitionCoordinator {
- coordinator.animate(alongsideTransition: { _ in
- self.dimmingView.alpha = 1.0
- }, completion: nil)
- } else {
- dimmingView.alpha = 1.0
- }
- }
- override func presentationTransitionDidEnd(_ completed: Bool) {
- if !completed {
- dimmingView.removeFromSuperview()
- }
- }
- override func dismissalTransitionWillBegin() {
- if let coordinator = presentedViewController.transitionCoordinator {
- coordinator.animate(alongsideTransition: { _ in
- self.dimmingView.alpha = 0.0
- }, completion: nil)
- } else {
- dimmingView.alpha = 0.0
- }
- }
- override func dismissalTransitionDidEnd(_ completed: Bool) {
- if completed {
- dimmingView.removeFromSuperview()
- }
- }
- }
|