Tenho a seguinte implementação de scrubber no SwiftUI. O +
botão deve mover para ScrollView
cima em 1 tick (ou scrollPosition
incrementado em 1), mas o problema é que não há rolagem até que eu clique 8-9 vezes. Isso é um bug no iOS ou um erro de programação?
struct BrokenVerticalScrubberDemo: View {
@State private var scrollPosition: Int? = 0
@State private var count: Int = 20
var body: some View {
VStack {
Text("Scroll Position: \(scrollPosition ?? 0)")
.font(.headline)
ScrollView(.vertical, showsIndicators: true) {
VStack(spacing: 8) {
ForEach(0..<count, id: \.self) { index in
Text("Tick \(index)")
.frame(height: 30)
.id(index)
}
}
.scrollTargetLayout()
.padding(.vertical, 50)
}
.scrollTargetBehavior(.viewAligned)
.scrollPosition(id: $scrollPosition)
.frame(height: 300)
.border(Color.gray)
Button("+1") {
withAnimation {
scrollPosition = min((scrollPosition ?? 0) + 1, count - 1)
}
}
.padding(.top)
}
.padding()
}
}
#Preview {
BrokenVerticalScrubberDemo()
}
Por outro lado, se eu usar ScrollViewReader
como solução alternativa, ele começa a rolar após 2 toques no botão '+'.
import SwiftUI
struct SomeWhatWorkingVerticalScrubberDemo: View {
@State private var scrollPosition: Int = 0
@State private var count: Int = 20
var body: some View {
VStack {
Text("Scroll Position: \(scrollPosition)")
.font(.headline)
ScrollView(.vertical, showsIndicators: true) {
ScrollViewReader { scrollViewProxy in
VStack(spacing: 8) {
ForEach(0..<count, id: \.self) { index in
Text("Tick \(index)")
.frame(height: 30)
.id(index)
}
}
.padding(.vertical, 50)
.onChange(of: scrollPosition) { newPosition in
withAnimation {
scrollViewProxy.scrollTo(newPosition, anchor: .center)
}
}
}
}
.frame(height: 300)
.border(Color.gray)
Button("+1") {
scrollPosition = min(scrollPosition + 1, count - 1)
}
.padding(.top)
}
.padding()
}
}
#Preview {
SomeWhatWorkingVerticalScrubberDemo()
}
Por favor, tente usar a âncora superior
na posição de rolagem.
A documentação sobre o comportamento da âncora padrão é um pouco enigmática para mim. Mas no seu exemplo parece ser a âncora inferior.
Adicionar uma âncora pode corrigir a rolagem dos botões, mas não corrige realmente a falha no layout e no uso geral do controle deslizante, já que você nunca poderá selecionar todo o intervalo de valores (como 0 ou qualquer valor acima de 13).
Além disso, se você arrastar o controle deslizante (sem usar o botão), os valores não serão alinhados corretamente. Eles avançam dois valores por vez ou exigem que você arraste mais de duas etapas para aumentá-los em uma etapa, conforme mostrado na gravação abaixo.
Há também o problema de que definir um valor padrão para
scrollPosition
não funciona assim. No seu exemplo, você o define como zero e o mostra como zero ao desembrulhá-lo noText
, mas na realidade ele ainda énil
.Então, se você precisasse rolar para o 5º tick inicialmente definindo o
scrollPosition
valor padrão para 5, não funcionaria. Para realmente fazer funcionar, deixe-o sem valor padrão e defina um valor inicial em.onAppear
ofScrollView
.O alinhamento da visualização (se implementado corretamente) deve funcionar com qualquer âncora (ou mesmo sem âncora). Nesse caso, qualquer âncora diferente
.top
basicamente não funcionará (e até mesmo .top tem os problemas descritos acima).O culpado por tudo isso é o preenchimento vertical adicionado ao
VStack
que é usado para o destino do layout:Aqui está um código completo que reproduz o problema, com opções para selecionar diferentes âncoras:
A solução:
A correção é bem simples e consiste em remover qualquer preenchimento de
VStack
e, em vez disso, adicionar margens aoScrollView
, considerando a altura de cadatick
, para centralizar tudo verticalmente e permitir a seleção de todo o intervalo de valores. Então, neste caso, seria:Aqui está o código de trabalho completo:
Observe que uma âncora não foi usada no código acima, porque ela não é realmente necessária neste caso de uso, onde as marcas de seleção são centralizadas na scrollview.
Para recapitular:
Use para ajustar
.contentMargins
oScrollView
preenchimento com base nas dimensões da scrollview e no tamanho da visualização individual.Defina um valor de posição de rolagem padrão na
.onAppear
visualização de rolagem.