SWIFT: Load different reality Scenes via Variable - swift

So, i am trying to load different Scenes made in RealityComposer, depending on a variable.
What worked so far:
let SceneAnchor = try! Experience1.loadScene()
arView.scene.anchors.append(SceneAnchor)
return arView
Now i looked into apples Documentation and saw the possibility of:
if let anchor = try? Entity.loadAnchor(named: "Scene") {
arView.scene.addAnchor(anchor)
}
where i thought i could just change "Scene" to "Scene(myVar)"
But once i have more than one scene in my file the first solution doesnt work anymore
and the second one doesnt work as well.
What am i missing?
I also looked into working with filenames and was able to make an array of all my .reality Files and Store them in an Array, so i thought i could recall that via the index, but
arrayName[1].loadScene() doesnt seem to work either, eventhough i can print the filenames to console.
Thanks in advance :)

The fact is that Reality Composer creates a separate static method for each scene load. The name of such method is load+scene name. So, if you have 2 scenes in your Exprerience.xcproject with the names Scene and Scene1, then you have 2 static methods
let scene = Experience.loadScene()
let scene1 = Experience.loadScene1()
Unfortunately it is not possible to use the scene name as a parameter, so, you need to use the switch statement in your app to select the appropriate method.

Related

URL updated for GeoAnchors in RealityKit with anchor info after being removed from ARView

I'm using GeoTrackingExample project from Apple:
GeoTrackingExample
and there is a bug in the logic where after you reset the AR Session, it still retains the anchor information in the URL. So the steps are as follows:
start app
tap in the arview and place an object
select Reset AR Session from the menu
tap in the arview to place an object
select save anchor from the menu
save file to device
Reset AR Session
Load the saved file
When the file loads, it will load both the initial object placed in the AR view in step 2 then removed in step 3 and the added object that was placed in step 4. It should only have saved the object added in step 4, not both objects from step 2 and 4. When stepping though the code to debug, it appears that the the anchors are removed from the ARview but somehow the URL will contain the path for both the anchors. What else do I need to do to clear so that objects from previous sessions are not added to the URL in the current session? Is the URL modified when the object is added to an ARView? Please help! Thanks
The example code from Apple uses a computed var to store the array of placed Geoanchors in the scene but since the computed variable is read-only, the values in the array cannot be removed when the scene is reset so it was keeping a long list every time a model was added to the scene with no way to delete them (that I know of.) Fixed the issue by not using a computed variable and modifying the code accordingly:
var currentAnchors: [ARAnchor] = [] // from
var currentAnchors: [ARAnchor] {
return arView.session.currentFrame?.anchors ?? []}
append the geo anchor to the array when the anchor is placed in the scene
add currentAnchors.removeAll() in the reset AR session function
Not sure if this is helpful at all to anyone out there but I'm glad I figured it out!
** Anyone know why they used computed variable for storing AR Anchors?

How to drag and drop an external item for Xcode-ui-testing

