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

SwiftUI使用.overlay()修饰符导致卡片数据重复显示异常

问题根因

这不是系统bug,是代码逻辑存在两个核心错误:

  • 每个TopActivityCardView实例内部都通过@StateObject var vm = ViewModel()创建了独立的ViewModel实例,且视图内部自带ScrollView+ForEach(vm.fetch())遍历全量接口数据,等于生成6张卡片时,每一张卡片都把所有6条活动数据完整渲染了一遍,多层内容堆叠后最上层的内容挡住了下层,视觉上就表现为所有卡片数据完全一致。
  • 父子视图各自初始化独立的ViewModel实例,没有实现单一数据源,之前更换@EnvironmentObject/@ObservedObject/@StateObject没有效果,是因为始终没有给子视图传入对应单条卡片的数据,还是让子视图自己拉取全量数据渲染。
修复方案

按以下三点调整即可:

  1. 只在最上层父视图初始化一次ViewModel,作为全局唯一数据源,禁止子视图自行初始化ViewModel
  2. 改造TopActivityCardView,移除内部的全量数据遍历逻辑,改为接收单个活动条目作为入参,只负责渲染单张卡片的内容
  3. 调整布局层级,用ZStack层叠实现UI效果,替代容易出现内容堆叠的多层.overlay()嵌套,移除子视图内部多余的ScrollView避免内容错位。

修正后的代码

TopActivityCardView 改造

import SwiftUI

//  TopActivityCardView 只负责单张卡片内容展示,不持有数据源
struct TopActivityCardView: View {
    // 入参:单条活动数据,由父视图传入
    let item: ActivityItem
    
    var body: some View {
        RoundedRectangle(cornerRadius: 20)
            .foregroundColor(Color.darkBlue)
            .frame(height: 120, alignment: .center)
            .overlay(
                ActivityTitleView(activityTitle: item.title)
            )
            .overlay(
                ActivityHoursView(workHours: item.timeframes.weekly.current)
            )
            .overlay(
                ActivityEllipisView()
            )
            .overlay(
                TimePeriodView(
                    timePeriod: "Last Week",
                    weeklyHrs: item.timeframes.weekly.previous
                )
            )
    }
}

struct TopActivityCardView_Previews: PreviewProvider {
    static var previews: some View {
        // 预览时传入测试数据即可
        TopActivityCardView(item: ActivityItem(
            title: "Work",
            timeframes: TimeFrame(weekly: (current: 32, previous: 36)),
            category: "work"
        ))
    }
}

RectCardView 改造

import SwiftUI

struct RectCardView: View {
    // 唯一数据源:只在这里初始化一次ViewModel
    @StateObject var vm = ViewModel()
    
    // 卡片配置:颜色、图标、对应的数据分类标识
    private let cardConfigs: [(color: Color, icon: String, category: String)] = [
        (Color.work, "icon-work", "work"),
        (Color.play, "icon-play", "play"),
        (Color.study, "icon-study", "study"),
        (Color.exercise, "icon-exercise", "exercise"),
        (Color.social, "icon-social", "social"),
        (Color.selfCare, "icon-self-care", "selfCare")
    ]
    
    var body: some View {
        ScrollView {
            VStack(spacing: 20) {
                ForEach(cardConfigs, id: \.category) { config in
                    // 匹配当前卡片对应的单条活动数据
                    if let matchedItem = vm.fetch().first(where: { $0.category == config.category }) {
                        ZStack(alignment: .top) {
                            // 背景层:彩色底+右上角图标
                            RoundedRectangle(cornerRadius: 20)
                                .foregroundColor(config.color)
                                .frame(height: 110)
                                .overlay(
                                    Image(config.icon)
                                        .brightness(-0.2)
                                        .frame(width: 60, height: 20, alignment: .topTrailing)
                                        .offset(x: -16, y: -10)
                                )
                                .clipped()
                            
                            // 内容层:深色内容卡片,用offset实现设计稿的错位效果
                            TopActivityCardView(item: matchedItem)
                                .offset(y: 40)
                        }
                        .frame(height: 160)
                        .clipped()
                    }
                }
            }
            .padding(.horizontal)
        }
    }
}

struct RectCardView_Previews: PreviewProvider {
    static var previews: some View {
        RectCardView()
    }
}

注:上述代码中用到的ActivityItem、TimeFrame是和接口返回字段对齐的数据模型,如果你已有对应模型,直接替换成自己的模型即可,只要保证能通过分类标识匹配到对应卡片的数据。

避坑提示
  • 不要在ForEach循环内部创建@StateObject实例,会导致SwiftUI复用视图时出现状态错乱、数据不一致问题
  • 纯展示类的子视图不要持有ViewModel,直接接收父视图传入的展示数据即可,保证整个页面只有一个数据源
  • 复杂层叠布局优先用ZStack实现,不要无限制嵌套.overlay(),否则很容易出现布局层级不可控、内容堆叠遮挡的问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 23:15:11