Eu tenho uma Roda de Rolagem Horizontal Personalizada que conecta um modelo que salva valor em um UserDefault usando didset. Quando eu uso um HStack regular, o valor está correto e salva corretamente em UserDefaults quando você rola para um novo número, mas o problema é que quando a visualização é mostrada inicialmente, ela sempre está em 0 quando eu confirmo que o valor passado não é zero. Quando eu mudo para um LazyHStack, ele funciona como recuado, mas vem com bugs estranhos onde ele não rola para a posição correta e às vezes quebra a rolagem. :
Onde o userDefaults está sendo definido.
@Published var captureInterval: Int = 1 {
didSet {
UserDefaults.standard.set(captureInterval, forKey: "captureInterval")
}
}
@Published var startCaptureInterval: Int = 2 {
didSet {
UserDefaults.standard.set(startCaptureInterval, forKey: "startCaptureInterval")
}
}
A visão em questão:
struct WheelPicker: View {
var count: Int //(20 passed in)
var spacing: CGFloat = 80
@Binding var value: Int
//TODO: Add some Haptic Feedback
var body: some View {
GeometryReader { geometry in
let size = geometry.size //Size of the entire Parent Container
let horizontalPadding = geometry.size.width / 2
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: spacing) {
ForEach(0..<count, id: \.self) { index in
Divider()
.foregroundStyle(.blue)
.frame(width: 0, height: 30, alignment: .center)
.frame(maxHeight: 30, alignment: .bottom)
.overlay(alignment: .bottom) {
Text("\(index)")
.font(.system(size: index == value ? 25 : 20))
//.fontWeight(.semibold)
.textScale(.secondary)
.fixedSize() //Not letting any parent Resize the text
//.offset(y: 38) //how much to push off the bottom of the text from bottom of Divider
.offset(y: index == value ? 43 : 38) //adjusting for the 5 extra points the bigger text has
}
}
}
.scrollTargetLayout()
.frame(height: size.height)
}
.scrollIndicators(.hidden)
.scrollTargetBehavior(.viewAligned) //will help us know which index is at the center
.scrollPosition(id: Binding<Int?>(get: { //needed because scrollPositon wont accept a Binding int
let position: Int? = value
return position
}, set: { newValue in
if let newValue {
value = newValue //simply taking in the new value and pass it back to the binding
}
}))
.overlay(alignment: .center, content: {
Rectangle() //will height the active index in wheelpicker by drawing a small rectangle over it
.frame(width: 1, height: 45) //you can adjust its height to make it bigger
})
.safeAreaPadding(.horizontal, horizontalPadding) //makes it start and end in the center
}
}
}
#Preview {
@Previewable @State var count: Int = 30
@Previewable @State var value: Int = 5
WheelPicker(count: count, value: $value)
}
O que tentei até agora é criar uma variável Int? que inicializo em .task para ser igual ao valor, passo como scrollPosition e adiciono um OnTap no HStack para definir o valor e a nova variável Int?. Isso faz com que a rolagem e a posição inicial funcionem perfeitamente, mas não define mais os userDefaults.
Parece que o scroll simplesmente não está rolando para a posição inicial de scroll por algum motivo. Você pode rolar manualmente
onAppear
usando umScrollViewReader
.Se você usar um
HStack
with.scrollPosition(id:_)
, ele não saberá qual está selecionado em appear, já que a vinculação da posição de rolagem é inicialmentenil
. Para corrigir isso, você normalmente definiria um valor para a vinculação da posição de rolagem em appear, o que não é possível com a vinculação personalizada como você a usou.Para evitar outras dores de cabeça, eu simplesmente criaria um estado local e então o "sincronizaria" com a vinculação original:
Use-o para
.scrollPosition
:Em seguida, defina um valor para aparecer e atualize a ligação original sempre que ela mudar:
Quanto ao restante, veja o código completo abaixo sobre como ele poderia ser estruturado para evitar ter que usar largura e altura de quadro definidas e ter mais flexibilidade: