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.

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