You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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()
}

关键修改点

  1. 添加strippedOfTime()方法,统一Date精度为年月日
  2. 生成dates数组时,使用处理后的今日日期
  3. 初始化selectedDate时,同样使用处理后的日期

这样所有Date对象的精度一致,scrollTo能精准匹配到对应的视图,初始化滚动和屏幕外滚动问题都能解决。

内容的提问来源于stack exchange,提问作者miltenkot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 18:23:15