swiftuiscenekitswift-packagesceneview

SceneKit: Loading .scnassets inside a Swift Package


I have a shared SceneView that I should show it directly on iPad and show in a seperate window on macOS if it's requested by the user.

So, I decided to have SceneView and it's assets inside a Swift Package for reusability.

For now, my SwiftUI View shows perfectly on both OS' but I couldn't make apps load my scene from .scnassets folder packed into the swift package.

I wrote the question till here but then I've found some solutions. So I stop the question here and adding an answer below. Check it out if you have the same difficulity.


Solution

  • First of all, I've learned that scnassets is not treated as an asset by Xcode and not getting bundled by automatically.

    To bundle it you need to add it - actually copy it- into the Bundle by yourself.

    To do that open your Package.swift package description file and add it into your target like

    .target(
        name: "MyPackage",
        resources: [
            .copy("GreatArt.scnassets")
        ]
    ),
    

    If you need to put your asset in a folder, add folder names before your file, for ex Resources/GreatArt.scnassets

    Now you can get it's url with

    Bundle.module.url(forResource: "aScene", withExtension: "scn", subdirectory: "GreatArt.scnassets")
    

    Be aware, we are looking into the module bundle, not main!

    Second thing to be aware, even if you put your assets into some folders, to get it's URL you do not use containing folders. Now it is bundled by Xcode as an asset. Folders containing it are irrelevant.

    For now, I still couldn't load my scene directly inside my body as

    SceneView(scene: SCNScene("GreatArt.scnassets/aScene.scn"))
    

    and I'm afraid it's not possible. I think that initializer looks into the main bundle for resource, but our resource is in the module bundle. If there is a way you know please share.

    So current final solution of mine is using the URL we got few lines above

        var scene: SCNScene? {
            let url = Bundle.module.url(forResource: "aScene", withExtension: "scn", subdirectory: "GreatArt.scnassets")!
            do {
                return try SCNScene(url: url)
            } catch(let err) {
                logger.error("Scene couldn't get loaded. \(err)")
                return nil
            }
        }
    
        public var body: some View {
            SceneView(scene: scene)
        }
    

    For more information check Apple Docs: Bundling resources with a Swift package

    I hope I was good at answering my own question. Happy coding.