swiftuitvos

tvOS: how to give focus to the list's next 50th element?


I have a list displaying all integers from 1 to 1000. When the user taps the > button on the remote, I would like to advance by 50 items in that list. This is what I tried, but when I press the > button, I get the information that focused is not nil, but it doesn't get updated though.

struct ContentView: View {
    @FocusState private var focused: Int?
    
    var body: some View {
        List(1..<1001) { i in
            Button(action: {}) {
                Text("\(i)")
            }
            .focused($focused, equals: i)
        }
        .onMoveCommand { direction in
            switch direction {
            case .right:
                if focused == nil {
                    print("focused is nil")
                } else {
                    print("focused is not nil")
                    focused! += 50
                }
            default:
                break
            }
        }
        .onChange(of: focused) { oldValue, newValue in
            print(newValue)
        }
    }
}

What should I do? Thanks for your help


Solution

  • When you've got a big list, like with 1000 numbers, it won't load everything at once—it just shows what's needed. This is the Lazy loading nature of List in SwiftUI.

    You can use a ScrollViewReader and use its proxy to help you jump around the list, even to parts not shown yet:

    struct ContentView: View {
        @FocusState private var focused: Int?
    
        var body: some View {
            ScrollViewReader { proxy in
                List(1..<1000, id: \.self) { i in
                    Button(action: {}) {
                        Text("\(i)")
                    }
                    .focused($focused, equals: i)
                    .id(i)
                }
                .onMoveCommand(perform: handleMoveCommand(proxy: proxy))
            }
        }
    
        private func handleMoveCommand(proxy: ScrollViewProxy) -> (MoveCommandDirection) -> Void {
            return { direction in
                if direction == .right {
                    withAnimation {
                        let newFocus = (focused ?? 0) + 50
                        focused = newFocus
                        proxy.scrollTo(newFocus, anchor: .top)
                    }
                }
            }
        }
    }
    

    When you press the right button, the list smoothly scrolls to number 50, even if it wasn't on the screen before. To do that, use proxy.scrollTo(50). And with focused = 50, the list knows that number 50 is where the action's at now.

    Try to avoid doing things like focused! in your code as much as possible. It can cause crashes when the values are truly not present and you try to access them.