EXC_CRASH (SIGABRT) in App Crash Logs

We are seeing this crash in our app crash logs.


The app presents a banner (similar to iOS notification) in its own window. It's a stackview with the banner views added & removed after a certain time interval. The stacktrace seems to indicate a crash when the banner view is hidden and then removed from the stackview.


We are unable to reproduce this issue in our dev or internal environments. But this does appear in the app store crash logs.


Here is the stack trace


Date/Time: 2019-09-04 12:54:21.1346 +0100

Launch Time: 2019-09-04 05:54:54.8879 +0100

OS Version: iPhone OS 12.4 (16G77)

Baseband Version: 3.03.04

Report Version: 104


Exception Type: EXC_CRASH (SIGABRT)

Exception Codes: 0x0000000000000000, 0x0000000000000000

Exception Note: EXC_CORPSE_NOTIFY

Triggered by Thread: 0


Last Exception Backtrace:

0 CoreFoundation 0x23a1fa98c __exceptionPreprocess + 228 (NSException.m:172)

1 libobjc.A.dylib 0x2393d39f8 objc_exception_throw + 56 (objc-exception.mm:557)

2 CoreFoundation 0x23a10c4a4 -[__NSSingleObjectArrayI objectAtIndex:] + 128 (NSSingleObjectArray.m:16)

