SwiftUI中基于起始锚点的像素级滚动偏移实现求助
SwiftUI实现滑块控制ScrollView精准滚动的问题
需求
需要实现通过滑块控制ScrollView的内容滚动(内容可为任意视图),但ScrollViewReader仅提供scrollTo(_ id: Hashable [, anchor: UnitPoint])方法,无法直接使用UIScrollView.setContentOffset实现精准偏移控制。计划通过滑块输出0-1的数值,结合lerp方法计算对应起始锚点的滚动偏移,lerp的基础实现如下:
extension Range where Bound: BinaryFloatingPoint { func lerp(_ t: Bound) -> Bound { return (1-t)*self.lowerBound + t*self.upperBound } } extension ClosedRange where Bound: BinaryFloatingPoint { func lerp(_ t: Bound) -> Bound { return (1-t)*self.lowerBound + t*self.upperBound } }
尝试的方案(不使用Introspect或UIViewRepresentable)
为了绕过ScrollViewReader的限制,尝试了以下步骤:
- 为ScrollView内容添加占满全屏且左对齐的overlay
- 在overlay中添加带
contentShape的矩形,用@State变量控制其相对于ScrollView内容起始锚点的位置,并设置固定id - 滚动时先更新锚点到目标偏移,再调用
scrollProxy.scrollTo(scrollAnchor)
但该方案未生效,滚动到的偏移无规律,即使延迟调用scrollTo也没有改善。
基础实现代码
主视图代码
struct ContentView: View { @State private var inputValue: String = "" @State private var contentOffset: CGFloat = .zero { didSet { self.updateOffset("scrollAnchor") } } @State private var updateOffset: (String) -> Void = { _ in } var body: some View { VStack { ScrollViewReader { scrollProxy in ScrollView(.horizontal, showsIndicators: true) { Rectangle() .fill(Colors.teal500) .frame(width: 1000, height: 100) .overlay( ZStack { ForEach(1..<40) { i in VStack(alignment: .leading) { Rectangle() .fill(Colors.white) .frame(width: 1, height: 10) Text("\(i*25)") .font(.caption) } .offset(x: CGFloat(i*25)) } } .fillMaxWidth(alignment: .leading) ,alignment: .topLeading) .overlay( ZStack { Rectangle() .fill(Color.red) .contentShape(Rectangle()) .frame(width: 1, height: 100) .position(x: self.contentOffset, y: 50) .onAppear { self.updateOffset = { id in DispatchQueue.main.asyncAfter(deadline: .now() + 2) { scrollProxy.scrollTo(id) } } } .id("scrollAnchor") } .fillMaxWidth(alignment: .leading) , alignment: .leading ) } } TextField( "Scroll offset:", text: self.$inputValue ) .keyboardType(.decimalPad) Button(action: { self.contentOffset = CGFloat(Int(inputValue) ?? 0) }) { Text("Scroll to input offset") } } .fillMaxSize(alignment: .leading) } }
辅助视图扩展代码
其中Colors.teal500是#319795颜色的语法糖,fillMaxWidth()等方法的实现如下:
struct FillMaxSize: ViewModifier { private var alignment: Alignment? init(alignment: Alignment?) { self.alignment = alignment } func body(content: Content) -> some View { if self.alignment != nil { return content .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: self.alignment!) } else { return content .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity) } } } struct FillMaxWidth: ViewModifier { private var alignment: Alignment? init(alignment: Alignment?) { self.alignment = alignment } func body(content: Content) -> some View { if self.alignment != nil { return content .frame(minWidth: 0, maxWidth: .infinity, alignment: self.alignment!) } else { return content .frame(minWidth: 0, maxWidth: .infinity) } } } struct FillMaxHeight: ViewModifier { private var alignment: Alignment? init(alignment: Alignment?) { self.alignment = alignment } func body(content: Content) -> some View { if self.alignment != nil { return content .frame(minHeight: 0, maxHeight: .infinity, alignment: self.alignment!) } else { return content .frame(minHeight: 0, maxHeight: .infinity) } } } extension View { func fillMaxSize(alignment: Alignment? = nil) -> some View { return self.modifier(FillMaxSize(alignment: alignment)) } func fillMaxWidth(alignment: Alignment? = nil) -> some View { return self.modifier(FillMaxWidth(alignment: alignment)) } func fillMaxHeight(alignment: Alignment? = nil) -> some View { return self.modifier(FillMaxHeight(alignment: alignment)) } }
求助需求
需要修正现有实现的问题,或者提供可行的实现思路。
内容的提问来源于stack exchange,提问作者NDR
相关产品推荐
相关产品推荐