In my application I allow the user to drag and drop items from Finder (or any other source of a file based URL) into my application. What I want to do is to add a mechanism that will allow me to test this in the Xcode UI testing.
I can see how to use XCUIElement.press(forDuration:thenDragTo:) to test the drag and drop of a source and destination within the application, but I have been unable to find a way to test when the source of the drag is outside of the application.
In a somewhat related test, I test the copy and paste portion of the application by setting the string I want to paste into NSPasteboard.general, then using XCUIElement.typeKey("v", modifierFlags: .command) to paste it into the desired element. That is a little less than ideal as it depends on Command-v actually being implemented as the paste command, but that is unlikely to change so it is acceptable for my needs. (In fact I've written an XCUIElement.paste(_ s: String) extension that makes it easy for me to add this in a test.)
I believe that drag and drop is also using an NSPasteboard for its communications, so with a little investigation into the underlying mechanism, I should be able to set my object into the correct pasteboard just like I do for the cut and paste. I'm reasonably certain I can figure that part out. But I haven't figured out how to perform the actual drop.
My goal would be to create an XCUIElement.drop(_ url) that would setup the proper "public.file-url" object into the correct pasteboard, and then simulate/perform the drop into the element.
Any ideas?
I should note that I have already tried the following two items:
First, I did use the Xcode record feature to attempt to record the drag and drop operation and see what events it would give me. Unfortunately, it records absolutely nothing.
Second, I do have a menu based alternative where the user selects a file via the file selector. So if I could simulate the file selection, that would be a suitable testing alternative for my purposes. Unfortunately, I didn't make any progress along that path either. When I used Xcode to record the events, it recorded the menu selection, nothing that was actually done in the dialog.
Based on your comments I would recommend you to read this article documentation piece
https://developer.apple.com/documentation/xctest/xcuiapplication
Notice the init(bundleIdentifier: String) and init(url: URL) methods. These allow you to interact with apps apart from the target application.
Then you can use XCUIElement.press(forDuration:thenDragTo:)
import XCTest
import XCTApps
import ScreenObject
let notes = XCTApps.notes.app
let photos = XCTApps.photos.app
class Tests: XCTestCase {
func testDragAndDrop() {
photos.launch()
notes.launch()
photos.images.lastMatch.press(forDuration: 1, thenDragTo: notes.textViews["Note Body Text View"])
}
}
P.S. In this example I use XCTApps because I don't want to remember or google bundle identifiers :D
https://github.com/rzakhar/XCTApps
Ok, so I haven't yet figured out the answer to my question (how to test a drag and drop), but I have come up with an acceptable workaround for my test.
Specifically, as I thought more about the pasteboard I realized that if I'm allowing the user to drag and drop a file into my application, then I should also be allowing them to cut and paste a file into the application.
Once I had that realization, then it was a reasonably simple process to test the necessary feature of my application by pasting a URL instead of dragging and dropping the URL. This has the added advantage that I can add the necessary test file to my testing package, keeping everything nicely self contained.
To this end I've added the following function to my XCUIElement extension:
extension XCUIElement {
func paste(url: URL) {
precondition(url.isFileURL, "This must be a file URL to match the pasteboard type.")
let pasteboard = NSPasteboard.general
pasteboard.clearContents()
pasteboard.setString(url.absoluteString, forType: .fileURL)
click()
typeKey("v", modifierFlags: .command)
}
}
Then in my test code I add the following to trigger the event:
let mainWindow = app.windows[/*...my main window name goes here...*/]
let testBundle = Bundle(for: type(of: self))
let fileURL = testBundle.url(forResource: "Resources/simple", withExtension: "json")
mainWindow.paste(url: fileURL!)
Granted, this doesn't actually test the drag and drop, but it does test the same portion of my code, since in my AppDelegate I have my onPaste action method calling the same underlying method as my performDrop method.
I will wait a couple of days to see if anyone comes up with an answer to the actual question (since I would still find that useful), but if no one does, I'll accept my own answer.

Get reference to a Particle System created in Scene Editor

I've created a particle system in the Scene Editor (not the particle Editor), and it's named "particles" (this is the default name).
Back in the ViewController, I'm attempting to get a reference to this particle system and change some properties of it.
But I can't figure out why this doesn't work:
let particleSystem = SCNParticleSystem(named: "particles", inDirectory: "")
particleSystem?.isAffectedByGravity = true
I know it's possible to set gravity on within the Scene Editor, but I'm simply using this as a test to see if the reference to the Particle System is working. It's not.
What am I missing or doing wrong?
ADDITIONAL EFFORTS:
As per Rickster's suggestion, trying this:
let particleSystems = scene.particleSystems
let myParticleSystem = particleSystems?[0]
myParticleSystem?.isAffectedByGravity = true
print(particleSystems)
This has now this problem:
My thinking (faulty as it is) was that the array's 0 location would have the only particle system I have in this scene.
Here is a picture of the scene I got:
And the code to get a reference to the particle system assigned to the particles SCNNode instance:
In Swift 2.3 (and earlier)
let particlesNode:SCNNode = scene.rootNode.childNodeWithName("particles", recursively: true)!
let particleSystem:SCNParticleSystem = (particlesNode.particleSystems?.first)!
In Swift 3.0
let particlesNode:SCNNode = scene.rootNode.childNode(withName: "particles", recursively: true)!
let particleSystem:SCNParticleSystem = (particlesNode.particleSystems?.first)!
Alternative Array Index Syntax
let particleSystem:SCNParticleSystem = (particlesNode.particleSystems?[0])!
As the docs for that initializer suggest, it's for loading particle systems that are in their own files. When you make a particle system in the scene editor, the particle system isn't its own file — it's part of the scene file you're making.
To find it... well, first you have to load the SCNScene object that file represents. Assuming you've done that already, you'll need one of two ways to access the particle system depending on how you defined it in the editor:
If the particle system is attached to a node — the straightforward way to position it in the scene — you can use the scene's rootNode property to get at the scene contents, then use methods like this one to find the node containing the particle system using the name you assigned it in the editor. (You did give it a name in the editor, right? If not, go do that... or worst case, there's always this method that lets you search for nodes based on any test you can code.)
If not, use the scene's particleSystems property to find all systems that aren't attached to nodes.

Swift/SpriteKit - Any way to pool/cache SKReferenceNodes?

