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

SwiftUI实现可点击横向日历:选中日期联动下方卡片

问题描述

需要实现如下布局效果:

  • 顶部固定展示7天日期,当日始终处于中间位置
  • 支持点击任意日期,点击后下方卡片切换为对应日期的描述内容,实现日期与内容的关联

目前已完成基础日期布局,但不清楚如何通过@State或类似状态变量实现点击交互,之前尝试在ZStack中使用@State未成功。现有代码如下:

import SwiftUI

struct DayPlanView: View {
    @State var monthString: String = "Not Set"

    let calendar = Calendar.current
     

    var body: some View {
        let dates = getWeek()
        
        VStack {
            // MARK: Change Date() to some identifier of selected date.
            HStack {
                Text("\(getDayNumber(date: Date()))")
                    .font(.title2)
                    .fontWeight(.semibold)
                Text("of")
                    .font(.title2)
                    .fontWeight(.semibold)
                Text(getMonth(date: Date()))
                    .font(.title2)
                    .fontWeight(.semibold)
            }
            
            HStack {
                ForEach(dates, id: \.self) { day in
                    VStack {


                        let dayNumber = getDayNumber(date: day)
                        
                        if dayNumber == getDayNumber(date: Date()) {
                            ZStack {
                                Circle()
                                    .fill(.blue)
                                    .scaleEffect(1.3)
                                
                                Text("\(getDayNumber(date: day))")
                                    .font(.title)
                                    .fontWeight(.semibold)
                                    .foregroundColor(.white)
                            }
                        } else {
                            Text("\(getDayNumber(date: day))")
                               .font(.title)
                        }
                    }
                    .frame(width: getWidth() / 8, height: getHeight() / 20)
                    .padding(.horizontal, -3)
               }
            }
           .frame(width: getWidth())
            Spacer()
        }
        .padding(.vertical, getHeight() / 20)
    }
    
    func getMonth(date: Date) -> String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "LLLL"
        return dateFormatter.string(from: date)
    }

    func getDayShort(date: Date) -> String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "E"
        return dateFormatter.string(from: date)
    }
    
    func getDayNumber(date: Date) -> Int {
        let calendar = Calendar.current
        let components = calendar.dateComponents([.day], from: date)
        return components.day ?? 0
    }

    func getWeek() -> [Date] {
        let currentDate = Date()

        let calendar = Calendar.current
        let dayOfWeek = calendar.component(.weekday, from: currentDate)
        
        let range = calendar.range(of: .day, in: .month, for: currentDate)!
        
        let daysMonth = (range.lowerBound - 1 ..< range.lowerBound + 6)
            .compactMap { calendar.date(byAdding: .day, value: $0 - dayOfWeek, to: currentDate) }
        
        return daysMonth
    }
}

struct DayPlanView_Previews: PreviewProvider {
    static var previews: some View {
        DayPlanView()
    }
}

当前界面截图:开发中的界面


解决方案

要实现点击日期切换内容的功能,核心是用@State变量追踪用户选中的日期,通过状态变化驱动UI更新。以下是修改后的完整代码,关键改动已标注:

import SwiftUI

struct DayPlanView: View {
    // 追踪选中的日期,默认设为当前日期
    @State private var selectedDate: Date = Date()
    @State var monthString: String = "Not Set"

    let calendar = Calendar.current
     

