使用Date作为数据时,ScrollViewReader的scrollTo在onAppear时失效
SwiftUI ScrollView日期选择器滚动与初始化定位问题
问题描述
开发基于ScrollView的日期选择器,可选范围为今日前后15天,但遇到两个问题:
- 无法滚动到屏幕外的日期
- 初始化时无法自动滚动到今日(当前为3月25日)
尝试过DispatchQueue延迟、Task.sleep等方法均无效。补充:将数据改为Int类型时所有功能正常,推测是Date类型处理存在问题。
问题代码
import SwiftUI struct ContentView: View { private let dates: [Date] = { let today = Date() let calendar = Calendar.current return (0...30) .map { calendar.date(byAdding: .day, value: $0 - 15, to: today)! } }() @State var selectedDate: Date = Date() var body: some View { GeometryReader { geometry in ScrollViewReader { scrollValue in ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 8) { ForEach(dates, id: \.self) { date in DateCell(date: date, isSelected: Calendar.current.isDate(date, inSameDayAs: selectedDate)) .id(date) .onTapGesture { withAnimation { selectedDate = date } } } } .padding(.horizontal, geometry.size.width / 2) } .onChange(of: selectedDate, initial: true) { _, newValue in withAnimation { scrollValue.scrollTo(newValue, anchor: .center) } } } } } } struct DateCell: View { let date: Date var isSelected: Bool var body: some View { VStack { Text(date, format: .dateTime.weekday(.short)) Text(date, format: .dateTime.day()) .bold() } .frame(width: 50, height: 50) .background(isSelected ? Color.red : Color.gray.opacity(0.3)) .clipShape(Circle()) } } #Preview { ContentView() }
问题原因
问题出在Date的精度不匹配:
dates数组中的日期是基于初始Date()生成的,保留了时分秒信息- 初始化的
selectedDate = Date()是当前精确到秒的时间,和dates数组中对应今日的Date对象并非完全相等(哪怕是同一天,时间部分有差异) - ScrollViewReader的
scrollTo是通过完全匹配id来定位的,精度不一致导致无法找到目标视图
解决方案
统一所有Date的精度,只保留年月日部分,去掉时分秒:
修改后的代码
import SwiftUI extension Date { // 去除时分秒,仅保留年月日 func strippedOfTime() -> Date { let calendar = Calendar.current let components = calendar.dateComponents([.year, .month, .day], from: self) return calendar.date(from: components)! } } struct ContentView: View { private let dates: [Date] = { let today = Date().strippedOfTime() let calendar = Calendar.current return (0...30) .map { calendar.date(byAdding: .day, value: $0 - 15, to: today)! } }() @State var selectedDate: Date = Date().strippedOfTime() var body: some View { GeometryReader { geometry in ScrollViewReader { scrollValue in ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 8) { ForEach(dates, id: \.self) { date in DateCell(date: date, isSelected: Calendar.current.isDate(date, inSameDayAs: selectedDate)) .id(date) .onTapGesture { withAnimation { selectedDate = date } } } } .padding(.horizontal, geometry.size.width / 2) } .onChange(of: selectedDate, initial: true) { _, newValue in withAnimation { scrollValue.scrollTo(newValue, anchor: .center) } } } } } } struct DateCell: View { let date: Date var isSelected: Bool var body: some View { VStack { Text(date, format: .dateTime.weekday(.short)) Text(date, format: .dateTime.day()) .bold() } .frame(width: 50, height: 50) .background(isSelected ? Color.red : Color.gray.opacity(0.3)) .clipShape(Circle()) } } #Preview { ContentView() }
关键修改点
- 添加
strippedOfTime()方法,统一Date精度为年月日 - 生成
dates数组时,使用处理后的今日日期 - 初始化
selectedDate时,同样使用处理后的日期
这样所有Date对象的精度一致,scrollTo能精准匹配到对应的视图,初始化滚动和屏幕外滚动问题都能解决。
内容的提问来源于stack exchange,提问作者miltenkot
相关产品推荐
相关产品推荐