Is there a way to pool/cache SKReferenceNodes in SpriteKit using Swift?
I am creating a game using xCodes visual level editor. I am creating different .sks files with the visual level editor that I am than calling in code when I need to. I am calling them in code because I am using them to create random levels or obstacles so I don't need all of them added to the scene at once.
At the moment I am doing it like this
I create a convince init method for SKReferenceNodes to init them with URLs. I am doing this because there is some sort of bug calling SKReferenceNodes by file name directly (https://forums.developer.apple.com/thread/44090).
Using such an extension makes makes the code a bit cleaner.
extension SKReferenceNode {
convenience init(roomName: String) {
let path: String
if let validPath = NSBundle.mainBundle().pathForResource(roomName, ofType: "sks") {
path = validPath
} else {
path = NSBundle.mainBundle().pathForResource("RoomTemplate", ofType: "sks")! // use empty roomTemplate as backup so force unwrap
}
self.init(URL: NSURL(fileURLWithPath: path))
}
}
and than in my scenes I can create them and add them like so (about every 10 seconds)
let room = SKReferenceNode(roomName: "Room1") // will cause lag without GCD
room.position = ...
addChild(room)
This works ok but I am getting some lag/stutter when creating these. So I am using GCD to reduce this to basically no stutter. It works well but I am wondering if I can preload all .sks files first.
I tried using arrays to do this but I am getting crashes and it just doesn't seem to work (I also get a message already adding node that has a parent).
I was trying to preload them like so at app launch
let allRooms = [SKReferenceNode]()
for i in 0...3 {
let room = SKReferenceNode(roomName: "Room\(i)")
allRooms.append(room)
}
and than use the array when I need too. This doesn't work however and I am getting a crash when trying to use code like this
let room = allRooms[0]
room.position =
addChild(room) // causes error/crash -> node already has parent
Has anyone done something similar? Is there another way I can pool/cache those reference nodes?. Am i missing something here?
Speaking about the SKReferenceNode preload, I think the policy to be followed is to load your object, find what kind they are and use the official preloading methods available in Sprite-Kit:
SKTextureAtlas.preloadTextureAtlases(_:withCompletionHandler:)
SKTexture.preloadTextures(_:withCompletionHandler:)
To avoid this kind of error you should create separate instances of the nodes.
Try to doing this:
let room = allRooms[0]
room.position = ...
room.removeFromParent()
addChild(room)
I just figured it out, I was just being an idiot.
Using arrays like I wanted to is fine, the problem I had which caused the crash was the following.
When the game scene first loads I am adding 3 rooms but when testing with the arrays I kept adding the same room
let room = allRooms[0]
instead of using a randomiser.
This obviously meant I was adding the same instance multiple times, hence the crash.
With a randomiser, that doesn't repeat the same room, this does not happen.
Furthermore I make sure to remove the room from the scene when I no longer need it. I have a node in the rooms (roomRemover) which fires a method to remove/create a new room once it comes into contact with the player.
This would be the code in DidBeginContact.
guard let roomToRemove = secondBody?.node.parent?.parent?.parent?.parent else { return }
// secondBody is the roomRemover node which is a child of the SKReferenceNode.
// If I want to remove the whole SKReferenceNode I need to add .parent 4 times.
// Not exactly sure why 4 times but it works
for room in allRooms {
if room == roomToRemove {
room.removeFromParent()
}
}
loadRandomRoom()
Hope this helps someone trying to do the same.

Trouble with SKEmitterNode - doesn't display colors created in sks file

I have the function below to show an emitter. It is supposed to pick which asks file to use for the emitter based upon the color passed into the function. The sks files have been created and named based upon their colors and they display the proper coloring in Xcode.
However when run on the simulator or the device, it does not appear that the coloring from the sks file is honored. No matter what color is passed in, the emitter shows the same particle colors. BTW this is a spark based emitter.
Any ideas what I may be doing wrong?
func showEmitter(theColor:String){
var ourEmitterName:String?
switch(theColor) {
case "black","white":
ourEmitterName = "blackwhiteemitter"
default:
ourEmitterName = "\(theColor)emitter"
}
let emitterPath = NSBundle.mainBundle().pathForResource(ourEmitterName, ofType: "sks")
let thisEmitter:SKEmitterNode = NSKeyedUnarchiver.unarchiveObjectWithFile(emitterPath!) as SKEmitterNode
thisEmitter.zPosition = SceneLevel.background.rawValue
self.addChild(thisEmitter)
}
Thanks for your help - Ken
I found the problem. In my emitters I had the Blend Mode set to 'Add'. After I changed it to 'Alpha' everything worked fine.
Not sure why that solved it, since I don't know to what was 'added' to what.