    var body: some View {
        let dates = getWeek()
        
        VStack {
            // 显示选中日期的日和月,替代原固定的当前日期
            HStack {
                Text("\(getDayNumber(date: selectedDate))")
                    .font(.title2)
                    .fontWeight(.semibold)
                Text("of")
                    .font(.title2)
                    .fontWeight(.semibold)
                Text(getMonth(date: selectedDate))
                    .font(.title2)
                    .fontWeight(.semibold)
            }
            
            HStack {
                ForEach(dates, id: \.self) { day in
                    VStack {
                        let dayNumber = getDayNumber(date: day)
                        // 同时判断是否为当日或选中日期,确保两种状态都高亮
                        let isHighlighted = isSameDay(day, Date()) || isSameDay(day, selectedDate)
                        
                        if isHighlighted {
                            ZStack {
                                Circle()
                                    .fill(.blue)
                                    .scaleEffect(1.3)
                                
                                Text("\(dayNumber)")
                                    .font(.title)
                                    .fontWeight(.semibold)
                                    .foregroundColor(.white)
                            }
                        } else {
                            Text("\(dayNumber)")
                               .font(.title)
                        }
                    }
                    .frame(width: UIScreen.main.bounds.width / 8, height: UIScreen.main.bounds.height / 20)
                    .padding(.horizontal, -3)
                    // 添加点击手势,更新选中日期
                    .onTapGesture {
                        selectedDate = day
                    }
               }
            }
           .frame(width: UIScreen.main.bounds.width)
            
            // 下方内容区域:根据选中日期展示对应描述
            VStack(spacing: 16) {
                Text("当日计划")
                    .font(.title)
                    .fontWeight(.bold)
                Text(getPlanForDate(selectedDate))
                    .font(.body)
                    .padding()
                    .background(Color.gray.opacity(0.1))
                    .cornerRadius(12)
            }
            .padding(.horizontal)
            Spacer()
        }
        .padding(.vertical, UIScreen.main.bounds.height / 20)
    }
    
    // 判断两个日期是否为同一天(避免时间部分干扰判断)
    private func isSameDay(_ date1: Date, _ date2: Date) -> Bool {
        return calendar.isDate(date1, inSameDayAs: date2)
    }
    
    // 模拟根据日期返回对应计划的方法,实际可替换为你的数据源
    private func getPlanForDate(_ date: Date) -> String {
        let dayNumber = getDayNumber(date: date)
        return "今天是\(getMonth(date: date))\(dayNumber)日,你的计划是:完成SwiftUI布局优化,整理项目文档,下午参加团队会议。"
    }
    
    func getMonth(date: Date) -> String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "LLLL"
        dateFormatter.locale = Locale(identifier: "zh_CN") // 显示中文月份
        return dateFormatter.string(from: date)
    }

    func getDayShort(date: Date) -> String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "E"
        dateFormatter.locale = Locale(identifier: "zh_CN") // 显示中文星期
        return dateFormatter.string(from: date)
    }
    
    func getDayNumber(date: Date) -> Int {
        let components = calendar.dateComponents([.day], from: date)
        return components.day ?? 0
    }

    func getWeek() -> [Date] {
        let currentDate = Date()
        // 确保当日位于7天列表的中间位置(第4位)
        let startOfWeek = calendar.date(from: calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: currentDate))!
        var days = [Date]()
        // 从当前周的前3天到后3天,共7天
        for i in -3...3 {
            if let date = calendar.date(byAdding: .day, value: i, to: startOfWeek) {
                days.append(date)
            }
        }
        return days
    }
}

struct DayPlanView_Previews: PreviewProvider {
    static var previews: some View {
        DayPlanView()
    }
}

核心改动说明

  • 状态变量追踪选中日期:新增@State private var selectedDate,用于记录用户点击的日期,状态变化会自动触发UI更新
  • 添加点击交互:给每个日期的容器添加.onTapGesture,点击时更新selectedDate
  • UI与状态关联:
    • 顶部标题改为展示selectedDate的信息
    • 日期高亮逻辑同时覆盖「当日」和「选中日期」,用户点击后对应日期会显示蓝色圆圈
  • 内容切换实现:新增下方内容区域,通过getPlanForDate方法根据选中日期返回对应描述,实现日期与内容的绑定
  • 日期逻辑优化:修改getWeek方法确保当日始终在7天列表中间;新增isSameDay方法,避免因日期的时间部分导致判断错误
  • 本地化适配:给日期格式化器添加中文区域设置,显示中文月份和星期

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 21:32:01