NeilsUltimateLab

Multiple Windows

Multiple windows within the same UIscreen.

We can create new window like this.

let frame = UIScreen.main.bounds
let newWindow = UIWindow(frame: CGRect(x: frame.origin.x, y: frame.midY, width: frame.width, height: frame.height/2))
newWindow.isHidden = false

or if we are on iOS 13 and above and have a UIWindowScene,

guard let refWindow = self.view.window, let scene = refWindow.scene else { return }
let newWindow = UIWindow(windowScene: scene)
newWindow.isHidden = false

make sure we hold window object strongly at application level.

class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    
    var additionalWindows: [UIWindow] = []
    ...
}

so the above window creation code will look like this

let newWindow = UIWindow(frame: CGRect(x: frame.origin.x, y: frame.midY, width: frame.width, height: frame.height/2))
newWindow.isHidden = false

let appDelegate = UIApplication.shared.delegate as? AppDelegate
appDelegate?.additionalWindows.append(newWindow)

Thats it. We have new window in the screen and we can have as many as we want. 🎉

To show content in the new window, use rootViewController property of UIWindow.

let newRootViewController = UIViewController()
newRootViewController.view.backgroundColor = UIColor.yellow

newWindow.rootViewController = newRootViewController

Animation 🤩

Let's add some animation for new window. Since UIWindow is just a UIView subclass we can use UIView.animate methods or UIViewPropertyAnimator for this.

UIView.animate example

newWindow.alpha = 0
newWindow.frame = CGRect(x: UIScreen.current.frame.midX, y: UIScreen.current.frame.midY, width: 0, height: 0)

UIView.animate(duration: 0.5, options: .curveEaseIn, animation: {
    newWindow.frame = CGRect(x: 100, y: 100, width: 200, height: 200)
    newWindow.alpha = 1
} { _ in
    print("Animation completed")
}

UIViewPropertyAnimator example

let timingParameters = UISpringTimingParameters(dampingRatio: 0.9)
let animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: timingParameters)

animation.addAnimations {
    newWindow.frame = CGRect(x: 100, y: 100, width: 200, height: 200)
    newWindow.alpha = 1
}

animation.addAnimations ({
    newWindow.rootViewController.view.alpha = 1
}, delayFactor: 0.4)

animation.startAnimation()

Remove window 🧹

To remove window, we simply remove it from the additionalWindows from AppDelegate.

func close(window: UIWindow, animated: Bool = true) {
    guard
        let appDelegate = UIApplication.shared.delegate as? AppDelegate,
        let index = appDelegate.additionalWindows.firstIndex(of: window)
    else { return }
    
    func remove() {
        window.isHidden = true
        appDelegate.additionalWindows.remove(at: index)
    }
    
    if animated {
        let timingParameters = UISpringTimingParameters(dampingRatio: 0.9)
        let animator = UIViewPropertyAnimator(duration: 0.7, timingParameters: timingParameters)
        
        animator.addAnimations {
            window.alpha = 0
            window.frame = CGRect(x: window.frame.midX, y: window.frame.midY, width: 0, height: 0)
        }
        
        animator.addCompletion { _ in
            remove()
        }
        
        animator.startAnimation()
    } else {
        remove()
    }
}

This will release the rootViewController for the closing window. So UIViewController life-cycle is maintained. 😎

Notes 📝

UIWindow is just a special UIView.

  • has rootViewController to show our app's content.
  • dispatches events to our views. sendEvent
  • handles the keyboard events.

We don't need to add window as a subview to another window. We just create new instance, provide some size and make it visible.

To get the keyboard events, we need to make our new window key. newWindow.makeKey(). Only one window can be the key at a time.

Output

Image

Check out the source code at github.

Use cases 👨🏻‍💻

  • Commonly we create extra windows for connected screens like TV or Projector. Good examples are Keynote (Presentation Apps) and Games. UIWindow reference document

Most apps need only one window, which displays the app’s content on the device’s main screen. Although we can create additional windows on the device’s main screen, extra windows are commonly used to display content on an external screen, as described in Displaying Content on a Connected Screen.

  • One interesting use case come from the iOS developer Gui Rambo did for EmojiPickerUI (macOS-style emoji picker for iPadOS). EmojiPickerUI

Thanks for reading. 🙂

Tagged with: