NCPopupViewController.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. //
  2. // NCPopupViewController.swift
  3. //
  4. // Based on EzPopup by Huy Nguyen
  5. // Modified by Marino Faggiana for Nextcloud progect.
  6. //
  7. // Author Marino Faggiana <marino.faggiana@nextcloud.com>
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU General Public License as published by
  11. // the Free Software Foundation, either version 3 of the License, or
  12. // (at your option) any later version.
  13. //
  14. // This program is distributed in the hope that it will be useful,
  15. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. // GNU General Public License for more details.
  18. //
  19. // You should have received a copy of the GNU General Public License
  20. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. //
  22. import UIKit
  23. public protocol NCPopupViewControllerDelegate: class {
  24. // It is called when pop up is dismissed by tap outside
  25. func popupViewControllerDidDismissByTapGesture(_ sender: NCPopupViewController)
  26. }
  27. // optional func
  28. public extension NCPopupViewControllerDelegate {
  29. func popupViewControllerDidDismissByTapGesture(_ sender: NCPopupViewController) {}
  30. }
  31. public class NCPopupViewController: UIViewController {
  32. private var centerYConstraint: NSLayoutConstraint?
  33. // Popup width, it's nil if width is determined by view's intrinsic size
  34. private(set) public var popupWidth: CGFloat?
  35. // Popup height, it's nil if width is determined by view's intrinsic size
  36. private(set) public var popupHeight: CGFloat?
  37. // Background alpha, default is 0.3
  38. public var backgroundAlpha: CGFloat = 0.3
  39. // Background color, default is black
  40. public var backgroundColor = UIColor.black
  41. // Allow tap outside popup to dismiss, default is true
  42. public var canTapOutsideToDismiss = true
  43. // Corner radius, default is 10 (0 no rounded corner)
  44. public var cornerRadius: CGFloat = 10
  45. // Shadow enabled, default is true
  46. public var shadowEnabled = true
  47. // Move the popup position H when show/hide keyboard
  48. public var keyboardPosizionEnabled = true
  49. // The pop up view controller. It's not mandatory.
  50. private(set) public var contentController: UIViewController?
  51. // The pop up view
  52. private(set) public var contentView: UIView?
  53. // The delegate to receive pop up event
  54. public weak var delegate: NCPopupViewControllerDelegate?
  55. private var containerView = UIView()
  56. // MARK: -
  57. // NOTE: Don't use this init method
  58. required public init?(coder aDecoder: NSCoder) {
  59. super.init(coder: aDecoder)
  60. }
  61. /**
  62. Init with content view controller. Your pop up content is a view controller (easiest way to design it is using storyboard)
  63. - Parameters:
  64. - contentController: Popup content view controller
  65. - popupWidth: Width of popup content. If it isn't set, width will be determine by popup content view intrinsic size.
  66. - popupHeight: Height of popup content. If it isn't set, height will be determine by popup content view intrinsic size.
  67. */
  68. public init(contentController: UIViewController, popupWidth: CGFloat? = nil, popupHeight: CGFloat? = nil) {
  69. super.init(nibName: nil, bundle: nil)
  70. self.contentController = contentController
  71. self.contentView = contentController.view
  72. self.popupWidth = popupWidth
  73. self.popupHeight = popupHeight
  74. commonInit()
  75. }
  76. /**
  77. Init with content view
  78. - Parameters:
  79. - contentView: Popup content view
  80. - popupWidth: Width of popup content. If it isn't set, width will be determine by popup content view intrinsic size.
  81. - popupHeight: Height of popup content. If it isn't set, height will be determine by popup content view intrinsic size.
  82. */
  83. public init(contentView: UIView, popupWidth: CGFloat? = nil, popupHeight: CGFloat? = nil) {
  84. super.init(nibName: nil, bundle: nil)
  85. self.contentView = contentView
  86. self.popupWidth = popupWidth
  87. self.popupHeight = popupHeight
  88. commonInit()
  89. }
  90. private func commonInit() {
  91. modalPresentationStyle = .overFullScreen
  92. modalTransitionStyle = .crossDissolve
  93. }
  94. override public func viewDidLoad() {
  95. super.viewDidLoad()
  96. setupUI()
  97. setupViews()
  98. addDismissGesture()
  99. NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
  100. NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil);
  101. }
  102. // MARK: -
  103. @objc internal func keyboardWillShow(_ notification : Notification?) {
  104. var keyboardSize = CGSize.zero
  105. if let info = notification?.userInfo {
  106. let frameEndUserInfoKey = UIResponder.keyboardFrameEndUserInfoKey
  107. // Getting UIKeyboardSize.
  108. if let keyboardFrame = info[frameEndUserInfoKey] as? CGRect {
  109. let screenSize = UIScreen.main.bounds
  110. //Calculating actual keyboard displayed size, keyboard frame may be different when hardware keyboard is attached (Bug ID: #469) (Bug ID: #381)
  111. let intersectRect = keyboardFrame.intersection(screenSize)
  112. if intersectRect.isNull {
  113. keyboardSize = CGSize(width: screenSize.size.width, height: 0)
  114. } else {
  115. keyboardSize = intersectRect.size
  116. }
  117. if keyboardPosizionEnabled {
  118. let popupDiff = screenSize.height - ((screenSize.height - (popupHeight ?? 0)) / 2)
  119. let keyboardDiff = screenSize.height - keyboardSize.height
  120. let diff = popupDiff - keyboardDiff
  121. if centerYConstraint != nil && diff > 0 {
  122. centerYConstraint?.constant = -(diff + 10)
  123. }
  124. }
  125. }
  126. }
  127. }
  128. @objc func keyboardWillHide(_ notification: Notification) {
  129. if keyboardPosizionEnabled {
  130. if centerYConstraint != nil {
  131. centerYConstraint?.constant = 0
  132. }
  133. }
  134. }
  135. // MARK: - Setup
  136. private func addDismissGesture() {
  137. let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissTapGesture(gesture:)))
  138. tapGesture.delegate = self
  139. view.addGestureRecognizer(tapGesture)
  140. }
  141. private func setupUI() {
  142. containerView.translatesAutoresizingMaskIntoConstraints = false
  143. contentView?.translatesAutoresizingMaskIntoConstraints = false
  144. view.backgroundColor = backgroundColor.withAlphaComponent(backgroundAlpha)
  145. if cornerRadius > 0 {
  146. contentView?.layer.cornerRadius = cornerRadius
  147. contentView?.layer.masksToBounds = true
  148. }
  149. if shadowEnabled {
  150. containerView.layer.shadowOpacity = 0.5
  151. containerView.layer.shadowColor = UIColor.black.cgColor
  152. containerView.layer.shadowOffset = CGSize(width: 5, height: 5)
  153. containerView.layer.shadowRadius = 5
  154. }
  155. }
  156. private func setupViews() {
  157. if let contentController = contentController {
  158. addChild(contentController)
  159. }
  160. addViews()
  161. addSizeConstraints()
  162. addCenterPositionConstraints()
  163. }
  164. private func addViews() {
  165. view.addSubview(containerView)
  166. if let contentView = contentView {
  167. containerView.addSubview(contentView)
  168. let topConstraint = NSLayoutConstraint(item: contentView, attribute: .top, relatedBy: .equal, toItem: containerView, attribute: .top, multiplier: 1, constant: 0)
  169. let leftConstraint = NSLayoutConstraint(item: contentView, attribute: .left, relatedBy: .equal, toItem: containerView, attribute: .left, multiplier: 1, constant: 0)
  170. let bottomConstraint = NSLayoutConstraint(item: contentView, attribute: .bottom, relatedBy: .equal, toItem: containerView, attribute: .bottom, multiplier: 1, constant: 0)
  171. let rightConstraint = NSLayoutConstraint(item: contentView, attribute: .right, relatedBy: .equal, toItem: containerView, attribute: .right, multiplier: 1, constant: 0)
  172. NSLayoutConstraint.activate([topConstraint, leftConstraint, bottomConstraint, rightConstraint])
  173. }
  174. }
  175. // MARK: - Add constraints
  176. private func addSizeConstraints() {
  177. if let popupWidth = popupWidth {
  178. let widthConstraint = NSLayoutConstraint(item: containerView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: popupWidth)
  179. NSLayoutConstraint.activate([widthConstraint])
  180. }
  181. if let popupHeight = popupHeight {
  182. let heightConstraint = NSLayoutConstraint(item: containerView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: popupHeight)
  183. NSLayoutConstraint.activate([heightConstraint])
  184. }
  185. }
  186. private func addCenterPositionConstraints() {
  187. let centerXConstraint = NSLayoutConstraint(item: containerView, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1, constant: 0)
  188. centerYConstraint = NSLayoutConstraint(item: containerView, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1, constant: 0)
  189. NSLayoutConstraint.activate([centerXConstraint, centerYConstraint!])
  190. }
  191. // MARK: - Actions
  192. @objc func dismissTapGesture(gesture: UIGestureRecognizer) {
  193. dismiss(animated: true) {
  194. self.delegate?.popupViewControllerDidDismissByTapGesture(self)
  195. }
  196. }
  197. }
  198. // MARK: - UIGestureRecognizerDelegate
  199. extension NCPopupViewController: UIGestureRecognizerDelegate {
  200. public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
  201. guard let touchView = touch.view, canTapOutsideToDismiss else {
  202. return false
  203. }
  204. return !touchView.isDescendant(of: containerView)
  205. }
  206. }