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

SwiftUI应用横竖屏切换时已选分类视图不显示问题求助

费用追踪App横屏模式已选分类不显示问题

问题背景

开发的费用追踪App支持用户选择分类记录费用,界面分两个区域:已选分类区(带图标和名称)、可用分类区。点击分类可在两个区域间切换。

现象:竖屏时两个区域分类都正常显示;切换横屏后,已选分类完全不显示。

实现逻辑

竖屏视图中两个区域上下堆叠,横屏时左右并排,横竖屏用不同函数绘制,但已选/可用分类的展示复用同一组件。横屏视图ShowLandCategories调用GetCurrentCategories组件展示已选分类,该组件同时用于竖屏视图。

已做排查

  • 移除GetCurrentCategories里的List容器后,横屏已选分类能正常显示,但List是自定义分类删除功能必需的
  • 在GetCurrentCategories的ForEach后断点,控制台能打印所有已选分类数据
  • 移除ShowLandCategories中的ZStack和Rectangle(),视图无变化

相关代码

横屏视图代码

struct ShowLandCategories: View {

    @State private var presentAlert = false
    @State private var presentDetail: String = ""

    @EnvironmentObject var categories: CategoryModel

    var g: GeometryProxy

    var body: some View {

        VStack {
            HStack {

                // 横屏左半区:已选分类
                VStack {
                    Text("Current Categories")
                        .font(.headline)
                        .frame(maxHeight: .infinity, alignment: .top)

                    ZStack {
                        Rectangle()
                            .foregroundColor(.white)
                            .frame(height: g.size.height * 0.65)
                            .border(Color("Dark Green"), width: 4)

                        ScrollView {
                            GetCurrentCategories(g: g, presentAlert: $presentAlert, presentDetail: $presentDetail)
                        }
                    }

                    ShwCurrentText()
                        .padding(.top, 3)
                        .frame(maxHeight: .infinity, alignment: .bottom)
                    Spacer()
                }

                // 横屏右半区:可用分类
                VStack {
                    Text("Available Categories")
                        .font(.headline)
                        .frame(maxHeight: .infinity, alignment: .top)

                    ZStack {
                        Rectangle()
                            .foregroundColor(.white)
                            .frame(height: g.size.height * 0.65)
                            .border(Color("Dark Green"), width: 4)
                        
                        ScrollView {
                            GetAvailableCats(g: g)
                        }
                    }
                    ShwAvailableText()
                        .padding(.top, 3)
                        .frame(maxHeight: .infinity, alignment: .bottom)
                    Spacer()

                    // ... 弹窗和工具栏相关代码
                }
            }
         }
      } 
   }

已选分类展示组件代码

struct GetCurrentCategories: View {

    var g: GeometryProxy
    @Binding var presentAlert: Bool
    @Binding var presentDetail: String

    @EnvironmentObject var categories: CategoryModel

    var columns: [GridItem] = [
        GridItem(.fixed(55)),
        GridItem(.fixed(145))
    ]

    var body: some View {

        VStack (alignment: .leading) {
            List {
                ForEach($categories.catItem.filter{ $0.catInUse.wrappedValue }) { $item in
                    Button {

                        // 余额为0且不是默认分类才能移除
                        if item.catTotal == 0.0 && item.catNum != 0 {

                            // 非自定义分类
                            if item.catCustom == false {   
                                withAnimation {
                                    item.catInUse = false
                                }
                            }
                        } else {
                            presentAlert = true
                            presentDetail = item.catName
                        }
                    } label: {
                        LazyVGrid(
                            columns: columns,
                            alignment: .leading,
                            spacing: 6
                        ) {
                            Image(systemName: item.catPix)
                                .resizable()
                                .foregroundStyle(Color(colors[item.catNum % colors.count]))
                                .frame(width: 25, height: 25)

                            Text(item.catName)
                                .padding(.horizontal, 8)
                                .background(item.catTotal != 0.0 ? Color(.systemRed) : Color("Dark Green"))
                                .foregroundStyle(Color.white)
                                .cornerRadius(8)
                        }
                        .frame(height: 33)
                        .padding(.leading, g.size.width * 0.04)

                    }.deleteDisabled(item.catTotal != 0.0)

                }.onDelete(perform: deleteCurCat)  
            } 
        }
        // ... deleteCurCat() 实现未展示
    }

问题原因

核心问题是布局嵌套冲突:

  1. GetCurrentCategories里的List被嵌套在ShowLandCategories的ScrollView中,而List本身自带滚动功能,双重滚动容器会导致SwiftUI的布局优先级混乱,横屏时List无法正确计算自身高度,最终被挤压成0高度,内容不可见。
  2. 固定宽度的GridItem在横屏左侧窄空间里,可能导致内容溢出,进一步加剧布局异常。

解决方案

1. 移除冗余的ScrollView嵌套

List自带滚动能力,不需要在外层再套ScrollView,直接将GetCurrentCategories放在ZStack中即可。同时给List设置明确高度,和外层容器匹配。

修改ShowLandCategories中的已选分类区域代码:

ZStack {
    Rectangle()
        .foregroundColor(.white)
        .frame(height: g.size.height * 0.65)
        .border(Color("Dark Green"), width: 4)

    // 移除外层ScrollView,直接放置组件并设置高度
    GetCurrentCategories(g: g, presentAlert: $presentAlert, presentDetail: $presentDetail)
        .frame(height: g.size.height * 0.65)
}

2. 调整GridItem为自适应宽度

横屏左侧区域宽度有限,固定宽度的GridItem容易溢出,改用自适应宽度适配不同屏幕:

修改GetCurrentCategories中的columns定义:

var columns: [GridItem] {
    [
        GridItem(.flexible(minimum: 40, maximum: 60)), // 图标列自适应
        GridItem(.flexible(minimum: 100, maximum: .infinity)) // 名称列占剩余空间
    ]
}

3. 优化List样式与布局

去掉多余的外层VStack,设置List为plain样式减少默认内边距,让内容更贴合容器:

修改GetCurrentCategories的body部分:

var body: some View {
    // 移除外层VStack,直接返回List
    List {
        ForEach($categories.catItem.filter{ $0.catInUse.wrappedValue }) { $item in
            Button {
                // 原按钮逻辑不变
            } label: {
                LazyVGrid(
                    columns: columns,
                    alignment: .leading,
                    spacing: 6
                ) {
                    // 原图标和文本布局不变
                }
                .frame(height: 33)
                .padding(.leading, g.size.width * 0.04)
            }.deleteDisabled(item.catTotal != 0.0)
        }.onDelete(perform: deleteCurCat)
    }
    .listStyle(.plain) // 采用简洁列表样式,减少默认内边距
    .padding(.vertical, 8) // 增加少量上下内边距,避免内容贴边
}

修改后效果

横屏模式下,已选分类能正常显示,同时保留List的删除功能;自适应布局能适配不同横屏尺寸,避免内容溢出或挤压。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 08:22:04