3 UIKitCore 0x2670156a0 -[_UIOrderedLayoutArrangement _cleanUpEdgeToEdgeConstraintsForHiddenItemsForItem:atIndex:processA... + 340 (_UIOrderedLayoutArrangement.m:606)

4 UIKitCore 0x267019958 -[_UIOrderedLayoutArrangement _updateArrangementConstraints] + 556 (_UIOrderedLayoutArrangement.m:1403)

5 UIKitCore 0x26703266c -[UIView(AdditionalLayoutSupport) _handleLayoutArrangementConstraintsIfNecessary] + 172 (NSLayoutConstraint_UIKitAdditions.m:4552)

6 UIKitCore 0x26703278c -[UIView(AdditionalLayoutSupport) _updateSystemConstraints] + 88 (NSLayoutConstraint_UIKitAdditions.m:4576)

7 UIKitCore 0x26701b630 __32-[UIStackView updateConstraints]_block_invoke + 52 (UIStackView.m:303)

8 UIKitCore 0x267030be0 -[UIView(AdditionalLayoutSupport) _withUnsatisfiableConstraintsLoggingSuspendedIfEngineDelegateEx... + 120 (NSLayoutConstraint_UIKitAdditions.m:4172)

9 UIKitCore 0x26701b5f0 -[UIStackView updateConstraints] + 76 (UIStackView.m:302)

10 UIKitCore 0x267031510 -[UIView(AdditionalLayoutSupport) _sendUpdateConstraintsIfNecessaryForSecondPass:] + 176 (NSLayoutConstraint_UIKitAdditions.m:0)

11 UIKitCore 0x2670319b8 -[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededCollectingViews:forSecondPass:] + 984 (NSLayoutConstraint_UIKitAdditions.m:4381)

12 UIKitCore 0x267031894 -[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededCollectingViews:forSecondPass:] + 692 (NSLayoutConstraint_UIKitAdditions.m:4362)

13 Foundation 0x23ab759f4 -[NSISEngine withBehaviors:performModifications:] + 108 (NSISEngine.m:3123)

14 UIKitCore 0x2670320d0 __100-[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededWithViewForVariableChangeNotific... + 92 (NSLayoutConstraint_UIKitAdditions.m:4427)

15 UIKitCore 0x267030be0 -[UIView(AdditionalLayoutSupport) _withUnsatisfiableConstraintsLoggingSuspendedIfEngineDelegateEx... + 120 (NSLayoutConstraint_UIKitAdditions.m:4172)

16 UIKitCore 0x267031cf0 -[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededWithViewForVariableChangeNotifications:] + 168 (NSLayoutConstraint_UIKitAdditions.m:4426)

17 UIKitCore 0x267022778 -[UIWindow(UIConstraintBasedLayout) _updateConstraintsIfNeededWithViewForVariableChangeNotificati... + 96 (NSLayoutConstraint_UIKitAdditions.m:574)

18 UIKitCore 0x267032b74 -[UIView(AdditionalLayoutSupport) _updateConstraintsAtEngineLevelIfNeededWithViewForVariableChang... + 416 (NSLayoutConstraint_UIKitAdditions.m:4682)

19 UIKitCore 0x2670d5300 -[UIView(Hierarchy) layoutBelowIfNeeded] + 892 (UIView.m:10841)

20 UIKitCore 0x2670db8f0 +[UIView(Animation) performWithoutAnimation:] + 104 (UIView.m:12458)

21 UIKitCore 0x2670b2b18 __59-[UIViewAnimationState _runConstraintBasedLayoutAnimations]_block_invoke + 124 (UIView.m:1751)

22 UIKitCore 0x267030be0 -[UIView(AdditionalLayoutSupport) _withUnsatisfiableConstraintsLoggingSuspendedIfEngineDelegateEx... + 120 (NSLayoutConstraint_UIKitAdditions.m:4172)

23 UIKitCore 0x2670b29e8 -[UIViewAnimationState _runConstraintBasedLayoutAnimations] + 820 (UIView.m:1748)

24 UIKitCore 0x2670b2628 -[UIViewAnimationState pop] + 2036 (UIView.m:1714)

25 UIKitCore 0x2670af56c +[UIViewAnimationState popAnimationState] + 64 (UIView.m:1005)

26 UIKitCore 0x2670dd2e4 +[UIView(UIViewAnimationWithBlocks) _setupAnimationWithDuration:delay:view:options:factory:animat... + 744 (UIView.m:12946)

27 UIKitCore 0x2670dd81c +[UIView(UIViewAnimationWithBlocks) animateWithDuration:animations:completion:] + 100 (UIView.m:12995)

28 HCM Cloud 0x102171658 BannerView.cleanup(_:operation:) + 452 (BannerView.swift:59)

29 HCM Cloud 0x1021728c0 partial apply for closure #1 in closure #1 in BannerView.queueCleanup(_:) + 56 (BannerView.swift:125)

30 HCM Cloud 0x1020ecbec thunk for @escaping @callee_guaranteed () -> () + 28 (<compiler-generated>:0)

31 libdispatch.dylib 0x239c38a38 _dispatch_call_block_and_release + 24 (init.c:1372)

32 libdispatch.dylib 0x239c397d4 _dispatch_client_callout + 16 (object.m:511)

33 libdispatch.dylib 0x239be7008 _dispatch_main_queue_callback_4CF$VARIANT$mp + 1068 (inline_internal.h:2441)

34 CoreFoundation 0x23a18c32c __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12 (CFRunLoop.c:1813)

35 CoreFoundation 0x23a187264 __CFRunLoopRun + 1924 (CFRunLoop.c:3113)

36 CoreFoundation 0x23a1867c0 CFRunLoopRunSpecific + 436 (CFRunLoop.c:3247)

37 GraphicsServices 0x23c38779c GSEventRunModal + 104 (GSEvent.c:2245)

38 UIKitCore 0x266c51c38 UIApplicationMain + 212 (UIApplication.m:4353)

39 HCM Cloud 0x1020bdf50 main + 60 (ResizableButton.swift:20)

40 libdyld.dylib 0x239c4a8e0 start + 4


Thread 0 name:

Thread 0 Crashed:

0 libsystem_kernel.dylib 0x0000000239d970dc __pthread_kill + 8

1 libsystem_pthread.dylib 0x0000000239e10094 pthread_kill$VARIANT$mp + 380 (pthread.c:1492)

2 libsystem_c.dylib 0x0000000239cefea8 abort + 140 (abort.c:94)

3 libc++abi.dylib 0x00000002393bc788 abort_message + 132 (abort_message.cpp:75)

4 libc++abi.dylib 0x00000002393bc934 default_terminate_handler() + 308 (cxa_default_handlers.cpp:68)

5 libobjc.A.dylib 0x00000002393d3e00 _objc_terminate() + 124 (objc-exception.mm:693)

6 libc++abi.dylib 0x00000002393c8838 std::__terminate(void (*)()) + 16 (cxa_handlers.cpp:66)

7 libc++abi.dylib 0x00000002393c88c4 std::terminate() + 84 (cxa_handlers.cpp:97)

8 libdispatch.dylib 0x0000000239c397e8 _dispatch_client_callout + 36 (object.m:514)

9 libdispatch.dylib 0x0000000239be7008 _dispatch_main_queue_callback_4CF$VARIANT$mp + 1068 (inline_internal.h:2441)

10 CoreFoundation 0x000000023a18c32c __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12 (CFRunLoop.c:1813)

11 CoreFoundation 0x000000023a187264 __CFRunLoopRun + 1924 (CFRunLoop.c:3113)

12 CoreFoundation 0x000000023a1867c0 CFRunLoopRunSpecific + 436 (CFRunLoop.c:3247)

13 GraphicsServices 0x000000023c38779c GSEventRunModal + 104 (GSEvent.c:2245)

14 UIKitCore 0x0000000266c51c38 UIApplicationMain + 212 (UIApplication.m:4353)

15 HCM Cloud 0x00000001020bdf50 main + 60 (ResizableButton.swift:20)

16 libdyld.dylib 0x0000000239c4a8e0 start + 4


Thread 1:

0 libsystem_pthread.dylib 0x0000000239e1ccd0 start_wqthread + 0


Thread 2:

0 libsystem_pthread.dylib 0x0000000239e1ccd0 start_wqthread + 0


Thread 3 name:

Thread 3:

0 libsystem_kernel.dylib 0x0000000239d8c0f4 mach_msg_trap + 8

1 libsystem_kernel.dylib 0x0000000239d8b5a0 mach_msg + 72 (mach_msg.c:103)

2 CoreFoundation 0x000000023a18c120 __CFRunLoopServiceMachPort + 236 (CFRunLoop.c:2615)

3 CoreFoundation 0x000000023a187030 __CFRunLoopRun + 1360 (CFRunLoop.c:2971)

4 CoreFoundation 0x000000023a1867c0 CFRunLoopRunSpecific + 436 (CFRunLoop.c:3247)

5 Foundation 0x000000023ab54eac -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 300 (NSRunLoop.m:367)

6 Foundation 0x000000023ab54d3c -[NSRunLoop(NSRunLoop) runUntilDate:] + 96 (NSRunLoop.m:411)

7 UIKitCore 0x0000000266d37754 -[UIEventFetcher threadMain] + 136 (UIEventFetcher.m:606)

8 Foundation 0x000000023ac81674 __NSThread__start__ + 984 (NSThread.m:1175)

9 libsystem_pthread.dylib 0x0000000239e192c0 _pthread_body + 128 (pthread.c:857)

10 libsystem_pthread.dylib 0x0000000239e19220 _pthread_start + 44 (pthread.c:884)

11 libsystem_pthread.dylib 0x0000000239e1ccdc thread_start + 4


Thread 4:

0 libsystem_pthread.dylib 0x0000000239e1ccd0 start_wqthread + 0


Thread 5:

0 libsystem_pthread.dylib 0x0000000239e1ccd0 start_wqthread + 0


Thread 6 name:

Thread 6:

0 libsystem_kernel.dylib 0x0000000239d96ee4 __psynch_cvwait + 8

1 libsystem_pthread.dylib 0x0000000239e11cf8 _pthread_cond_wait$VARIANT$mp + 636 (pthread_cond.c:578)

2 libc++.1.dylib 0x000000023936d090 std::__1::condition_variable::wait(std::__1::unique_lock<std::__1::mutex>&) + 24 (__threading_support:278)

3 JavaScriptCore 0x000000024141ade0 void std::__1::condition_variable_any::wait<std::__1::unique_lock<bmalloc::Mutex> >(std::__1::uni... + 108 (condition_variable:204)

4 JavaScriptCore 0x000000024141edd4 bmalloc::Scavenger::threadRunLoop() + 176 (condition_variable:213)

5 JavaScriptCore 0x000000024141e54c bmalloc::Scavenger::threadEntryPoint(bmalloc::Scavenger*) + 12 (Scavenger.cpp:359)

6 JavaScriptCore 0x000000024141ff8c void* std::__1::__thread_proxy<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, st... + 40 (type_traits:4345)

7 libsystem_pthread.dylib 0x0000000239e192c0 _pthread_body + 128 (pthread.c:857)

8 libsystem_pthread.dylib 0x0000000239e19220 _pthread_start + 44 (pthread.c:884)

9 libsystem_pthread.dylib 0x0000000239e1ccdc thread_start + 4


Thread 7 name:

Thread 7:

0 libsystem_kernel.dylib 0x0000000239d8c0f4 mach_msg_trap + 8

1 libsystem_kernel.dylib 0x0000000239d8b5a0 mach_msg + 72 (mach_msg.c:103)

2 CoreFoundation 0x000000023a18c120 __CFRunLoopServiceMachPort + 236 (CFRunLoop.c:2615)

3 CoreFoundation 0x000000023a187030 __CFRunLoopRun + 1360 (CFRunLoop.c:2971)

4 CoreFoundation 0x000000023a1867c0 CFRunLoopRunSpecific + 436 (CFRunLoop.c:3247)

5 AVFAudio 0x00000002400f8334 GenericRunLoopThread::Entry(void*) + 156 (GenericRunLoopThread.h:92)

6 AVFAudio 0x0000000240122c60 CAPThread::Entry(CAPThread*) + 88

7 libsystem_pthread.dylib 0x0000000239e192c0 _pthread_body + 128 (pthread.c:857)

8 libsystem_pthread.dylib 0x0000000239e19220 _pthread_start + 44 (pthread.c:884)

9 libsystem_pthread.dylib 0x0000000239e1ccdc thread_start + 4


Thread 0 crashed with ARM Thread State (64-bit):

x0: 0x0000000000000000 x1: 0x0000000000000000 x2: 0x0000000000000000 x3: 0x0000000281c758b7

x4: 0x00000002393cbb71 x5: 0x000000016dd466d0 x6: 0x000000000000006e x7: 0xffffffffffffffec

x8: 0x0000000000000800 x9: 0x0000000239e14888 x10: 0x0000000239e0ff18 x11: 0x0000000000000003

x12: 0x0000000000000000 x13: 0x0000000000000001 x14: 0x0000000000000010 x15: 0x000000000000000c

x16: 0x0000000000000148 x17: 0x0000000000000000 x18: 0x0000000000000000 x19: 0x0000000000000006

x20: 0x0000000102aeebc0 x21: 0x000000016dd466d0 x22: 0x0000000000000303 x23: 0x0000000102aeeca0

x24: 0x000000027445a9c0 x25: 0x0000000102aeeca0 x26: 0x0000000000000114 x27: 0x0000000000000000

x28: 0x00000002827c0980 fp: 0x000000016dd46630 lr: 0x0000000239e10094

sp: 0x000000016dd46600 pc: 0x0000000239d970dc cpsr: 0x00000000



Here is the applicable code

BannerView.swift

class BannerView: UIView, CAAnimationDelegate {

    @IBOutlet weak var backgroundView: UIView!
    @IBOutlet weak var visualEffectView: UIVisualEffectView!
    @IBOutlet weak var details: UILabel!
    @IBOutlet weak var dismissButton: UIButton!
    @IBOutlet weak var heading: UILabel!

    private let sl = CAShapeLayer()

    var waitTimeBeforeDismissal: Double = defaultWaitTimeBeforeDismissal

    var persist = false
    var postDismissAction:((UIUpdate) -> Void) = { _ in }
    var showHideQueue: OperationQueue?

    override func awakeFromNib() {
        super.awakeFromNib()

        dismissButton.setTitle(NSLocalizedString("ok", comment: ""), for: .normal)
        var t = CATransform3DIdentity
        //Add the perspective!!!
        t.m34 = 1.0 / -400.0
        t = CATransform3DRotate(t, CGFloat.pi/2, 1, 0, 0)
        layer.transform = t

        sl.masksToBounds = true
        backgroundView.layer.mask = sl

        details.text = ""
        translatesAutoresizingMaskIntoConstraints = false

        heading.font = UIFontMetrics.default.scaledFont(for: UIFont.boldSystemFont(ofSize: 16))
        heading.adjustsFontForContentSizeCategory = true
        details.font = UIFontMetrics.default.scaledFont(for: UIFont.systemFont(ofSize: 17, weight: .medium))
        details.adjustsFontForContentSizeCategory = true
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        sl.frame = layer.bounds
        sl.path = UIBezierPath(roundedRect: bounds, cornerRadius: 12.0).cgPath
    }

    fileprivate func cleanup(_ sv: UIStackView, operation op: UIUpdate) {
        layer.removeAllAnimations()
        alpha = 0.0
        UIView.animate(withDuration: 0.25, animations: { [weak self] in
            guard let s = self else { return }
            s.isHidden = true
            }, completion: { [weak self] (_) in
                guard let s = self else { return }
                sv.removeArrangedSubview(s)
                s.removeFromSuperview()
                DispatchQueue.main.async {
                    s.postDismissAction(op)
                }
        })
    }

    @IBAction func dismissNotification(_ sender: Any) {
        guard let sv = superview as? UIStackView else { return }
        queueCleanup(sv)
    }

    func present() {
        let ba = CABasicAnimation(keyPath: "transform.rotation.x")
        ba.fromValue = CGFloat.pi/2
        ba.toValue = 0.0
        ba.duration = 0.3
        ba.autoreverses = false
        ba.isRemovedOnCompletion = false

        layer.add(ba, forKey: "Rotate")

        var t = CATransform3DIdentity
        t.m34 = 1.0 / -400.0
        layer.transform = t

        DispatchQueue.main.asyncAfter(deadline: .now() + waitTimeBeforeDismissal) { [weak self] in
            guard let s = self, !s.persist else { return }
            s.dismiss()
        }
    }

    func dismiss() {
        guard superview != nil else { return }
        let ba = CABasicAnimation(keyPath: "transform.rotation.x")
        ba.delegate = self
        ba.fromValue = 0.0
        ba.toValue = CGFloat.pi/2
        ba.duration = 0.3
        ba.autoreverses = false
        ba.isRemovedOnCompletion = false

        layer.add(ba, forKey: "Reverse Rotate")

        var t = CATransform3DIdentity
        //Add the perspective!!!
        t.m34 = 1.0 / -400.0
        t = CATransform3DRotate(t, CGFloat.pi/2, 1, 0, 0)
        layer.transform = t
    }

    func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        guard let sv = superview as? UIStackView else { return }
        queueCleanup(sv)
    }

    private func queueCleanup(_ sv: UIStackView) {
        let uiUpdate = UIUpdate()
        uiUpdate.executionBlock = {
            DispatchQueue.main.async { [weak self] in
                self?.cleanup(sv, operation: uiUpdate)
            }
        }
        showHideQueue?.addOperation(uiUpdate)
    }
}


BannerPresenter.swift

class BannerPresenter: NSObject, CAAnimationDelegate {

    fileprivate lazy var bannerWindow: BannerWindow = {
        let mw = BannerWindow(frame: UIScreen.main.bounds)
        mw.rootViewController = BannerWindowRootViewController()
        mw.rootViewController?.view.isUserInteractionEnabled = false
        mw.isUserInteractionEnabled = true
        mw.windowLevel = UIWindowLevelAlert + 1
        if !mw.subviews.contains(bannerStack) {
            mw.addSubview(bannerStack)
            let topAnchor = mw.layoutMargins.top > mw.safeAreaInsets.top ? mw.layoutMarginsGuide.topAnchor : mw.safeAreaLayoutGuide.topAnchor
            let additionalMargin: CGFloat = topAnchor == mw.layoutMarginsGuide.topAnchor ? 16 : 0
            bannerStack.topAnchor.constraint(equalTo: topAnchor, constant: additionalMargin).isActive = true
            bannerStack.leadingAnchor.constraint(equalTo: mw.readableContentGuide.leadingAnchor, constant: 0).isActive = true
            mw.readableContentGuide.trailingAnchor.constraint(equalTo: bannerStack.trailingAnchor, constant: 0).isActive = true
        }
        return mw
    }()

    fileprivate lazy var bannerStack: UIStackView = {
        let sv = UIStackView(arrangedSubviews: [])
        sv.translatesAutoresizingMaskIntoConstraints = false
        sv.layoutMargins = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
        sv.axis = .vertical
        sv.alignment = .fill
        sv.distribution = .fill
        sv.spacing = 8
        return sv
    }()

    fileprivate var notificationFeedback: UIImpactFeedbackGenerator = UIImpactFeedbackGenerator(style: .medium)

    var showHideQueue = OperationQueue()
    override init() {
        super.init()
        showHideQueue.maxConcurrentOperationCount = 1
        DispatchQueue.main.async { [weak self] in
            self?.notificationFeedback.prepare()
        }
    }

    deinit {
        bannerStack.arrangedSubviews.forEach { (aView) in
            bannerStack.removeArrangedSubview(aView)
        }
        bannerStack.subviews.forEach { (sView) in
            sView.removeFromSuperview()
        }
        bannerStack.removeFromSuperview()
        showHideQueue.cancelAllOperations()
    }

    func presentBanner(title: String?, message: String?, persist: Bool=false, dismissAfter: Double=defaultWaitTimeBeforeDismissal) {
        HCMLog.logWithCategory(.green, message: "Present Banner with title >>> [\(title ?? "")] and message >>>> \(message ?? "")")
        let iTitle: String? = nil
        DispatchQueue.main.async { [weak self] in
            guard UIApplication.shared.applicationState != .background, let s = self, s.bannerStack.arrangedSubviews.filter({s.alreadyShowing($0, title: iTitle, message: message)}).count == 0 else { return }
            s.presentNewBanner(title: iTitle, message: message, persist: persist, dismissAfter: dismissAfter)
        }
    }

    fileprivate func alreadyShowing(_ bannerView: UIView, title: String?, message: String?) -> Bool {
        guard let bView = bannerView as? BannerView else { return false }
        return ((bView.heading.text ?? "") == (title ?? "")) && ((bView.details.text ?? "") == (message ?? ""))
    }

    fileprivate func postDismissAction(operation op: UIUpdate) {
        if bannerStack.arrangedSubviews.count == 0 {
            bannerWindow.isHidden = true
            //newWindow.isUserInteractionEnabled = false
        }
        op.state = .hasFinished
    }

    fileprivate func presentNewBanner(title: String?, message: String?, persist: Bool=false, dismissAfter: Double) {
        let bv = configureBannerWithMessage(title: title, message: message, persist: persist, dismissAfter: dismissAfter)
        let uiUpdate = UIUpdate()
        uiUpdate.executionBlock = {
            DispatchQueue.main.async { [weak self] in
                self?.showBanner(bannerView: bv, operation: uiUpdate)
            }
        }
        showHideQueue.addOperation(uiUpdate)
    }

    fileprivate func showBanner(bannerView: BannerView?, operation op: UIUpdate) {
        guard let bv = bannerView else { return }
        bv.isHidden = true
        if bannerStack.arrangedSubviews.count == 0 {
            bannerStack.addArrangedSubview(bv)
        } else {
            bannerStack.insertArrangedSubview(bv, at: 0)
        }
        bannerWindow.isHidden = false
        DispatchQueue.main.async { [weak self] in
            bv.isHidden = false
            bv.present()
            self?.notificationFeedback.impactOccurred()
            op.state = .hasFinished
            guard UIAccessibilityIsVoiceOverRunning(), let bvdText = bv.details.text else { return }
            let bannerAnnouncementText = NSAttributedString(string: bvdText, attributes: [NSAttributedStringKey(rawValue: UIAccessibilitySpeechAttributeQueueAnnouncement): NSNumber(value: true)])
            UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, bannerAnnouncementText)
        }
    }

    fileprivate func configureBannerWithMessage(title: String?, message: String?, persist: Bool=false, dismissAfter: Double) -> BannerView? {
        let bv = UINib(nibName: "BannerView", bundle: Bundle.main).instantiate(withOwner: nil, options: nil).first as? BannerView
        bv?.showHideQueue = showHideQueue
        bv?.persist = persist
        bv?.postDismissAction = postDismissAction
        bv?.heading.text = title
        bv?.details.text = message
        bv?.waitTimeBeforeDismissal = dismissAfter
        bv?.invalidateIntrinsicContentSize()
        return bv
    }

}

class BannerWindow: UIWindow {
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        var allow = false
        subviews.forEach { (view) in
            guard view is UIStackView else { return }
            allow = view.isUserInteractionEnabled && view.point(inside: convert(point, to: view), with: event)
        }
        return allow
    }
}

class BannerWindowRootViewController: UIViewController {
    override var preferredStatusBarStyle: UIStatusBarStyle {
        return .lightContent
    }

    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        let win = UIApplication.shared.delegate?.window
        return win??.rootViewController?.supportedInterfaceOrientations ?? .portrait
    }

    override var shouldAutorotate: Bool {
        var fromVC = UIApplication.shared.delegate?.window??.rootViewController
        while fromVC?.presentedViewController != nil {
            fromVC = fromVC?.presentedViewController
        }
        return fromVC?.shouldAutorotate ?? false
    }
}