Daltron / NotificationBanner

The easiest way to display highly customizable in app notification banners in iOS
MIT License
4.78k stars 662 forks source link

CarPlay blocks the NotificationBanner #421

Open Anf139 opened 9 months ago

Anf139 commented 9 months ago

I have the iOS application with CarPlay support. I don't see the NotificationBanner during CarPlay on the iPhone's display or CarPlay's display.

Because of bannerPositionFrame is nil here:

func show(
        placeOnQueue: Bool,
        queuePosition: QueuePosition = .back,
        bannerPosition: BannerPosition = .top
    )

guard bannerPositionFrame != nil else {
    remove();
    return
}

I investigated a little bit and found the next call stack where appWindow is nil:

internal func updateBannerPositionFrames() {
    guard let window = appWindow else { return }
    bannerPositionFrame = BannerPositionFrame(
/// The main window of the application which banner views are placed on
private let appWindow: UIWindow? = {
    if #available(iOS 13.0, *) {
        return UIApplication.shared.connectedScenes
            .first { $0.activationState == .foregroundActive || $0.activationState == .foregroundInactive }
            .map { $0 as? UIWindowScene }
            .flatMap { $0?.windows.first } ?? UIApplication.shared.delegate?.window ?? UIApplication.shared.keyWindow
    }

    return UIApplication.shared.delegate?.window ?? nil
}()

During CarPlay usage the connectedScenes can contain

<CPTemplateApplicationScene: 0x10a556f50; role: CPTemplateApplicationSceneSessionRoleApplication; activationState: UISceneActivationStateForegroundActive>
<UIWindowScene: 0x10a434c40; role: UIWindowSceneSessionRoleApplication; activationState: UISceneActivationStateForegroundActive>

I propose to use something like this:

var appWindow: UIWindow? {
    let scenes: [UIScene] = Array(UIApplication.shared.connectedScenes)
    let windowScenes: [UIWindowScene] = scenes.compactMap { $0 as? UIWindowScene } // avoid CarPlay CPTemplateApplicationScene
    let foregroundScene: UIWindowScene? = windowScenes.first { $0.activationState == .foregroundActive || $0.activationState == .foregroundInactive }
    return foregroundScene?.windows.first
}

To avoid usage of type CPTemplateApplicationScene which cannot be converted to UIWindowScene.