Despite reading loads of documentation and SO threads, I just can’t make my tvOS app focus change while using the keyboard on the tvOS simulator.
Used config: XCode 15, tvOS 17
Starting point: on the simulator home screen, I can use the arrow keys and the return key to navigate and launch apps.
I just have a simple app with 3 horizontally aligned sprites, and I’d like the arrow keys to navigate between them.
View Controller:
class GameViewController: UIViewController {
// overriding preferredFocusEnvironments to return [scene] doesn't change anything
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
/* set or unset doesn't change anything
scene.focusBehavior = .focusable
scene.isUserInteractionEnabled = true
*/
// Present the scene
view.presentScene(scene)
}
}
}
override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
print("EVT- press began in controller")
}
override func pressesChanged(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
print("EVT- press changed in controller")
}
override func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
print("EVT- press ended in controller")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("EVT- touch began in controller")
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print("EVT- touches moved in controller")
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
print("EVT- touches ended in controller")
}
}
Scene class:
class GameScene: SKScene {
private var button1 : MySpriteNode!
private var button2 : MySpriteNode!
private var button3 : MySpriteNode!
//Stef: overriden or not doens't make a difference
// override var preferredFocusEnvironments: [UIFocusEnvironment] {
// return [button1, button2, button3]
// }
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
//Stef: called after the didUpdateFocus of the buttons
print("GameScene::didUpdateFocus")
print("FOC- Focus is now: (UIScreen.main.focusedItem)")
}
private var label : SKLabelNode?
private var spinnyNode : SKShapeNode?
override func didMove(to view: SKView) {
self.button1 = self.childNode(withName: "//sprite1") as? MySpriteNode
self.button1.isUserInteractionEnabled = true //set or unset doesn't make a difference
self.button2 = self.childNode(withName: "//sprite2") as? MySpriteNode
self.button2.isUserInteractionEnabled = true
self.button3 = self.childNode(withName: "//sprite3") as? MySpriteNode
self.button3.isUserInteractionEnabled = true
self.isUserInteractionEnabled = true //set or unset doesn't make a difference
// self.becomeFirstResponder() doesn't make any difference
}
}
Sprite node:
class MySpriteNode: SKSpriteNode {
override var canBecomeFocused: Bool {
true
}
override var isUserInteractionEnabled: Bool {
get { true }
set { }
}
override var focusBehavior: SKNodeFocusBehavior {
get { .focusable }
set { }
}
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
print("SKSpriteNode::didUpdateFocus")
if context.previouslyFocusedItem === self {
print("FOC- Leaving (self.name!)")
self.resignFirstResponder()
}
if context.nextFocusedItem === self {
// SKAction to run focus animation for focused button
print("FOC- Entering (self.name!)")
self.becomeFirstResponder()
printResponderChain(from: self)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("EVT- touch began in sprite")
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
print("EVT- (self.name!) touched")
}
override func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
print("EVT- (self.name!) pressed")
}
}
Whatever I do, I always get the same console logs:
SKSpriteNode::didUpdateFocus
FOC- Entering sprite1
Resp: <SKSpriteNode> name:'sprite1' texture:['nil'] position:{-348.35546875, 220.66392517089844} scale:{1.00, 0.37} size:{100, 37.421669006347656} anchor:{0.5, 0.5} rotation:0.00
GameScene::didUpdateFocus
FOC- Focus is now: Optional(<SKSpriteNode> name:'sprite1' texture:['nil'] position:{-348.35546875, 220.66392517089844} scale:{1.00, 0.37} size:{100, 37.421669006347656} anchor:{0.5, 0.5} rotation:0.00)
void * _Nullable NSMapGet(NSMapTable * _Nonnull, const void * _Nullable): map table argument is NULL
I also get a bunch of warnings like this:
Using legacy initializer -[UIFocusRegion initWithFrame:] for region <_UIFocusItemRegion: 0x600001762780> - if this region is initialized by a client, please move over to using the UIFocusItem API. If this region is coming from UIKit, this is a UIKit bug.
As stated by the warning, looks like a UIKit bug.
That shows that Sprite 1 gets the focus, but when I use the arrow keys, the other ones never ever get the focus.
I have tried 3 or 4 Open Source packages, they all date from 2017, and none of them work, I never succeed in having the arrow key to navigate the focus.
What’s the catch ?
Am I missing something huge ?