Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SpriteKit getting error regarding UIElements when transitioning to new scene

Hello I am working on a SpriteKit game in Swift that right now only has two scenes, a loading scene and the game scene. The game scene reaches out to a database to get info before it is presented. Everything in the game scene works great. I am running into issues when I go to the loading scene first instead of straight to the game scene. Through call backs I tell the loading scene to present the game scene once it is done doing the database stuff. However when I try presenting I get 10+ of these massive errors:

2015-11-12 09:48:33.880 SpaceAcademy[2373:764586] This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes. This will cause an exception in a future release. (followed by a bunch of random info)

Now through some testing I have figured out that its my UIElement such as buttons, sliders, and labels that are causing this. But I moved them into the didMoveToView function so I am confused why they are still throwing errors because they are in the "background thread"..

Also something odd is that these aren't fatal errors. My console just blows up and the UIElements show up after a ten second delay. Something else odd is that if I tap where I know the button is supposed to be the function is still called as if the button was actually there.

I would greatly appreciate a solution to get rid of the console explosion but more importantly get rid of the ten second delay of the UI Elements showing up, Thanks!!

Code Setting up Scenes

let skView = view as! SKView
    skView.showsFPS = false
    skView.showsNodeCount = false
    skView.ignoresSiblingOrder = true

    let scene = GameScene(size: view.bounds.size)
    scene.scaleMode = SKSceneScaleMode.ResizeFill


    let loading = LoadingScene(size: view.bounds.size, newScene: scene)
    skView.presentScene(loading, transition: SKTransition.crossFadeWithDuration(1))

    scene.setEverythingUp(loading)

The loading Scene:

import Foundation
import SpriteKit

class LoadingScene:SKScene{
    var newScene:SKScene!
    var oldCount = 0
    var loadCount = 0{
    didSet{
        print(loadCount)
        if loadCount == 0{
            if oldCount == 1{
                self.view?.presentScene(newScene)
            }
        }
        oldCount = loadCount
    }
}

init(size: CGSize, newScene:SKScene) {
    self.newScene = newScene
    super.init(size: size)

}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

override func didMoveToView(view: SKView) {
    self.backgroundColor = UIColor.blackColor()
    let imageArray = [
        SKTexture(imageNamed: "Loader/Loader1.png"),
        SKTexture(imageNamed: "Loader/Loader2.png"),
        SKTexture(imageNamed: "Loader/Loader3.png")
    ]

    let loader = SKSpriteNode(texture: imageArray[0])
    let animation = SKAction.animateWithTextures(imageArray, timePerFrame: 0.33)
    loader.runAction(SKAction.scaleBy(0.25, duration: 0))
    loader.zPosition = 100
    loader.runAction(SKAction.repeatActionForever(animation))
    loader.position = CGPointMake(self.size.width/2, self.size.height/2)
    self.addChild(loader)
}

func start(){
    self.loadCount = self.loadCount + 1
}

func end(){
    self.loadCount = self.loadCount - 1
}

}

Code from GameScene:

func setEverythingUp(myLoadingScene: LoadingScene){

    let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()

    var Id = 0
    if let tempID = defaults.objectForKey("UserId") as? Int {
       Id = tempID
    }else{
        print("newUser")
        defaults.setObject(0, forKey: "UserId")
        defaults.synchronize()
    }

    self.mySettings = Settings(UserId: Id)
    self.myURLRequests = URLRequests(thesettings: self.mySettings)

    ///////////call back system here, start() increments number of tasks waiting to finish, end() means a task is finished

    myLoadingScene.start()
    myURLRequests.getFleet(self, UserId: self.mySettings.UserId, enemy: false){ () -> () in
        myLoadingScene.end()
    }

    myLoadingScene.start()
    myURLRequests.getFleet(self, UserId: self.mySettings.UserId, enemy: true){ () -> () in
        myLoadingScene.end()
    }



    self.backgroundColor = SKColor.blackColor()
    self.physicsWorld.contactDelegate = self
    self.physicsWorld.gravity = CGVector(dx: 0, dy: 0)
    addStars()


    self.anchorPoint = CGPoint(x:self.size.width/2, y:self.size.height/2)
    self.myCamera.position = CGPointMake(200, 0)
    self.addChild(self.myCamera)
    self.camera = self.myCamera
    self.myAnchor.position = self.myCamera.position
}



