SwiftUI中.viewAligned滚动目标行为导致离散Scrubber端点值异常的问题咨询
SwiftUI中.viewAligned滚动目标行为导致离散Scrubber端点值异常的问题咨询
我最近基于SwiftUI的ScrollView实现了一个垂直离散Scrubber,核心功能正常,但在处理端点值时出现了几个异常情况:
- 直接滚动到底部时,控件显示的值为87,而非预期的100
- 通过点击
+按钮逐步递增到端点时,能正确显示100 - 点击
-按钮调整值时,Scrubber不会立即滚动到对应位置,需要连续点击两次才会跳转
我怀疑问题出在.viewAligned的滚动目标行为上,但无法定位具体的逻辑漏洞。以下是完整的实现代码:
import SwiftUI struct ScrubberConfig { let count: Int let steps: Int let spacing: CGFloat } struct VerticalScrubber: View { var config: ScrubberConfig @Binding var value: CGFloat @State private var scrollPosition: Int? var body: some View { GeometryReader { geometry in let verticalPadding = geometry.size.height / 2 - 8 ZStack(alignment: .trailing) { ScrollView(.vertical, showsIndicators: false) { VStack(spacing: config.spacing) { ForEach(0...(config.steps * config.count), id: \.self) { index in horizontalTickMark(for: index) .id(index) } } .frame(width: 80) .scrollTargetLayout() .safeAreaPadding(.vertical, verticalPadding) } .scrollTargetBehavior(.viewAligned) .scrollPosition(id: $scrollPosition, anchor: .top) Capsule() .frame(width: 32, height: 3) .foregroundColor(.accentColor) .shadow(color: .accentColor.opacity(0.3), radius: 3, x: 0, y: 1) } .frame(width: 100) .onAppear { DispatchQueue.main.async { scrollPosition = Int(value * CGFloat(config.steps)) } } .onChange(of: value, { oldValue, newValue in let newIndex = Int(newValue * CGFloat(config.steps)) print("New index \(newIndex)") if scrollPosition != newIndex { withAnimation { scrollPosition = newIndex print("\(scrollPosition)") } } }) .onChange(of: scrollPosition, { oldIndex, newIndex in guard let pos = newIndex else { return } let newValue = CGFloat(pos) / CGFloat(config.steps) if abs(value - newValue) > 0.001 { value = newValue } }) } } private func horizontalTickMark(for index: Int) -> some View { let isMajorTick = index % config.steps == 0 let tickValue = index / config.steps return HStack(spacing: 8) { Rectangle() .fill(isMajorTick ? Color.accentColor : Color.gray.opacity(0.5)) .frame(width: isMajorTick ? 24 : 12, height: isMajorTick ? 2 : 1) if isMajorTick { Text("\(tickValue * 5)") .font(.system(size: 12, weight: .medium)) .foregroundColor(.primary) .fixedSize() } } .frame(maxWidth: .infinity, alignment: .trailing) .padding(.trailing, 8) } } #Preview("Vertical Scrubber") { struct VerticalScrubberPreview: View { @State private var value: CGFloat = 0 private let config = ScrubberConfig(count: 20, steps: 5, spacing: 8) var body: some View { VStack { Text("Vertical Scrubber (0–100 in steps of 5)") .font(.title2) .padding() HStack(spacing: 30) { VerticalScrubber(config: config, value: $value) .frame(width: 120, height: 300) .background(Color(.systemBackground)) .border(Color.gray.opacity(0.3)) VStack { Text("Current Value:") .font(.headline) Text("\(value * 5, specifier: "%.0f")") .font(.system(size: 36, weight: .bold)) .padding() HStack { Button("−5") { let newValue = max(0, value - 1) if value != newValue { value = newValue UISelectionFeedbackGenerator().selectionChanged() } print("Value \(newValue), \(value)") } .disabled(value <= 0) Button("+5") { let newValue = min(CGFloat(config.count), value + 1) if value != newValue { value = newValue UISelectionFeedbackGenerator().selectionChanged() } print("Value \(newValue), \(value)") } .disabled(value >= CGFloat(config.count)) } .buttonStyle(.bordered) } } Spacer() } .padding() } } return VerticalScrubberPreview() }
我尝试过在onAppear和onChange中同步scrollPosition与value的关联,但端点的异常问题依然存在。有没有人能帮我分析下.viewAligned在这里的具体作用逻辑,以及如何修复这些端点行为问题?
内容来源于stack exchange
相关产品推荐
相关产品推荐

