@@ -48,7 +48,7 @@ import UIKit
4848
4949 @objc public var message : String ?
5050
51- /// Determines wether `StatusAlert` can be picked or dismissed by tap
51+ /// Determines whether `StatusAlert` can be picked or dismissed by tap
5252 @objc public var canBePickedOrDismissed : Bool {
5353 get { return self . contentView. isUserInteractionEnabled }
5454 set { self . contentView. isUserInteractionEnabled = newValue }
@@ -60,6 +60,11 @@ import UIKit
6060 /// or to dismiss currently presented alerts if `multiplePresentationsBehavior` is `dismissCurrentlyPresented`
6161 private static var currentlyPresentedStatusAlerts : [ StatusAlert ] = [ ]
6262
63+ private static var alertToPresent : StatusAlert ? = nil
64+ private static var dismissing : Bool {
65+ return alertToPresent != nil
66+ }
67+
6368 private let defaultFadeAnimationDuration : TimeInterval = TimeInterval ( UINavigationControllerHideShowBarDuration)
6469 private let blurEffect : UIBlurEffect = UIBlurEffect ( style: . light)
6570
@@ -70,22 +75,16 @@ import UIKit
7075 private var titleLabel : UILabel ? = nil
7176 private var messageLabel : UILabel ? = nil
7277
78+ private var contentStackViewConstraints : [ NSLayoutConstraint ] = [ ]
7379 private var reusableObjectsConstraints : [ NSLayoutConstraint ] = [ ]
7480
7581 private var timer : Timer ?
7682
77- /// Determines whether `StatusAlert` can be shown
78- private var canBeShown : Bool {
79- if StatusAlert . multiplePresentationsBehavior == . ignoreIfAlreadyPresenting
80- && !StatusAlert. currentlyPresentedStatusAlerts. isEmpty {
81- return false
82- }
83- if self . image == nil ,
84- self . title == nil ,
85- self . message == nil {
86- return false
87- }
88- return true
83+ /// Determines whether `StatusAlert` has at least one item to show
84+ private var isContentEmpty : Bool {
85+ return self . image == nil
86+ && self . title == nil
87+ && self . message == nil
8988 }
9089
9190 /// Determines whether blur is available
@@ -122,16 +121,7 @@ import UIKit
122121 }
123122
124123 deinit {
125- NSLayoutConstraint . deactivate ( self . reusableObjectsConstraints)
126- if let imageView = self . imageView {
127- StatusAlert . reusableImageViewsManager. enqueueReusable ( imageView)
128- }
129- if let titleLabel = self . titleLabel {
130- StatusAlert . reusableLabelsManager. enqueueReusable ( titleLabel)
131- }
132- if let messageLabel = self . messageLabel {
133- StatusAlert . reusableLabelsManager. enqueueReusable ( messageLabel)
134- }
124+ self . enqueueReusableObjects ( )
135125 }
136126
137127 // MARK: - Static methods -
@@ -142,7 +132,7 @@ import UIKit
142132 /// - image: @1x should be 90*90 by default, optional
143133 /// - title: displayed beyond image
144134 /// - message: displayed beyond title or
145- /// - canBePickedOrDismissed: determines wether StatusAlert can be picked or dismissed by tap
135+ /// - canBePickedOrDismissed: determines whether StatusAlert can be picked or dismissed by tap
146136 /// - Returns: `StatusAlert` instance
147137 @available ( * , deprecated, message: " Use `init` instead and set all properties directly " )
148138 @objc ( statusAlertWithImage: title: message: canBePickedOrDismissed: )
@@ -280,16 +270,17 @@ import UIKit
280270 ) {
281271
282272 self . assertIsMainThread ( )
283- guard self . canBeShown else {
284- return
273+ guard !self . isContentEmpty else { return }
274+
275+ self . prepareForPresentation { [ weak self] in
276+ self ? . prepareContent ( )
277+ self ? . positionAlert (
278+ inPresenter: presenter,
279+ withVerticalPosition: verticalPosition,
280+ offset: offset
281+ )
282+ self ? . performPresentation ( )
285283 }
286- self . prepare ( )
287- self . position (
288- inPresenter: presenter,
289- withVerticalPosition: verticalPosition,
290- offset: offset
291- )
292- self . present ( )
293284 }
294285
295286 private func commonInit( ) {
@@ -387,12 +378,42 @@ import UIKit
387378 self . accessibilityElementsHidden = true
388379 self . accessibilityTraits = UIAccessibilityTraitNone
389380 }
381+
382+ private func resetView( ) {
383+ self . deactivateConstraints ( & self . contentStackViewConstraints)
384+
385+ self . enqueueReusableObjects ( )
386+ }
387+
388+ private func enqueueReusableObjects( ) {
389+ self . deactivateConstraints ( & self . reusableObjectsConstraints)
390+ if let imageView = self . imageView {
391+ imageView. removeFromSuperview ( )
392+ StatusAlert . reusableImageViewsManager. enqueueReusable ( imageView)
393+ self . imageView = nil
394+ }
395+ if let titleLabel = self . titleLabel {
396+ titleLabel. removeFromSuperview ( )
397+ StatusAlert . reusableLabelsManager. enqueueReusable ( titleLabel)
398+ self . titleLabel = nil
399+ }
400+ if let messageLabel = self . messageLabel {
401+ messageLabel. removeFromSuperview ( )
402+ StatusAlert . reusableLabelsManager. enqueueReusable ( messageLabel)
403+ self . messageLabel = nil
404+ }
405+ }
406+
407+ private func deactivateConstraints( _ array: inout [ NSLayoutConstraint ] ) {
408+ NSLayoutConstraint . deactivate ( array)
409+ array = [ ]
410+ }
390411
391412 // MARK: Creation methods
392413
393414 /// Must be called before the `StatusAlert` presenting
394- private func prepare ( ) {
395- self . completeStackViewConstraints ( )
415+ private func prepareContent ( ) {
416+ self . completeContentStackViewConstraints ( )
396417
397418 self . imageView = self . createImageViewIfNeeded ( )
398419 if let imageView = self . imageView {
@@ -434,7 +455,7 @@ import UIKit
434455 NSLayoutConstraint . activate ( self . reusableObjectsConstraints)
435456 }
436457
437- private func position (
458+ private func positionAlert (
438459 inPresenter presenter: UIView ,
439460 withVerticalPosition verticalPosition: VerticalPosition ,
440461 offset: CGFloat ?
@@ -477,37 +498,42 @@ import UIKit
477498 }
478499 }
479500
480- private func completeStackViewConstraints( ) {
501+ private func completeContentStackViewConstraints( ) {
502+ var constraints : [ NSLayoutConstraint ] = [ ]
481503 if self . image != nil
482504 && ( self . title != nil || self . message != nil ) {
483- self . contentView. heightAnchor. constraint (
505+
506+ constraints. append ( self . contentView. heightAnchor. constraint (
484507 greaterThanOrEqualToConstant: self . sizesAndDistances. minimumAlertHeight
485- ) . isActive = true
486- self . contentView. widthAnchor. constraint (
508+ ) )
509+ constraints . append ( self . contentView. widthAnchor. constraint (
487510 equalToConstant: self . sizesAndDistances. defaultAlertWidth
488- ) . isActive = true
489- self . contentStackView. topAnchor. constraint (
511+ ) )
512+ constraints . append ( self . contentStackView. topAnchor. constraint (
490513 greaterThanOrEqualTo: self . contentView. topAnchor,
491514 constant: self . sizesAndDistances. minimumStackViewTopSpace
492- ) . isActive = true
493- self . contentStackView. centerYAnchor. constraint (
515+ ) )
516+ constraints . append ( self . contentStackView. centerYAnchor. constraint (
494517 equalTo: self . contentView. centerYAnchor,
495518 constant: ( self . sizesAndDistances. minimumStackViewTopSpace - self . sizesAndDistances. minimumStackViewBottomSpace) / 2
496- ) . isActive = true
519+ ) )
497520 } else {
498521 if self . image == nil {
499- self . contentView. widthAnchor. constraint (
522+ constraints . append ( self . contentView. widthAnchor. constraint (
500523 equalToConstant: self . sizesAndDistances. defaultAlertWidth
501- ) . isActive = true
524+ ) )
502525 }
503- self . contentStackView. topAnchor. constraint (
526+ constraints . append ( self . contentStackView. topAnchor. constraint (
504527 greaterThanOrEqualTo: self . contentView. topAnchor,
505528 constant: self . sizesAndDistances. minimumStackViewBottomSpace
506- ) . isActive = true
507- self . contentStackView. centerYAnchor. constraint (
529+ ) )
530+ constraints . append ( self . contentStackView. centerYAnchor. constraint (
508531 equalTo: self . contentView. centerYAnchor
509- ) . isActive = true
532+ ) )
510533 }
534+
535+ self . contentStackViewConstraints. append ( contentsOf: constraints)
536+ NSLayoutConstraint . activate ( self . contentStackViewConstraints)
511537 }
512538
513539 @objc private func reduceTransparencyStatusDidChange( ) {
@@ -595,24 +621,35 @@ import UIKit
595621 }
596622
597623 // MARK: Presentation methods
598-
599- private func present( ) {
624+
625+ private func prepareForPresentation(
626+ onPrepared: @escaping ( ) -> Void
627+ ) {
628+
600629 switch StatusAlert . multiplePresentationsBehavior {
601630 case . ignoreIfAlreadyPresenting:
602631 guard StatusAlert . currentlyPresentedStatusAlerts. isEmpty else { return }
603- self . performPresentation ( )
632+ onPrepared ( )
604633 case . showMultiple:
605- self . performPresentation ( )
634+ guard !StatusAlert. currentlyPresentedStatusAlerts. contains ( self ) else { return }
635+ onPrepared ( )
606636 case . dismissCurrentlyPresented:
607- let group = DispatchGroup ( )
608- for alert in StatusAlert . currentlyPresentedStatusAlerts {
609- group. enter ( )
610- alert. dismiss {
611- group. leave ( )
637+ guard !StatusAlert. dismissing else { return }
638+ if !StatusAlert. currentlyPresentedStatusAlerts. isEmpty {
639+ StatusAlert . alertToPresent = self
640+ let group = DispatchGroup ( )
641+ for alert in StatusAlert . currentlyPresentedStatusAlerts {
642+ group. enter ( )
643+ alert. dismiss {
644+ group. leave ( )
645+ }
612646 }
613- }
614- group. notify ( queue: DispatchQueue . main) { [ weak self] in
615- self ? . performPresentation ( )
647+ group. notify ( queue: DispatchQueue . main) {
648+ onPrepared ( )
649+ StatusAlert . alertToPresent = nil
650+ }
651+ } else {
652+ onPrepared ( )
616653 }
617654 }
618655 }
@@ -621,24 +658,23 @@ import UIKit
621658 StatusAlert . currentlyPresentedStatusAlerts. append ( self )
622659
623660 let scale : CGFloat = self . sizesAndDistances. defaultInitialScale
624- self . timer = Timer . scheduledTimer (
661+ let timer = Timer . scheduledTimer (
625662 timeInterval: self . alertShowingDuration - self . defaultFadeAnimationDuration,
626663 target: self ,
627664 selector: #selector( self . dismissByTimer) ,
628665 userInfo: nil ,
629666 repeats: false )
630- if let timer = self . timer {
631- RunLoop . main. add (
632- timer,
633- forMode: RunLoopMode . commonModes
634- )
635- }
667+ RunLoop . main. add (
668+ timer,
669+ forMode: RunLoopMode . commonModes
670+ )
671+ self . timer = timer
636672 self . contentView. transform = CGAffineTransform . identity. scaledBy ( x: scale, y: scale)
637673
638674 UIView . animate (
639675 withDuration: self . defaultFadeAnimationDuration,
640676 delay: 0 ,
641- options: UIViewAnimationOptions . curveEaseOut,
677+ options: . curveEaseOut,
642678 animations: {
643679 if self . isBlurAvailable {
644680 if #available( iOS 11 , * ) {
@@ -690,8 +726,9 @@ import UIKit
690726 if let strongSelf = self ,
691727 let index = StatusAlert . currentlyPresentedStatusAlerts. index ( of: strongSelf) {
692728 StatusAlert . currentlyPresentedStatusAlerts. remove ( at: index)
693- self ? . removeFromSuperview ( )
694729 }
730+ self ? . removeFromSuperview ( )
731+ self ? . resetView ( )
695732 completion ? ( )
696733 } )
697734 }
0 commit comments