crashoverride777 crashoverride777 - 2 months ago 53
Swift Question

TVOS 10 SpriteKit Focus Navigation default focused item

I am trying to update my SpriteKit games to use the new SKNode focus navigation feature, but I am having trouble to change the default focused item.

Essentially I have this code in my button class to support focus navigation

class Button: SKSpriteNode {

required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)

userInteractionEnabled = true
}

// MARK: - Focus navigation

#if os(tvOS)
extension Button {

/// Can become focused
override var canBecomeFocused: Bool {
return true
}

/// Did update focus
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {

if context.previouslyFocusedItem === self {
// some SKAction to reset the button to default settings
}

else if context.nextFocusedItem === self {
// some SKAction to scale the button up
}
}
}
#endif


Everything is working great, however by default the first button on the left side of the screen is focused.

I am trying to change this to another button but I cannot do it. I now you are supposed to use

override var preferredFocusEnvironments: [UIFocusEnvironment]...

// preferredFocusedView is depreciated


but I dont understand how and where to use this.

I tried adding this code to my menu scene to change the focused button from the default (shop button) to the play button thats on the right side of the screen.

class MenuScene: SKScene {

// left side of screen
lazy var shopButton: Button = self.childNode(withName: "shopButton")

// right side of screen
lazy var playButton: Button = self.childNode(withName: "playButton")

// Set preferred focus
open override var preferredFocusEnvironments: [UIFocusEnvironment] {
return [self.playButton]
}
}


and calling

setNeedsFocusUpdate()
updateFocusIfNeeded()


in didMoveToView but it doesn't work.

How can I change my default focused button in SpriteKit?

Answer

So I finally got this working thanks to this great article.

https://medium.com/folded-plane/tvos-10-getting-started-with-spritekit-and-focus-engine-53d8ef3b34f3#.pfwcw4u70

The main step I completely missed is that you have tell your GameViewController that your scenes are the preferred focus environment.

class GameViewController: UIViewController {

    static var currentScene: SKScene? // static because I have multiple scenes and need to change this value easily


    override func viewDidLoad() {
         super.viewDidLoad()

         let scene = StartScene(...)
         GameViewController.currentScene = scene
         ...
    }

    override var preferredFocusEnvironments: [UIFocusEnvironment] {
        if let scene = GameViewController.currentScene {
            return [scene]
        } else {
            return []
        }
    }
}

Now I can actually set the preferred focus environment within my StartScene

 class StartScene: SKScene {

      // Set preferred focus 
      override var preferredFocusEnvironments: [UIFocusEnvironment] {
           return [self.playButton]
      }
 }

When I change from StartScene to GameScene I need to update the GameViewController scene property so that GameScene is now the preferred focus environment

 StartScene: SKScene {

       func loadGameScene() {
          let gameScene = GameScene(...)
          GameViewController.currentScene = gameScene
          ...
      }
 }

Than in game scene I set the preferred focus environment again

  class GameScene: SKScene {

      // Set preferred focus 
      override var preferredFocusEnvironments: [UIFocusEnvironment] {

          // 
          if isShowingGameMenu { // some bool to check if game menu node is showing
             return [gameMenuNode]
          } else {
             return [] // means self 
          }
       }
   }

I added a check here to see if my game menu node is added to the scene (just an example) to give it focus environment priority, otherwise its the scene itself.

In gameMenuNode I would than set the preferred environments as well just like the scenes. You basically set up your own responder chain this way.

Finally every time you do such a focus change, it seems you need to also call these

  updateFocusIfNeeded()
  setNeedsFocusUpdate()

It also seems that most tutorials, including apples WWDC keynote, put this code

  /// Did update focus
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {

    if context.previouslyFocusedItem === self {
        // some SKAction to reset the button to default settings  
    }

    else if context.nextFocusedItem === self {
        // some SKAction to scale the button up 
    }
}

not within the button subclass but in each scene that has buttons. I am still experimenting to see if that is necessary.

    class StartScene: SKScene {


      /// Did update focus
     override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {

         if let prevButton = context.previouslyFocusedItem as? Button {
             prevButton.isFocused = false // will run some SKAction
         }

        if let nextButton = context.nextFocusedItem as? Button {
            prevButton.isFocused = false // will run some SKAction
        }
    }
}

I will update this answer if I encounter further issues.

Hope this helps somebody stuck with the same problem.

Comments