Show SKScene on External Display

I'm trying to show a scene on an external display (iOS). I made a simple program using what I have found regarding this topic, but it doesn't work. I suspect that I'm just missing a setting or two.


I started with the simple 'Hello, World' example game that XCode makes for an iOS SpriteKit project. I modified the GameViewController class to the following:

class GameViewController: UIViewController {
  
    var primaryView: SKView?
    var secondaryView: SKView?
    override func viewDidLoad() {
        super.viewDidLoad()
      
        checkForExternalDisplay()
       
        if let view = primaryView {
            if let scene = SKScene(fileNamed: "GameScene") {
                scene.scaleMode = .aspectFill
                view.presentScene(scene)
            }
          
            view.ignoresSiblingOrder = true          
            view.showsFPS = true
            view.showsNodeCount = true
        }
    }


Here is the code for 'checkForExternalDisplay':


    func checkForExternalDisplay() {
        let screens = UIScreen.screens
        if screens.count > 1 {
            let mainScreenIndex = self.view.frame.width == screens[0].bounds.width ? 0 : 1
            let otherScreenIndex = self.view.frame.width == screens[0].bounds.width ? 1 : 0
            let primaryScreenIndex = screens[0].bounds.width > screens[1].bounds.width ? 0 : 1
            let otherWindow = UIWindow(frame: screens[otherScreenIndex].bounds)
            otherWindow.rootViewController = UIViewController()
            otherWindow.screen = screens[otherScreenIndex]
            let otherView = UIView(frame: screens[otherScreenIndex].bounds)
            otherWindow.addSubview(otherView)
            otherWindow.isHidden = false
            otherWindow.makeKeyAndVisible()
           
            if primaryScreenIndex == mainScreenIndex {
                primaryView = self.view as? SKView
            } else {
                primaryView = otherView as? SKView
            }
        } else {
            primaryView = self.view as? SKView
        }
        primaryView?.backgroundColor = SKColor.green
    }


When I run the program with just the iOS display (in the simulator), it works fine. However, when I add an external display in the simulator, and then launch the app, the scene doesn't show in either of the windows (I want it to show in the external window).


Any advice? Did I miss something, or do I have something set incorrectly?

Answered by PhasersOnStun in 260482022

I finally got the code to work. Posting here in case anyone else comes upon this and needs a similar solution.


I changed 2 items. 1) When comparing screen widths to the initial view, I used 'bounds' instead of 'frame'. 2) I turned off the 'Hello World' label fade in animation (the text stayed transparent for some reason).


    func checkForExternalDisplay() {
        let screens = UIScreen.screens
        if screens.count > 1 {
            let mainScreenIndex = self.view.bounds.width == screens[0].bounds.width ? 0 : 1
            let otherScreenIndex = self.view.bounds.width == screens[0].bounds.width ? 1 : 0
            let primaryScreenIndex = screens[0].bounds.width >= screens[1].bounds.width ? 0 : 1
            let otherWindow = UIWindow(frame: screens[otherScreenIndex].bounds)
            otherWindow.rootViewController = UIViewController()
            otherWindow.screen = screens[otherScreenIndex]
            let windowRectangle = CGRect(x: 0, y: 0, width: screens[otherScreenIndex].bounds.width, height: screens[otherScreenIndex].bounds.height)
            let otherView = SKView(frame: windowRectangle)
            otherWindow.addSubview(otherView)
            otherWindow.makeKeyAndVisible()
            otherWindow.isHidden = false
  
            if primaryScreenIndex == mainScreenIndex {
                primaryView = self.view as? SKView
            } else {
                primaryView = otherView
            }
        } else {
            primaryView = self.view as? SKView
        }
        primaryView?.backgroundColor = SKColor.green
    }


(code from GameScene.swift)

    override func didMove(to view: SKView) {

        // Get label node from scene and store it for use later
        self.label = self.childNode(withName: "//helloLabel") as? SKLabelNode
//        if let label = self.label {
//            label.alpha = 0.0
//            label.run(SKAction.fadeIn(withDuration: 2.0))
//        }

        // Create shape node to use during mouse interaction
        let w = (self.size.width + self.size.height) * 0.05
        self.spinnyNode = SKShapeNode.init(rectOf: CGSize.init(width: w, height: w), cornerRadius: w * 0.3)

        if let spinnyNode = self.spinnyNode {
            spinnyNode.lineWidth = 2.5
    
            spinnyNode.run(SKAction.repeatForever(SKAction.rotate(byAngle: CGFloat(Double.pi), duration: 1)))
            spinnyNode.run(SKAction.sequence([SKAction.wait(forDuration: 0.5),
                                              SKAction.fadeOut(withDuration: 0.5),
                                              SKAction.removeFromParent()]))
        }
    }

What version Xcode on which macOS are you working with...

Accepted Answer

I finally got the code to work. Posting here in case anyone else comes upon this and needs a similar solution.


I changed 2 items. 1) When comparing screen widths to the initial view, I used 'bounds' instead of 'frame'. 2) I turned off the 'Hello World' label fade in animation (the text stayed transparent for some reason).


    func checkForExternalDisplay() {
        let screens = UIScreen.screens
        if screens.count > 1 {
            let mainScreenIndex = self.view.bounds.width == screens[0].bounds.width ? 0 : 1
            let otherScreenIndex = self.view.bounds.width == screens[0].bounds.width ? 1 : 0
            let primaryScreenIndex = screens[0].bounds.width >= screens[1].bounds.width ? 0 : 1
            let otherWindow = UIWindow(frame: screens[otherScreenIndex].bounds)
            otherWindow.rootViewController = UIViewController()
            otherWindow.screen = screens[otherScreenIndex]
            let windowRectangle = CGRect(x: 0, y: 0, width: screens[otherScreenIndex].bounds.width, height: screens[otherScreenIndex].bounds.height)
            let otherView = SKView(frame: windowRectangle)
            otherWindow.addSubview(otherView)
            otherWindow.makeKeyAndVisible()
            otherWindow.isHidden = false
  
            if primaryScreenIndex == mainScreenIndex {
                primaryView = self.view as? SKView
            } else {
                primaryView = otherView
            }
        } else {
            primaryView = self.view as? SKView
        }
        primaryView?.backgroundColor = SKColor.green
    }


(code from GameScene.swift)

    override func didMove(to view: SKView) {

        // Get label node from scene and store it for use later
        self.label = self.childNode(withName: "//helloLabel") as? SKLabelNode
//        if let label = self.label {
//            label.alpha = 0.0
//            label.run(SKAction.fadeIn(withDuration: 2.0))
//        }

        // Create shape node to use during mouse interaction
        let w = (self.size.width + self.size.height) * 0.05
        self.spinnyNode = SKShapeNode.init(rectOf: CGSize.init(width: w, height: w), cornerRadius: w * 0.3)

        if let spinnyNode = self.spinnyNode {
            spinnyNode.lineWidth = 2.5
    
            spinnyNode.run(SKAction.repeatForever(SKAction.rotate(byAngle: CGFloat(Double.pi), duration: 1)))
            spinnyNode.run(SKAction.sequence([SKAction.wait(forDuration: 0.5),
                                              SKAction.fadeOut(withDuration: 0.5),
                                              SKAction.removeFromParent()]))
        }
    }
Show SKScene on External Display
 
 
Q