AnimatablePresentationController.swift 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. //
  2. // Created by Tom Baranes on 16/07/16.
  3. // Copyright © 2016 Jake Lin. All rights reserved.
  4. //
  5. import UIKit
  6. /// `AnimatablePresentationController` is a subclass of `UIPresentationController` with `PresentationConfiguration` to specify the dimming view and modal view for presentation.
  7. public class AnimatablePresentationController: UIPresentationController {
  8. // MARK: Properties
  9. fileprivate let presentationConfiguration: PresentationConfiguration
  10. fileprivate var dimmingView = AnimatableView()
  11. fileprivate var presentationBackgroundView = PresentationBackgroundView()
  12. fileprivate var containerFrame: CGRect {
  13. return presentationConfiguration.contextFrameForPresentation ?? containerView?.bounds ?? .zero
  14. }
  15. // MARK: Init
  16. init(presentedViewController: UIViewController,
  17. presentingViewController: UIViewController?,
  18. presentationConfiguration: PresentationConfiguration) {
  19. self.presentationConfiguration = presentationConfiguration
  20. super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
  21. configureDimmingView()
  22. configurePresentedView()
  23. configureObservers()
  24. }
  25. deinit {
  26. NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
  27. NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
  28. }
  29. // MARK: Actions
  30. @objc
  31. func dimmingViewTapped(gesture: UIGestureRecognizer) {
  32. if gesture.state == .ended && presentationConfiguration.dismissOnTap {
  33. presentingViewController.dismiss(animated: true, completion: nil)
  34. }
  35. }
  36. }
  37. // MARK: - Setup
  38. private extension AnimatablePresentationController {
  39. func configureDimmingView() {
  40. var tap = UITapGestureRecognizer(target: self, action: #selector(dimmingViewTapped))
  41. dimmingView.addGestureRecognizer(tap)
  42. tap = UITapGestureRecognizer(target: self, action: #selector(dimmingViewTapped))
  43. presentationBackgroundView.addGestureRecognizer(tap)
  44. if let blurEffectStyle = presentationConfiguration.blurEffectStyle {
  45. dimmingView.blurEffectStyle = blurEffectStyle
  46. dimmingView.blurOpacity = presentationConfiguration.blurOpacity
  47. } else {
  48. dimmingView.fillColor = presentationConfiguration.backgroundColor.withAlphaComponent(presentationConfiguration.opacity)
  49. }
  50. }
  51. func configurePresentedView() {
  52. if presentationConfiguration.cornerRadius > 0 {
  53. presentedViewController.view.layer.cornerRadius = presentationConfiguration.cornerRadius
  54. presentedViewController.view.layer.masksToBounds = true
  55. }
  56. // Set up shadow
  57. presentedViewController.view.layer.shadowOffset.width = presentationConfiguration.shadowOffset.x
  58. presentedViewController.view.layer.shadowOffset.height = presentationConfiguration.shadowOffset.y
  59. presentedViewController.view.layer.shadowOpacity = Float(presentationConfiguration.shadowOpacity)
  60. if let shadowColor = presentationConfiguration.shadowColor, presentedViewController.view.layer.shadowRadius > 0 {
  61. presentedViewController.view.layer.shadowRadius = presentationConfiguration.shadowRadius
  62. presentedViewController.view.layer.shadowColor = shadowColor.cgColor
  63. presentedViewController.view.layer.masksToBounds = false
  64. }
  65. }
  66. }
  67. // MARK: - Notifications
  68. extension AnimatablePresentationController {
  69. fileprivate func configureObservers() {
  70. guard presentationConfiguration.keyboardTranslation != .none else {
  71. return
  72. }
  73. NotificationCenter.default.addObserver(
  74. self,
  75. selector: #selector(keyboardWillShow(notification:)),
  76. name: UIResponder.keyboardWillShowNotification,
  77. object: nil
  78. )
  79. NotificationCenter.default.addObserver(
  80. self,
  81. selector: #selector(keyboardWillHide(notification:)),
  82. name: UIResponder.keyboardWillHideNotification,
  83. object: nil
  84. )
  85. }
  86. @objc
  87. func keyboardWillShow(notification: NSNotification) {
  88. if let keyboardFrame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
  89. let presentedFrame = frameOfPresentedViewInContainerView
  90. let duration = (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as AnyObject).doubleValue ?? 0.5
  91. let translatedFrame = presentationConfiguration.keyboardTranslation.translationFrame(keyboardFrame: keyboardFrame,
  92. presentedFrame: presentedFrame)
  93. let curve = UIView.AnimationOptions(rawValue: UInt(duration))
  94. UIView.animate(withDuration: duration, delay: 0, options: curve, animations: {
  95. self.presentedView?.frame = translatedFrame
  96. }, completion: nil)
  97. }
  98. }
  99. @objc
  100. func keyboardWillHide(notification: NSNotification) {
  101. let duration = (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as AnyObject).doubleValue ?? 0.5
  102. let curve = UIView.AnimationOptions(rawValue: UInt(duration))
  103. UIView.animate(withDuration: duration, delay: 0, options: curve, animations: {
  104. self.presentedView!.frame = self.frameOfPresentedViewInContainerView
  105. }, completion: nil)
  106. }
  107. }
  108. // MARK: - Size & origin helpers
  109. private extension AnimatablePresentationController {
  110. func modalCenter(for modalSize: CGSize) -> CGPoint? {
  111. return presentationConfiguration.modalPosition.modalCenter(in: containerFrame, modalSize: modalSize)
  112. }
  113. var modalOrigin: CGPoint? {
  114. return presentationConfiguration.modalPosition.calculateOrigin()
  115. }
  116. func calculateOrigin(center: CGPoint, size: CGSize) -> CGPoint {
  117. let x: CGFloat = center.x - size.width / 2
  118. let y: CGFloat = center.y - size.height / 2
  119. return CGPoint(x: x, y: y)
  120. }
  121. }
  122. // MARK: - UIPresentationController
  123. public extension AnimatablePresentationController {
  124. // MARK: Presentation
  125. override var frameOfPresentedViewInContainerView: CGRect {
  126. let containerBounds = containerFrame
  127. var presentedViewFrame = CGRect.zero
  128. let sizeForChildContentContainer = size(forChildContentContainer: presentedViewController, withParentContainerSize: containerBounds.size)
  129. let origin: CGPoint
  130. if let center = modalCenter(for: sizeForChildContentContainer) {
  131. origin = calculateOrigin(center: center, size: sizeForChildContentContainer)
  132. } else {
  133. origin = modalOrigin ?? .zero
  134. }
  135. presentedViewFrame.size = sizeForChildContentContainer
  136. presentedViewFrame.origin = origin
  137. return presentedViewFrame
  138. }
  139. override func size(forChildContentContainer container: UIContentContainer, withParentContainerSize parentSize: CGSize) -> CGSize {
  140. let preferredContentSize = container.preferredContentSize
  141. let widthSize = presentationConfiguration.modalSize.0
  142. let heightSize = presentationConfiguration.modalSize.1
  143. let width: CGFloat
  144. if preferredContentSize.width != 0, case .preferred = widthSize {
  145. width = preferredContentSize.width
  146. } else {
  147. width = CGFloat(widthSize.width(parentSize: parentSize))
  148. }
  149. let height: CGFloat
  150. if preferredContentSize.height != 0, case .preferred = heightSize {
  151. height = preferredContentSize.height
  152. } else {
  153. height = CGFloat(heightSize.height(parentSize: parentSize))
  154. }
  155. return CGSize(width: width, height: height)
  156. }
  157. override func containerViewWillLayoutSubviews() {
  158. dimmingView.frame = containerFrame
  159. presentedView?.frame = frameOfPresentedViewInContainerView
  160. }
  161. // MARK: Animation
  162. override func presentationTransitionWillBegin() {
  163. presentationBackgroundView.frame = containerView?.bounds ?? .zero
  164. presentationBackgroundView.passthroughViews = presentingViewController.view.subviews
  165. containerView?.insertSubview(presentationBackgroundView, at: 0)
  166. dimmingView.frame = containerFrame
  167. dimmingView.alpha = 0.0
  168. containerView?.insertSubview(dimmingView, at: 1)
  169. if let coordinator = presentedViewController.transitionCoordinator {
  170. coordinator.animate(alongsideTransition: { _ in
  171. self.dimmingView.alpha = 1.0
  172. }, completion: nil)
  173. } else {
  174. dimmingView.alpha = 1.0
  175. }
  176. }
  177. override func presentationTransitionDidEnd(_ completed: Bool) {
  178. if !completed {
  179. dimmingView.removeFromSuperview()
  180. }
  181. }
  182. override func dismissalTransitionWillBegin() {
  183. if let coordinator = presentedViewController.transitionCoordinator {
  184. coordinator.animate(alongsideTransition: { _ in
  185. self.dimmingView.alpha = 0.0
  186. }, completion: nil)
  187. } else {
  188. dimmingView.alpha = 0.0
  189. }
  190. }
  191. override func dismissalTransitionDidEnd(_ completed: Bool) {
  192. if completed {
  193. dimmingView.removeFromSuperview()
  194. }
  195. }
  196. }