我有以下代码,可以在 tvOS 17.5 的模拟器中构建和运行。 我正在尝试在
SKNode
内的两个 GameScene
(即 SKScene
)之间导航焦点。 我无法让两个 FocusableNode
中的任何一个变得明显聚焦。 我能够聚焦场景(周围有一个蓝色矩形),但不能将焦点转移到两个可聚焦子节点中的任何一个。 一个问题是 pressesBegan
永远不会被调用,preferredFocusEnvironments
也不会被调用。
[编辑]一旦我注释掉与焦点相关的视图修饰符并删除了正文中的按钮,我就可以将其中一个可聚焦节点聚焦(其颜色变为蓝色),但无法导航焦点,它只是停留在该节点上第一个按钮。 我还收到此运行时警告:
Using legacy initializer -[UIFocusRegion initWithFrame:] for region <_UIFocusItemRegion: 0x6000017529c0> - 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.
struct ContentView: View {
//@FocusState private var isSpriteViewFocused: Bool
var body: some View {
VStack {
SpriteView(scene: GameScene())
.frame(width: 600, height: 400)
//.focusable(true)
//.focused($isSpriteViewFocused)
//.overlay(
// RoundedRectangle(cornerRadius: 10)
// .stroke(isSpriteViewFocused ? Color.blue : Color.clear, lineWidth: 5)
//)
//Button("Focus SpriteView [\(isSpriteViewFocused)]") {
// isSpriteViewFocused = true //.toggle()
//}
//.padding()
}
.padding()
}
}
class GameScene: SKScene {
override func didMove(to view: SKView) {
backgroundColor = .gray
let size1 = view.bounds.size
self.size = size1
// Create a focusable SKNode
let focusableNode1 = FocusableNode()
focusableNode1.position = CGPoint(x: size1.width / 2, y: size1.height / 2 + 100)
focusableNode1.name = "Focusable Node 1"
addChild(focusableNode1)
let focusableNode2 = FocusableNode()
focusableNode2.position = CGPoint(x: size1.width / 2, y: size1.height / 2 - 100)
focusableNode2.name = "Focusable Node 2"
addChild(focusableNode2)
// Create a non-focusable SKNode
let nonFocusableNode = SKLabelNode(text: "Non-Focusable Node".uppercased())
nonFocusableNode.fontSize = 15
nonFocusableNode.fontColor = .white
nonFocusableNode.position = CGPoint(x: size1.width / 2, y: size1.height / 2)
nonFocusableNode.isUserInteractionEnabled = false // Disable focus
addChild(nonFocusableNode)
if false {
addFocusGuides()
} else {
self.scene?.view?.window?.setNeedsFocusUpdate()
self.scene?.view?.window?.updateFocusIfNeeded()
}
}
private func addFocusGuides() {
guard let view = view else { return }
let guide1 = UIFocusGuide()
view.addLayoutGuide(guide1)
NSLayoutConstraint.activate([
guide1.leadingAnchor.constraint(equalTo: view.leadingAnchor),
guide1.trailingAnchor.constraint(equalTo: view.trailingAnchor),
guide1.topAnchor.constraint(equalTo: view.topAnchor),
guide1.bottomAnchor.constraint(equalTo: view.centerYAnchor)
])
guide1.preferredFocusEnvironments = [childNode(withName: "Focusable Node 2")!]
let guide2 = UIFocusGuide()
view.addLayoutGuide(guide2)
NSLayoutConstraint.activate([
guide2.leadingAnchor.constraint(equalTo: view.leadingAnchor),
guide2.trailingAnchor.constraint(equalTo: view.trailingAnchor),
guide2.topAnchor.constraint(equalTo: view.centerYAnchor),
guide2.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
guide2.preferredFocusEnvironments = [childNode(withName: "Focusable Node 1")!]
}
override var preferredFocusEnvironments: [UIFocusEnvironment] {
return children.compactMap { $0 as? FocusableNode }
}
override func shouldUpdateFocus(in context: UIFocusUpdateContext) -> Bool {
return true
}
override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
super.pressesBegan(presses, with: event)
// Handle focusable node presses
if presses.first?.type == .select {
if let windowScene = view?.window?.windowScene {
if let focusedNode = windowScene.focusSystem?.focusedItem as? FocusableNode {
print("\(focusedNode.name ?? "Unknown Node") selected")
focusedNode.performAction()
}
}
}
}
}
class FocusableNode: SKSpriteNode {
private let defaultColor = UIColor.white
private let focusedColor = UIColor.blue
init() {
let size = CGSize(width: 200, height: 100)
super.init(texture: nil, color: defaultColor, size: size)
isUserInteractionEnabled = true // Enables focus and interaction
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override var canBecomeFocused: Bool {
return true
}
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
if context.nextFocusedItem === self {
color = focusedColor
} else {
color = defaultColor
}
}
func performAction() {
// Perform an action when selected
print("Action performed by \(name ?? "Unknown Node")")
}
}
@main
struct MyTVOSApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
我无法找到集成 SwiftUI 和 SpriteKit 焦点系统的解决方案。 我找到了一种解决方法,使用 SwiftUI 进行焦点管理,并将该信息传达给场景,场景会更新以匹配状态。 这不是我正在寻找的答案,因为它相当复杂,但它有效。 我不会将此答案标记为解决方案 - 我将其附在此处作为解决相同问题的任何人的想法。
import Foundation
import SwiftUI
import SpriteKit
struct ContentView: View {
@FocusState private var focusButton0 : Bool
@FocusState private var focusButton1 : Bool
@State private var focusedIndex : Int = -1
@State private var tappedIndex : Int = -1
var body: some View {
ZStack {
// The SpriteKit scene
SpriteView(scene: GameScene(focusedIndex: $focusedIndex, tappedIndex: $tappedIndex))
.frame(width: 600, height: 400)
.background(Color.black)
// Overlaying SwiftUI buttons that correspond to FocusableNodes
VStack(spacing: 0) {
Rectangle()
.fill(Color.clear)
.frame(width: 200, height: 100)
// .background(focusedIndex == 0 ? Color.orange.opacity(0.5) : Color.clear)
.focusable(true)
.focused($focusButton0)
.onChange(of: focusButton0) { _,v in
if v {
focusedIndex = 0
}
}
.onTapGesture {
tappedIndex = 0
}
Rectangle()
.fill(Color.clear)
.frame(width: 200, height: 100)
// .background(focusedIndex == 1 ? Color.orange.opacity(0.5) : Color.clear)
.focusable(true)
.focused($focusButton1)
.onChange(of: focusButton1) { _,v in
if v {
focusedIndex = 1
}
}
.onTapGesture {
tappedIndex = 1
}
}
}
}
}
class GameScene: SKScene {
@Binding var focusedIndex: Int
@Binding var tappedIndex: Int
private var focusableNodes: [FocusableNode] = []
init(focusedIndex: Binding<Int>, tappedIndex: Binding<Int>) {
self._focusedIndex = focusedIndex
self._tappedIndex = tappedIndex
super.init(size: CGSize(width: 600, height: 400))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func didMove(to view: SKView) {
backgroundColor = .black
// Define focusable nodes
let button1 = FocusableNode(name: "Button 1", position: 0)
button1.position = CGPoint(x: size.width / 2, y: size.height / 2 + 50)
addChild(button1)
let button2 = FocusableNode(name: "Button 2", position: 1)
button2.position = CGPoint(x: size.width / 2, y: size.height / 2 - 50)
addChild(button2)
focusableNodes = [button1, button2]
}
override func update(_ currentTime: TimeInterval) {
for node in focusableNodes {
node.update(isFocused: node.positionIndex == focusedIndex, isTapped: node.positionIndex == tappedIndex)
}
}
}
class FocusableNode: SKSpriteNode {
var positionIndex: Int
init(name: String, position: Int) {
self.positionIndex = position
let size = CGSize(width: 200, height: 100)
super.init(texture: nil, color: .white, size: size)
// isUserInteractionEnabled = true
self.name = name
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(isFocused: Bool, isTapped: Bool) {
if isFocused {
color = .blue
} else if isTapped {
color = .red
} else {
color = .gray
}
}
}
@main
struct MyTVOSApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}