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

如何让SwiftUI ScrollView默认收起可折叠日历视图

默认收起的可折叠日历视图实现方案

方案一:用ScrollViewReader直接定位(推荐,无需计算高度)

不用纠结ScrollView总高度,给日历容器加锚点ID,视图加载时直接滚动到折叠状态的位置,同时默认将日历设为收起:

struct ContentView: View {
    // 默认收起日历
    @State private var isCalendarCollapsed = true
    // 自定义日历展开/收起的高度
    let expandedHeight: CGFloat = 320
    let collapsedHeight: CGFloat = 60
    
    var body: some View {
        ScrollViewReader { proxy in
            ScrollView {
                // 日历容器
                VStack(spacing: 0) {
                    // 顶部触发栏(点击切换展开/收起)
                    HStack {
                        Text("选择日期")
                            .font(.headline)
                        Spacer()
                        Image(systemName: isCalendarCollapsed ? "chevron.down" : "chevron.up")
                    }
                    .padding()
                    .onTapGesture {
                        withAnimation(.easeInOut) {
                            isCalendarCollapsed.toggle()
                        }
                    }
                    
                    // 日历内容(展开时显示)
                    if !isCalendarCollapsed {
                        CalendarView()
                            .frame(height: expandedHeight - collapsedHeight)
                    }
                }
                .frame(height: isCalendarCollapsed ? collapsedHeight : expandedHeight)
                .background(Color.gray.opacity(0.1))
                .id("calendarCollapsedAnchor") // 锚点标记
                
                // 下方数据展示区
                VStack(spacing: 12) {
                    ForEach(1...25, id: \.self) { index in
                        Text("当日数据 \(index)")
                            .padding()
                            .frame(maxWidth: .infinity)
                            .background(Color.white)
                            .cornerRadius(8)
                            .padding(.horizontal)
                    }
                }
            }
            .onAppear {
                // 初始滚动到日历锚点,实现默认收起的视觉效果
                withAnimation(.easeInOut) {
                    proxy.scrollTo("calendarCollapsedAnchor", anchor: .top)
                }
            }
            // 监听滚动偏移,自动展开/收起(iOS 17+)
            .onScrollGeometryChange { geometry in
                let offset = geometry.contentOffset.y
                // 下滑到一定程度(offset为负)时展开日历
                withAnimation(.easeInOut) {
                    isCalendarCollapsed = offset > -60
                }
            }
        }
    }
}

// 你的自定义日历视图
struct CalendarView: View {
    var body: some View {
        // 替换成你的日历实现(比如DatePicker、自定义日历组件)
        Text("日历内容区域")
            .frame(maxWidth: .infinity)
            .background(Color.blue.opacity(0.2))
    }
}

方案二:通过GeometryReader获取总高度适配defaultScrollAnchor

如果坚持要用defaultScrollAnchor,可以用GeometryReader包裹滚动内容,获取总高度后计算锚点位置:

struct ContentView: View {
    @State private var isCalendarCollapsed = true
    let expandedHeight: CGFloat = 320
    let collapsedHeight: CGFloat = 60
    @State private var totalScrollHeight: CGFloat = 0
    
    var body: some View {
        ScrollView {
            GeometryReader { geo in
                VStack(spacing: 0) {
                    // 日历容器(同方案一)
                    VStack(spacing: 0) {
                        HStack {
                            Text("选择日期")
                                .font(.headline)
                            Spacer()
                            Image(systemName: isCalendarCollapsed ? "chevron.down" : "chevron.up")
                        }
                        .padding()
                        .onTapGesture {
                            withAnimation(.easeInOut) {
                                isCalendarCollapsed.toggle()
                            }
                        }
                        
                        if !isCalendarCollapsed {
                            CalendarView()
                                .frame(height: expandedHeight - collapsedHeight)
                        }
                    }
                    .frame(height: isCalendarCollapsed ? collapsedHeight : expandedHeight)
                    .background(Color.gray.opacity(0.1))
                    
                    // 数据展示区
                    VStack(spacing: 12) {
                        ForEach(1...25, id: \.self) { index in
                            Text("当日数据 \(index)")
                                .padding()
                                .frame(maxWidth: .infinity)
                                .background(Color.white)
                                .cornerRadius(8)
                                .padding(.horizontal)
                        }
                    }
                }
                .onAppear {
                    // 获取滚动内容的总高度
                    totalScrollHeight = geo.size.height
                }
            }
        }
        // 计算锚点:滚动到内容顶部下方(总高度 - 日历收起高度)的位置
        .defaultScrollAnchor(totalScrollHeight > 0 ? UnitPoint(x: 0, y: (totalScrollHeight - collapsedHeight)/totalScrollHeight) : .top)
        // 低版本iOS滚动监听(替换iOS17+的onScrollGeometryChange)
        .coordinateSpace(name: "scrollSpace")
        .overlay(
            GeometryReader { geo in
                Color.clear
                    .preference(key: ScrollOffsetKey.self, value: geo.frame(in: .named("scrollSpace")).origin.y)
            }
        )
        .onPreferenceChange(ScrollOffsetKey.self) { offset in
            withAnimation(.easeInOut) {
                isCalendarCollapsed = offset > -60
            }
        }
    }
}

// 自定义偏好键(低版本iOS监听滚动偏移用)
struct ScrollOffsetKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {}
}

关键注意点

  • 两个方案都将isCalendarCollapsed初始值设为true,确保日历默认收起
  • 方案一无需计算高度,逻辑更简洁,兼容性更好
  • 若需兼容iOS16及以下,用方案二中的偏好键方式监听滚动偏移,替换iOS17+的onScrollGeometryChange

内容的提问来源于stack exchange,提问作者Thiến Bảo Lê

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 04:47:16