How to update window frames on an iPad split view?

I am trying to add a banner window which should show on top of the main window at all times. nothing from the main window should ever block the content on that banner window and the banner window should also not block the content of the main window. The following code worked fine on an iPhone but on an iPad with split view enabled I am facing some issues.

This is how it looks normally
enter image description here

I manually set the frames of both the windows and this is causing an issue when the width of the scene changes as we modify the width available for the scene in the split view.

How can I watch for changes in the available width for the app’s scene in split view?


I tried using viewWillLayoutSubviews, traitCollectionDidChange and also UIContentContainer protocol methods as shown in the code below, none of those methods are called when I change the width available for the app on split view. They only get called when the orientation changes.

Complete code for the project is available here

import UIKit

class ViewController: UIViewController {
    let label: UILabel = {
        let label = UILabel()
        label.numberOfLines = 0
        label.font = UIFont.preferredFont(forTextStyle: .largeTitle)
        label.textAlignment = .center
        label.textColor = .white
        return label

    override func viewDidLoad() {
        // Do any additional setup after loading the view.
        view.backgroundColor = .systemGreen
        label.text = "This is the key window"
        label.translatesAutoresizingMaskIntoConstraints = false
            label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            label.topAnchor.constraint(equalTo: view.topAnchor),
            label.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            label.trailingAnchor.constraint(equalTo: view.trailingAnchor)
    override func viewWillLayoutSubviews() {
        print("laying out subviews")

    override func viewWillAppear(_ animated: Bool) {
        // Just added a small delay to see the addition of the banner window clearly
        DispatchQueue.main.asyncAfter(deadline: + 2.0) {
            guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }

            // height of the Banner window
            let height: CGFloat = 100
            guard let window = self.view.window,
                  let windowScene = window.windowScene,
                  let keyWindow = { $0.isKeyWindow })
            else { return }
            // accessing the key window and changing its frame so that the window's content is not
            // blocked by the banner window that is going to be above it
            let size = keyWindow.frame.size
            keyWindow.frame = CGRect(origin: CGPoint(x: 0, y: height), size: CGSize(width: size.width, height: size.height - height))
            // Creating a banner window
            let bannerWindow = UIWindow(windowScene: windowScene)
            bannerWindow.frame = CGRect(origin: .zero, size: CGSize(width: size.width, height: height))
            bannerWindow.isHidden = false
            // Creating a banner view and adding it to the banner window
            let bannerView = UIView(frame: .zero)
            bannerView.backgroundColor = .systemPink
            bannerView.translatesAutoresizingMaskIntoConstraints = false
            bannerWindow.windowLevel = keyWindow.windowLevel + 1
                bannerView.leadingAnchor.constraint(equalTo: bannerWindow.leadingAnchor),
                bannerView.trailingAnchor.constraint(equalTo: bannerWindow.trailingAnchor),
                bannerView.heightAnchor.constraint(equalToConstant: height),
                bannerView.bottomAnchor.constraint(equalTo: bannerWindow.bottomAnchor),
            // Keeping a reference to the banner window to prevent it from being deallocated
            appDelegate.bannerWindow = bannerWindow
    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        print("Trait collection changed")
    override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
        super.willTransition(to: newCollection, with: coordinator)
        print("will transition to new collection")
    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        print("will transtion to size: (size)")

When split screen is enabled it is still using the frame that was set previously for the full screen which makes sense.

enter image description here

enter image description here

Source: Ios Questions