You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

240 lines
9.8 KiB

  1. //
  2. // PMAlertController.swift
  3. // PMAlertController
  4. //
  5. // Created by Paolo Musolino on 07/05/16.
  6. // Copyright © 2018 Codeido. All rights reserved.
  7. //
  8. import UIKit
  9. @objc public enum PMAlertControllerStyle : Int {
  10. case alert // The alert will adopt a width of 270 (like UIAlertController).
  11. case walkthrough //The alert will adopt a width of the screen size minus 18 (from the left and right side). This style is designed to accommodate localization, push notifications and more.
  12. }
  13. @objc open class PMAlertController: UIViewController {
  14. // MARK: Properties
  15. @IBOutlet weak open var alertMaskBackground: UIImageView!
  16. @IBOutlet weak open var alertView: UIView!
  17. @IBOutlet weak open var alertViewWidthConstraint: NSLayoutConstraint!
  18. @IBOutlet weak open var headerView: UIView!
  19. @IBOutlet weak open var headerViewHeightConstraint: NSLayoutConstraint!
  20. @IBOutlet weak open var headerViewTopSpaceConstraint: NSLayoutConstraint!
  21. @IBOutlet weak open var alertImage: UIImageView!
  22. @IBOutlet weak open var alertContentStackView: UIStackView!
  23. @IBOutlet weak open var alertTitle: UILabel!
  24. @IBOutlet weak open var alertDescription: UILabel!
  25. @IBOutlet weak open var alertContentStackViewLeadingConstraint: NSLayoutConstraint!
  26. @IBOutlet weak open var alertContentStackViewTrailingConstraint: NSLayoutConstraint!
  27. @IBOutlet weak open var alertContentStackViewTopConstraint: NSLayoutConstraint!
  28. @IBOutlet weak open var alertActionStackView: UIStackView!
  29. @IBOutlet weak open var alertActionStackViewHeightConstraint: NSLayoutConstraint!
  30. @IBOutlet weak open var alertActionStackViewLeadingConstraint: NSLayoutConstraint!
  31. @IBOutlet weak open var alertActionStackViewTrailingConstraint: NSLayoutConstraint!
  32. @IBOutlet weak open var alertActionStackViewTopConstraint: NSLayoutConstraint!
  33. @IBOutlet weak open var alertActionStackViewBottomConstraint: NSLayoutConstraint!
  34. open var ALERT_STACK_VIEW_HEIGHT : CGFloat = UIScreen.main.bounds.height < 568.0 ? 40 : 62 //if iphone 4 the stack_view_height is 40, else 62
  35. var animator : UIDynamicAnimator?
  36. open var textFields: [UITextField] = []
  37. @objc open var gravityDismissAnimation = true
  38. @objc open var dismissWithBackgroudTouch = false // enable touch background to dismiss. Off by default.
  39. //MARK: - Lifecycle
  40. override open func viewDidAppear(_ animated: Bool) {
  41. super.viewDidAppear(animated)
  42. NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
  43. NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
  44. }
  45. open override func viewDidDisappear(_ animated: Bool) {
  46. super.viewDidDisappear(animated)
  47. NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
  48. NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
  49. }
  50. //MARK: - Initialiser
  51. @objc public convenience init(title: String?, description: String?, image: UIImage?, style: PMAlertControllerStyle) {
  52. self.init()
  53. guard let nib = loadNibAlertController(), let unwrappedView = nib[0] as? UIView else { return }
  54. self.view = unwrappedView
  55. self.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
  56. self.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
  57. alertView.layer.cornerRadius = 5
  58. (image != nil) ? (alertImage.image = image) : (headerViewHeightConstraint.constant = 0)
  59. if let title = title {
  60. alertTitle.text = title
  61. }else{
  62. alertTitle.isHidden = true
  63. }
  64. if let description = description {
  65. alertDescription.text = description
  66. }else{
  67. alertDescription.isHidden = true
  68. }
  69. //if alert width = 270, else width = screen width - 36
  70. alertViewWidthConstraint.constant = (style == .alert) ? 270 : UIScreen.main.bounds.width - 36
  71. //Gesture recognizer for background dismiss with background touch
  72. let tapRecognizer: UITapGestureRecognizer = UITapGestureRecognizer.init(target: self, action: #selector(dismissAlertControllerFromBackgroundTap))
  73. alertMaskBackground.addGestureRecognizer(tapRecognizer)
  74. setShadowAlertView()
  75. }
  76. //MARK: - Actions
  77. @objc open func addAction(_ alertAction: PMAlertAction){
  78. alertActionStackView.addArrangedSubview(alertAction)
  79. if alertActionStackView.arrangedSubviews.count > 2 || hasTextFieldAdded(){
  80. alertActionStackViewHeightConstraint.constant = ALERT_STACK_VIEW_HEIGHT * CGFloat(alertActionStackView.arrangedSubviews.count)
  81. alertActionStackView.axis = .vertical
  82. }
  83. else{
  84. alertActionStackViewHeightConstraint.constant = ALERT_STACK_VIEW_HEIGHT
  85. alertActionStackView.axis = .horizontal
  86. }
  87. alertAction.addTarget(self, action: #selector(PMAlertController.dismissAlertController(_:)), for: .touchUpInside)
  88. }
  89. @objc fileprivate func dismissAlertController(_ sender: PMAlertAction){
  90. self.animateDismissWithGravity(sender.actionStyle)
  91. self.dismiss(animated: true, completion: nil)
  92. }
  93. @objc fileprivate func dismissAlertControllerFromBackgroundTap() {
  94. if !dismissWithBackgroudTouch {
  95. return
  96. }
  97. self.animateDismissWithGravity(.cancel)
  98. self.dismiss(animated: true, completion: nil)
  99. }
  100. //MARK: - Text Fields
  101. @objc open func addTextField(textField:UITextField? = nil, _ configuration: (_ textField: UITextField?) -> Void){
  102. let textField = textField ?? UITextField()
  103. textField.delegate = self
  104. textField.returnKeyType = .done
  105. textField.font = UIFont(name: "Avenir-Heavy", size: 17)
  106. textField.textAlignment = .center
  107. configuration (textField)
  108. _addTextField(textField)
  109. }
  110. func _addTextField(_ textField: UITextField){
  111. alertActionStackView.addArrangedSubview(textField)
  112. alertActionStackViewHeightConstraint.constant = ALERT_STACK_VIEW_HEIGHT * CGFloat(alertActionStackView.arrangedSubviews.count)
  113. alertActionStackView.axis = .vertical
  114. textFields.append(textField)
  115. }
  116. func hasTextFieldAdded () -> Bool{
  117. return textFields.count > 0
  118. }
  119. //MARK: - Customizations
  120. @objc fileprivate func setShadowAlertView(){
  121. alertView.layer.masksToBounds = false
  122. alertView.layer.shadowOffset = CGSize(width: 0, height: 0)
  123. alertView.layer.shadowRadius = 8
  124. alertView.layer.shadowOpacity = 0.3
  125. }
  126. @objc fileprivate func loadNibAlertController() -> [AnyObject]?{
  127. let podBundle = Bundle(for: self.classForCoder)
  128. if let bundleURL = podBundle.url(forResource: "PMAlertController", withExtension: "bundle"){
  129. if let bundle = Bundle(url: bundleURL) {
  130. return bundle.loadNibNamed("PMAlertController", owner: self, options: nil) as [AnyObject]?
  131. }
  132. else {
  133. assertionFailure("Could not load the bundle")
  134. }
  135. }
  136. else if let nib = podBundle.loadNibNamed("PMAlertController", owner: self, options: nil) as [AnyObject]?{
  137. return nib
  138. }
  139. else{
  140. assertionFailure("Could not create a path to the bundle")
  141. }
  142. return nil
  143. }
  144. //MARK: - Animations
  145. @objc fileprivate func animateDismissWithGravity(_ style: PMAlertActionStyle){
  146. if gravityDismissAnimation == true{
  147. var radian = Double.pi
  148. if style == .default {
  149. radian = 2 * Double.pi
  150. }else{
  151. radian = -2 * Double.pi
  152. }
  153. animator = UIDynamicAnimator(referenceView: self.view)
  154. let gravityBehavior = UIGravityBehavior(items: [alertView])
  155. gravityBehavior.gravityDirection = CGVector(dx: 0, dy: 10)
  156. animator?.addBehavior(gravityBehavior)
  157. let itemBehavior = UIDynamicItemBehavior(items: [alertView])
  158. itemBehavior.addAngularVelocity(CGFloat(radian), for: alertView)
  159. animator?.addBehavior(itemBehavior)
  160. }
  161. }
  162. //MARK: - Keyboard avoiding
  163. var tempFrameOrigin: CGPoint?
  164. var keyboardHasBeenShown:Bool = false
  165. @objc func keyboardWillShow(_ notification: Notification) {
  166. keyboardHasBeenShown = true
  167. guard let userInfo = (notification as NSNotification).userInfo else {return}
  168. guard let endKeyBoardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.minY else {return}
  169. if tempFrameOrigin == nil {
  170. tempFrameOrigin = alertView.frame.origin
  171. }
  172. var newContentViewFrameY = alertView.frame.maxY - endKeyBoardFrame
  173. if newContentViewFrameY < 0 {
  174. newContentViewFrameY = 0
  175. }
  176. alertView.frame.origin.y -= newContentViewFrameY
  177. }
  178. @objc func keyboardWillHide(_ notification: Notification) {
  179. if (keyboardHasBeenShown) { // Only on the simulator (keyboard will be hidden)
  180. if (tempFrameOrigin != nil){
  181. alertView.frame.origin.y = tempFrameOrigin!.y
  182. tempFrameOrigin = nil
  183. }
  184. keyboardHasBeenShown = false
  185. }
  186. }
  187. }
  188. extension PMAlertController: UITextFieldDelegate {
  189. public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
  190. textField.resignFirstResponder()
  191. return true
  192. }
  193. }