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

复刻macOS日历横向滚动:固定7天显示的滚动对齐问题求助

复刻macOS日历横向滚动问题:强制对齐7天边界

我正在复刻苹果macOS日历,需要实现横向无限滚动且始终固定显示7天,不允许用户停在任意滚动位置。目前尝试用ScrollViewReader和滚动位置绑定来实现,但效果不稳定——偶尔生效但无动画,多数时候还是能滚动到任意位置。

当前实现效果:
实际日历效果

我的代码如下:

@State private var loadedDays = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun","Mon1", "Tue1", "Wed1", "Thu1", "Fri1", "Sat1", "Sun1"]
@State private var scrollPos: String?

var body: some View {
    GeometryReader { geometry in
        Text("\(scrollPos ?? "nil")")
        ScrollViewReader { proxy in
            ScrollView (.horizontal) {
                LazyHStack(spacing: 10) {
                    ForEach($loadedDays, id: \.self) { day in
                        HStack {
                            Rectangle()
                                .frame(width: 5, height: 5, alignment: .leading)
                                .id("r" + day.wrappedValue)
                            VStack {
                                Text(day.wrappedValue)
                                
                            }
                            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
                            .id(day.wrappedValue)
                            
                            Rectangle()
                                .frame(width: 5, height: 5, alignment: .trailing)
                                .id("l" + day.wrappedValue)
                        }.border().frame(width: geometry.size.width/7)

                    }
                }
                .padding()
                .scrollTargetLayout()
            }
            .defaultScrollAnchor(.center)
            .onChange(of: scrollPos) { old, new in
                if(new != nil && !new!.starts(with: "r")) {
                    withAnimation {
                        proxy.scrollTo("r" + new!, anchor: .leading)
                    }
                }
            }
        }
        .scrollPosition(id: $scrollPos)

    }
}

解决方案:使用滚动目标对齐替代手动滚动修正

核心问题是依赖scrollPosition的onChange手动修正位置,延迟高且不稳定。应该利用SwiftUI的滚动目标自动对齐特性,直接让滚动视图锁定到每个日期单元格的起始位置,确保每次滚动停在七天边界上。

修改步骤

  1. 简化ID绑定:仅给每个日期容器设置唯一ID,移除冗余的前后矩形ID
  2. 配置滚动目标行为:启用.viewAligned让滚动视图自动对齐到目标视图边界
  3. 修正滚动锚点:将defaultScrollAnchor设为.leading,确保对齐单元格起始位置
  4. 调整布局逻辑:保持单元格宽度严格为屏幕1/7,确保7天刚好填满屏幕

修改后代码

@State private var loadedDays = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun","Mon1", "Tue1", "Wed1", "Thu1", "Fri1", "Sat1", "Sun1"]
@State private var scrollPos: String?

var body: some View {
    GeometryReader { geometry in
        Text("\(scrollPos ?? "nil")")
        ScrollViewReader { proxy in
            ScrollView(.horizontal) {
                LazyHStack(spacing: 10) {
                    ForEach(loadedDays, id: \.self) { day in
                        VStack {
                            Text(day)
                        }
                        .frame(width: geometry.size.width/7, maxHeight: .infinity)
                        .border()
                        .id(day) // 仅保留容器ID
                    }
                }
                .padding()
                .scrollTargetLayout()
            }
            // 启用视图对齐的滚动目标行为
            .scrollTargetBehavior(.viewAligned)
            // 设置默认锚点为左侧,确保对齐单元格起始位置
            .defaultScrollAnchor(.leading)
            // 绑定滚动位置到单元格ID
            .scrollPosition(id: $scrollPos)
        }
    }
}

关键改进说明

  • .scrollTargetBehavior(.viewAligned):让滚动视图自动将每个标记为滚动目标的视图对齐到容器边缘,无需手动调用scrollTo
  • 移除冗余逻辑:之前的前后矩形ID和修正逻辑会干扰自动对齐,简化后更可靠
  • 自动动画:滚动目标对齐自带平滑过渡动画,无需额外withAnimation包裹
  • 无限滚动扩展:若需无限滚动,可在loadedDays滚动到边缘时动态添加前后日期数据,保持滚动连续性

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 00:40:15