Search code examples
iosswiftsprite-kituitextfieldskscene

SKView integrated into UIViewController with UITextFields lagging


I am trying to use a SKView in my login UI for some animations. The SKView is added as a subview to a LoginContainerView as well as the view containing the UITextFields and the sign in button. All of the subviews are positioned with autolayout and the FireScene attached to the SKView has it's scaleMode set to .resizeFill.

The issue I'm having is very clear to observe from this clip: https://streamable.com/5it17 .

Clip explanation: Whenever I double tap any of the UITextFields (to trigger the UIMenuController / Paste button) the FireScene freezes for a brief amount of time (yet it's very noticeable) and the FPS drops down to around 30 for 1-2 seconds.

The only node that the FireScene has added as its child node is a modified fireflies SKEmitterNode template. I'm fairly certain that this has nothing to do with the actual code (as it is very straightforward and concise) and is probably a UIKit mixed with SpriteKit bug.

Relevant code:

class FireScene: SKScene {

    // MARK: - Properties
    var fireSparkEmitter: SKEmitterNode? = nil

    // MARK: - Inherited
    override func didChangeSize(_ oldSize: CGSize) {
        // position the emitter to scene's center
        fireSparkEmitter?.position = CGPoint(x: size.width/2, y: size.height/2)
    }

    override func didMove(to view: SKView) {
        if let emitter = fireSparkEmitter {
            emitter.resetSimulation()
        } else {
            guard let emitter = SKEmitterNode(fileNamed: "FireSparks.sks") else { return }
            addChild(emitter)
            fireSparkEmitter = emitter
        }

    }
}

class LoginContainerView: UIView {
    /// ...
    let fireBackgroundScene: FireScene = {
        let scene = FireScene(size: .zero)
        scene.scaleMode = .resizeFill
        return scene
    }()
    /// ...

    // MARK: - Inherited
    // called after view initialization
   func setupSubviews() {
        let skView = SKView(frame: .zero)
        skView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(skView)
        skView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
        skView.topAnchor.constraint(equalTo: topAnchor).isActive = true
        skView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        skView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true

        skView.showsFPS = true
        skView.presentScene(fireBackgroundScene)
    }
}

PS: I've tried to profile the app with Time Profiler and it seems that the bottleneck is the UITextField gesture handling by UIKit itself.


Solution

  • Your issue seems related to a different layout syncronization between SKView and the other UIKit objects. SKView have a property called isAsynchronous (default = true) that indicates whether the content is rendered asynchronously. You can try to set:

    skView.isAsynchronous = false
    

    to syncronize your SKView with the core animation updates. This could be not enough because you'll syncronize SKView with the Core Animations of your project but there are always the UIKit-style animations that could be out of sync. If this property don't solve your issue you can change also the preferredFramesPerSecond property. Remember that when you try to mix two frameworks (sprite-kit and UIKit) you will inevitably incur to unpleasant hitches.