override func didMoveToView(view: SKView) {
    print("in view")

    let pinch = UIPinchGestureRecognizer(target: self, action: Selector("PinchScale:"))
    let tap = UITapGestureRecognizer(target: self, action: Selector("Tap:"))
    let pan = UIPanGestureRecognizer(target: self, action: Selector("DragMove:"))

    self.view?.addGestureRecognizer(pinch)
    self.view?.addGestureRecognizer(tap)
    self.view?.addGestureRecognizer(pan)


    let fireButton = UIButton(type: .System)
    fireButton.setTitle("Fire!", forState: UIControlState.Normal)
    fireButton.titleLabel!.font = UIFont(name: "TrebuchetMS", size: 70)
    fireButton.sizeToFit()
    fireButton.frame = CGRect(x: self.size.width*0.5 - 80, y: 20, width: self.size.width*0.3, height: 30)
    fireButton.addTarget(self, action: Selector("FireButtonClicked"), forControlEvents: UIControlEvents.TouchUpInside)
    self.view?.addSubview(fireButton)

    let alternateControls = false

    let AngleLabel = UILabel()
    AngleLabel.text = "Angle"
    AngleLabel.font = UIFont(name: "TrebuchetMS", size: 30)
    AngleLabel.textColor = UIColor.blueColor()


    let AngleSlider = UISlider()
    AngleSlider.addTarget(self, action: Selector("AngleSlided:"), forControlEvents: UIControlEvents.ValueChanged)
    AngleSlider.maximumValue = 90
    AngleSlider.minimumValue = -90
    AngleSlider.value = 0
    AngleSlider.continuous = true



    let PowerLabel = UILabel()
    PowerLabel.text = "Power"
    PowerLabel.font = UIFont(name: "TrebuchetMS", size: 30)
    PowerLabel.textColor = UIColor.blueColor()


    let PowerSlider = UISlider()
    PowerSlider.addTarget(self, action: Selector("PowerSlided:"), forControlEvents: UIControlEvents.ValueChanged)
    PowerSlider.maximumValue = 0.99
    PowerSlider.minimumValue = 0.1
    PowerSlider.value = 0.5
    PowerSlider.continuous = true


    if alternateControls{
        AngleLabel.frame = CGRect(x: self.size.width*0.05, y: 10, width: self.size.width*0.3, height: 60)
        AngleSlider.frame = CGRect(x: self.size.width * -0.05, y: self.size.height*0.55, width: self.size.height*0.7, height: 30)
        AngleSlider.transform = CGAffineTransformMakeRotation(CGFloat(M_PI) * 1.5)
        PowerLabel.frame = CGRect(x: self.size.width*0.7, y: 10, width: self.size.width*0.3, height: 60)
        PowerSlider.frame = CGRect(x: self.size.width * 0.65, y: self.size.height*0.55, width: self.size.height*0.7, height: 30)
        PowerSlider.transform = CGAffineTransformMakeRotation(CGFloat(M_PI) * 1.5)
    }else{
        AngleLabel.frame = CGRect(x: self.size.width*0.21, y: self.size.height-80, width: self.size.width*0.3, height: 60)
        AngleSlider.frame = CGRect(x: self.size.width * 0.025, y: self.size.height-30, width: self.size.width*0.45, height: 30)
        PowerLabel.frame = CGRect(x: self.size.width*0.71, y: self.size.height-80, width: self.size.width*0.3, height: 60)
        PowerSlider.frame = CGRect(x: self.size.width * 0.525, y: self.size.height-30, width: self.size.width*0.45, height: 30)
    }

    self.view?.addSubview(AngleLabel)
    self.view?.addSubview(AngleSlider)
    self.view?.addSubview(PowerLabel)
    self.view?.addSubview(PowerSlider)

}
like image 955
AwesomeTN Avatar asked Nov 12 '15 17:11

AwesomeTN


1 Answers

You're doing this:

myURLRequests.getFleet(self, UserId: self.mySettings.UserId, enemy: false){ () -> () in
    myLoadingScene.end()
}

Which presumably calls myLoadingScene.end() on a background thread once the URL request is complete. end sets loadCount, which has a setter that starts the new view, self.view?.presentScene(newScene)

It's this last piece that needs to run on the main thread.

Let's say you had a handy function to run something on the main thread:

func on_main_async (closure:()->()) {
    dispatch_async(dispatch_get_main_queue()) {
        closure()
    }
}

Then you could change the scene presentation code to

on_main_async() { self.view?.presentScene(newScene) }

Should do the trick.

like image 129
Graham Perks Avatar answered Feb 21 '23 08:02

Graham Perks