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

Xcode13及iOS15/iPadOS15下SwiftUI Form单元格高度异常问题咨询

问题描述

此前在Xcode 12、iOS 14环境下开发Form表单功能,所有表现均符合预期。切换到Xcode 13、iOS 15及iPadOS 15环境后,出现以下异常:

  • 单元格高度不一致,滚动出屏幕外再返回时高度会发生变化,表现和可复用UITableViewCell一致
  • 部分Section单元格高度为0,但内部文本输入框悬浮在其他单元格上方
  • Picker视图设置.pickerStyle(InlinePickerStyle())后,单元格会被拉伸至超出iPad屏幕尺寸,无法像旧版本一样显示为滚轮样式

相关代码

表单视图代码

struct EvalViewMHx: View {
    var body: some View {
        VStack {
            Form {
                Label(
                    title: {
                        Text("Demographics & Medical History")
                            .font(.title)
                            .foregroundColor(.white)
                            .padding()
                    },
                    icon: {
                        Image(systemName: "heart.text.square.fill")
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                            .foregroundColor(.white)
                        
                    })
                    .listRowBackground(Color.blue)
                    .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
                
                Section(header: Text("Demographics")) {
                    demographicsView(nameText: $nameText, DOB: $DOB, height: $height, weight: $weight)
                }
                
                Section(header: Text("Medical History")) {
                    VStack {
                        HStack {
                            Text("When did you get hurt?")
                                .padding()
                            Spacer()
                            Toggle("", isOn: $doesNotKnowInjuryDate)
                                .frame(width: 35)
                                .padding(.horizontal)
                            Text("I don't know")
                                .fontWeight(!doesNotKnowInjuryDate ? .regular : .bold)
                                .padding(.leading)
                        }
                        if !doesNotKnowInjuryDate {
                            DatePicker("Injury Date", selection: $injuryDate, displayedComponents: [.date])
                                .foregroundColor(Color(UIColor.systemGray3))
                                .padding()
                        }
                    }
                    .frame(minWidth: .zero, idealWidth: .infinity, maxWidth: .infinity, minHeight: 150, idealHeight: 150, maxHeight: 150, alignment: .center)
                    
                    VStack(alignment: .leading) {
                        HStack {
                            Text("Where is your injury?")
                            Spacer()
                            Text("Right")
                                .fontWeight(isInjuryOnRightSide ? .regular : .bold)
                            Toggle("", isOn: $isInjuryOnRightSide)
                                .toggleStyle(SwitchToggleStyle(tint: Color(UIColor.systemGray5)))
                                .frame(width: 35)
                                .padding(.horizontal)
                            Text("Left")
                                .fontWeight(!isInjuryOnRightSide ? .regular : .bold)
                                .padding(.leading)
                        }
                        .zIndex(2.0)
                        
                        Picker(selection: $injuryLocation, label: Text(""), content: {
                            ForEach(injuryLocations, id: \.self) { location in
                                Text(location)
                            }
                        })
                        .pickerStyle(WheelPickerStyle())
                        .frame(minWidth: .zero, idealWidth: .infinity, maxWidth: .infinity, minHeight: 100, idealHeight: 100, maxHeight: 100, alignment: .center)
                        .zIndex(1.0)
                        .padding()
                    }
                    .padding()
                    
                    VStack(alignment: .leading) {
                        Text("How did you get hurt? (Method of Injury)")
                            .padding()
                        TextEditor(text: $MOI)
                            .frame(height: 150)
                            .background(Color(UIColor.systemGray5).opacity(0.75))
                            .cornerRadius(15)
                    }
                    .frame(minWidth: .zero, idealWidth: .infinity, maxWidth: .infinity, minHeight: 200, idealHeight: 200, maxHeight: 200, alignment: .center)
                    .padding()
                    
                    if injuryLocation != "Lower Back" && injuryLocation != "Upper Back" && injuryLocation != "Head / Neck" && injuryLocation != "Chest" {
                        VStack {
                            pastInjuryHistory(injuryLocation: $injuryLocation, hasHadSameSideInjury: $hasHadSameSideInjury, hasHadOppositeSideInjury: $hasHadOppositeSideInjury, isSameSide: true)
                            pastInjuryHistory(injuryLocation: $injuryLocation, hasHadSameSideInjury: $hasHadSameSideInjury, hasHadOppositeSideInjury: $hasHadOppositeSideInjury, isSameSide: false)
                        }
                    }
                }
            }
        }
    }
}

外层TabView嵌套代码

TabView(selection: $activeTab) {
    EvalViewMHx()
        .tag(1)
    EvalObjectiveView()
        .tag(2)
    viewThree(activeTab: $activeTab)
        .tag(3)
    viewFour(activeTab: $activeTab)
        .tag(4)
}
.tabViewStyle(PageTabViewStyle())
.onChange(of: activeTab, perform: { value in
    print(activeTab)
})
.animation(.default)

异常效果

异常效果截图

解决方案

以上属于iOS 15 SwiftUI的已知兼容性问题,无需等待官方修复,可通过以下方案落地解决:

  • 强制禁用Form的cell复用与自动高度估算行为,给Form添加如下修饰符:
    .onAppear {
        UITableView.appearance().estimatedRowHeight = 0
        UITableView.appearance().rowHeight = UITableView.automaticDimension
    }
    .onDisappear {
        // 页面消失时重置全局配置,避免影响其他页面
        UITableView.appearance().estimatedRowHeight = UITableView.automaticDimension
    }
    
    同时给每个Section内的根容器添加.fixedSize(horizontal: false, vertical: true)、.listRowInsets(EdgeInsets())修饰符,触发布局强制计算。
  • 修复Picker样式异常:给Picker添加.labelsHidden()、.compositingGroup()、.clipped()修饰符,强制约束布局范围,示例如下:
    Picker(selection: $injuryLocation, label: Text("")) {
        ForEach(injuryLocations, id: \.self) { location in
            Text(location)
        }
    }
    .pickerStyle(.wheel)
    .labelsHidden()
    .compositingGroup()
    .frame(height: 100)
    .clipped()
    
  • 解决单元格高度为0、内容悬浮问题:移除内容容器不必要的idealHeight/maxHeight限制,优先用minHeight配合fixedSize做自适应,同时给根容器添加.listRowBackground(Color(UIColor.systemBackground))强制触发行背景布局计算。
  • 修复TabView动画冲突:把全局的.animation(.default)修改为仅绑定activeTab变化的精准动画,避免不必要的列表重绘:
    .animation(.default, value: activeTab)
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 00:45:05