CodeBender
CodeBender

Reputation: 36670

Unable to properly size SKScene when working with SwiftUI

I am attempting to use SpriteKit with a SwiftUI based interface, but have encountered an issue when initializing my SKScene.

First, my UIViewRepresentable that contains the SKView has been taken from a similar question:

struct SpriteKitContainer: UIViewRepresentable {
    let scene: SKScene

    func makeUIView(context: Context) -> SKView {
        return SKView(frame: .zero)
    }
    
    func updateUIView(_ view: SKView, context: Context) {
        view.presentScene(scene)
    }
}

A key piece here is that it requires the SKScene to be initialized prior to SpriteKitContainer being initialized.

I currently have the following abbreviated snippet doing this:

import SwiftUI
import SpriteKit

struct GameView: View {
    @State private var isEnabled = true
    
    var body: some View {
        let tapGesture = TapGesture()
            .onEnded { value in }
        
        // This size is obviously wrong
        let size = UIScreen.main.bounds.size
        let scene = Scene(size: size)
        scene.scaleMode = .resizeFill
        
        return SpriteKitContainer(scene: scene)
            .disabled(!isEnabled)
            .frame(maxWidth: .infinity,
                   maxHeight: .infinity,
                   alignment: .center)
            .gesture(tapGesture)
            .padding(.all, 30)
    }
}

I only include the TapGesture to show why the body is structured the way it is. With the code included, everything will work as expected, but the size used for the scene is obviously wrong (it fails to account for the padding, and navigation elements).

However, I have not found any other way to obtain a size or frame at this point, and sending over a .zero size for the Scene(size:) call will not size it correctly either.

My assumption is that I am missing some precursor step as I am still ramping up with SwiftUI. Otherwise, I could calculate a new CGSize given what I know, but that strikes me as the wrong approach here.

Upvotes: 4

Views: 781

Answers (3)

LukeSideWalker
LukeSideWalker

Reputation: 7930

You don't need GeometryReader. Just let SwiftUI itself give the available space to your SpriteView. An important thing is, that the views size is not automatically the scene size. So, you have to explicitly set the scene size.

import SwiftUI
import SpriteKit

struct ContentView: View {

    var body: some View {
        SpriteView(scene: spriteKitScene)
    }

    private var spriteKitScene : SKScene {
        let scene = SpriteKit_View()
        scene.scaleMode = .aspectFit
        return scene
    }

}

class SpriteKit_View : SKScene {

    override func didMove(to view: SKView) {

       // Set the scene size as you want, in my case 
          equal to the views size

       size = view.frame.size
       // ...
    }
}

The scene size can be completely different from the view size, that's when the scene scaleMode comes into action.

Upvotes: 0

ixany
ixany

Reputation: 6070

SKScene has a didChangeSize method for scenarios like that:

override func didChangeSize(_ oldSize: CGSize) {
    self.physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame)
}

From Apples Documentation:

This method is intended to be overridden in a subclass. Typically, you use this method to adjust the positions of nodes in the scene.

Upvotes: 0

Josh Homann
Josh Homann

Reputation: 16347

In SwiftUI you need to use GeometryReader as a proxy to get the view's current size (this happens every time the view is rebuilt). You then want to resize your UIViewRepresentable based on the size of its parent only if the size has actually changed. I have a GeometryReader example here: https://github.com/joshuajhomann/SwiftUI-Spirograph

Upvotes: 3

Related Questions