look at this simple example of scroll on tvOS (ver 14.7 18M60):
struct TestView: View
{
var body: some View {
ScrollView (.vertical, showsIndicators: false, content: {
ForEach(0..<600) { index in
Button(action: {}, label: {Text("Button - \(index)")})
.background(Color.blue)
.padding()
}
})
}
}
this example will behave badly when scrolling continuously, the scroll will stop moving and wait for me to finish scrolling on the remote and only a half second after i have finished swipeing on the Apple TV remote then it will update the view and scroll to the requested position,
See a video with this behavior
obviously this is an unwanted behavior, since during the swipe i cannot see where i have reached, therefor i have to stop swipeing, allow the view to refresh and only after i see where the scroll has reached, i can decide wether to keep scrolling or scroll back up (if needed)
it looks like a SwiftUI (try to) optimize performance, but it destroy the whole ATV app experience
i tried fixing it using a ScrollViewReader like this:
import Foundation
import SwiftUI
struct FocusableButton: View
{
@State var isFocused = false
let index: Int
var scrollView: ScrollViewProxy
var body: some View
{
VStack
{
Text("Button - \(index)")
}
.id(index)
.frame(width: 200, height: 100)
.padding()
.scaleEffect(isFocused ? 1.1 : 1)
.background(isFocused ? Color.white : Color.blue)
.focusable(true, onFocusChange: { focused in
isFocused = focused
(focused ? {
scrollView.scrollTo(index)
}() : { /* Lost Focus */ }())
})
}
}
struct TestView: View
{
var body: some View {
ScrollViewReader { scrollView in
ScrollView (.vertical, showsIndicators: false, content: {
ForEach(0..<600) { index in
FocusableButton(index: index, scrollView: scrollView)
}
})
}
}
}
see a video of how it behaves after the fix
but if scroll too fast - the auto - paging - scroll kicks in and crash the app and i get the error (in xcode):
Fatal error: ScrollViewProxy may not be accessed during view updates
and the runtime issue:
Modifying state during view update, this will cause undefined behavior.
can anyone help? any idea how to solve it? (the scroll issue or the error)
even if there is a way to bypass the auto paging scroll i would love to see a code example
strange fact: if you surround the ScrollView
with a List
(even of 1 dummy item) - the scrolling behaves correctly,
so i implemented this test with List
and it behaves correctly:
struct TestView: View
{
var body: some View {
VStack(spacing: 40)
{
List(0..<600) { index in
FocusableButton(index: index)
}
}
}
}
so my current conclusion is that the combination of:
wont work!
if i remove the focusable
and replace it with a button
the ScrollView
and ForEach
also work correctly:
struct TestView: View
{
var body: some View {
VStack(spacing: 40)
{
List(0..<600) { index in
Button(action: {}, label: {
Text("Button - \(index)")
.frame(width: 200, height: 100)
.padding()
.background(Color.blue)
})
}
}
}
}
but all 3 together (ScrollView + ForEach + focusable) behaves badly