SKNode.zPosition causes nodes to flicker by reordering them for 1 frame

When running the sample code below, every 3 seconds the middle sprite is replaced by a new one. When this happens, most of the time a flicker is noticeable. When recording the screen and stepping through the recording frame by frame, I noticed that the flicker is caused by a temporary reordering of the nodes’. Below you find two screenshots of two consecutive frames where the reordering is clearly visible.

This only happens for a SpriteKit scene used as an overlay for a SceneKit scene. Commenting out

buttons.zPosition = 1

or avoiding the fade in/out animations solves the issue.

I have created FB15945016.

import SceneKit
import SpriteKit

class GameViewController: NSViewController {
    
    let overlay = SKScene()
    var buttons: SKNode!
    var previousButton: SKSpriteNode!
    var nextButton: SKSpriteNode!
    var pageContainer: SKNode!
    var pageViews = [SKNode]()
    var page = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let scene = SCNScene(named: "art.scnassets/ship.scn")!
        let scnView = self.view as! SCNView
        scnView.scene = scene
        
        overlay.anchorPoint = CGPoint(x: 0.5, y: 0.5)
        scnView.overlaySKScene = overlay
        
        buttons = SKNode()
        buttons.zPosition = 1
        overlay.addChild(buttons)
        
        previousButton = SKSpriteNode(systemImage: "arrow.uturn.backward.circle")
        previousButton.position = CGPoint(x: -100, y: 0)
        buttons.addChild(previousButton)
        
        nextButton = SKSpriteNode(systemImage: "arrow.uturn.forward.circle")
        nextButton.position = CGPoint(x: 100, y: 0)
        buttons.addChild(nextButton)
        
        pageContainer = SKNode()
        pageViews = [SKSpriteNode(systemImage: "square.and.arrow.up"), SKSpriteNode(systemImage: "eraser")]
        overlay.addChild(pageContainer)
        
        setPage(0)

        Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { [self] _ in
            setPage((page + 1) % 2)
        }
    }
    
    func setPage(_ page: Int) {
        pageViews[self.page].run(.sequence([
            .fadeOut(withDuration: 0.2),
            .removeFromParent()
        ]), withKey: "fade")
        self.page = page
        let pageView = pageViews[page]
        pageView.alpha = 0
        pageView.run(.fadeIn(withDuration: 0.2), withKey: "fade")
        pageContainer.addChild(pageView)
    }
    
    override func viewDidLayout() {
        overlay.size = view.frame.size
    }
    
}

extension SKSpriteNode {
    
    public convenience init(systemImage: String) {
        self.init()
        let width = 100.0
        let image = NSImage(systemSymbolName: systemImage, accessibilityDescription: nil)!.withSymbolConfiguration(.init(hierarchicalColor: NSColor.black))!
        let scale = NSScreen.main!.backingScaleFactor
        image.size = CGSize(width: width * scale, height: width / image.size.width * image.size.height * scale)
        texture = SKTexture(image: image)
        size = CGSize(width: width, height: width / image.size.width * image.size.height)
    }
    
}

After some more playing around perhaps I was able to find the real issue by reducing the setPage method to this:

    func setPage(_ page: Int) {
        pageViews[self.page].run(.removeFromParent())
//        pageViews[self.page].removeFromParent()
        self.page = page
        let pageView = pageViews[page]
        pageContainer.addChild(pageView)
    }

When this code runs, only the second sprite becomes visible, and when the first one should appear, its space is left empty.

Could SKAction.removeFromParent() be the problem? Replacing that instantaneous animation with a simple call to SKNode.removeFromParent() allows both sprites to alternate correctly.

SKNode.zPosition causes nodes to flicker by reordering them for 1 frame
 
 
Q