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

SwiftUI实现单菜单展开时自动折叠其他菜单技术问询

SwiftUI 实现单一展开的可折叠菜单

问题说明

当前的可折叠菜单各自独立维护展开状态,存在以下问题:

  • 初始状态下Property Information菜单展开
  • 用户可同时展开Property Information和Financials两个菜单,导致界面需要滚动才能查看全部内容

需要实现每次仅能展开一个菜单,其余自动折叠的功能,以最大化利用屏幕空间。

解决方案思路

  1. 合并重复组件:删除原有的Collapsible_main和Collapsible_other,创建通用的Collapsible组件,通过外部状态控制展开/折叠。
  2. 统一状态管理:在父视图AnalyzeView中用一个状态变量记录当前展开的菜单标识,点击菜单时更新该标识,让其他菜单自动折叠。
  3. 组件响应外部状态:通用Collapsible组件根据父视图传入的状态判断自身是否展开,同步更新UI和箭头图标。

修改后的完整代码

1. 通用Collapsible组件(替换原有两个Collapsible结构)

import SwiftUI

struct Collapsible<Content: View>: View {
    let label: () -> Text
    let content: () -> Content
    let menuId: String
    @Binding var expandedMenuId: String?
    
    private var isExpanded: Bool {
        expandedMenuId == menuId
    }
    
    var body: some View {
        VStack {
            Button(
                action: {
                    if isExpanded {
                        expandedMenuId = nil
                    } else {
                        expandedMenuId = menuId
                    }
                },
                label: {
                    HStack {
                        label()
                        Spacer()
                        Image(systemName: isExpanded ? "chevron.up" : "chevron.down")
                    }
                    .padding(.bottom, 1)
                    .background(Color.white.opacity(0.01))
                }
            )
            .buttonStyle(PlainButtonStyle())
            
            VStack {
                content()
            }
            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: isExpanded ? .none : 0)
            .clipped()
            .animation(.easeOut, value: isExpanded)
            .transition(.slide)
        }
    }
}

2. 修改后的AnalyzeView

import SwiftUI

struct AnalyzeView: View {
    // 记录当前展开的菜单ID,初始展开"propertyInfo"
    @State private var expandedMenuId: String? = "propertyInfo"
    
    @State var listingValue: Double = 200000
    @State var unitsValue: Double = 2
    @State var sqftValue: Double = 3000
    @State var downPaymentValue: Double = 100000
    @State var loanValue: Double = 10000
    @State var pmiValue: Double = 700
    @State var interestValue: Double = 0.02
    @State var closingValue: Double = 14000
    
    var body: some View {
        VStack {
            ZStack() {
                Image("analyze_property")
                    .resizable()
                    .scaledToFit()
                
                RoundedRectangle(cornerRadius: 30, style: .continuous)
                    .fill(.blue)
                    .opacity(0.8)
                    .frame(width: 300, height: 170)
                
                VStack {
                    Image("gauge")
                    
                    Text("Analyze")
                        .foregroundColor(.white)
                        .font(
                            .largeTitle
                            .weight(.bold)
                        )
                    
                    Text("Return On Investment")
                        .foregroundColor(.white)
                }
            }
            
            // 房产信息菜单,传入唯一ID和绑定的展开状态
            Collapsible(
                label: { Text("Property Information").font(.largeTitle) },
                content: {
                    VStack(spacing: 20) {
                        Text("请在下方输入您的房产信息,提供的数据越详细,我们的分析结果越准确。")
                            .frame(maxWidth: .infinity, alignment: .leading)
                            .fixedSize(horizontal: false, vertical: true)
                        
                        Text("Listing Price")
                        Slider(value: $listingValue, in: 0...10000000, step: 1){
                        } minimumValueLabel: {Text("0")} maximumValueLabel: {Text("$10M")}
                            .background(Color.gray.brightness(0.4))
                            .padding(.horizontal, 50)
                        
                        Text("Total Units")
                        Slider(value: $unitsValue, in: 0...20, step: 1){
                        } minimumValueLabel: {Text("0")} maximumValueLabel: {Text("20")}
                            .background(Color.gray.brightness(0.4))
                            .padding(.horizontal, 50)
                        
                        Text("Square Feet")
                        Slider(value: $sqftValue, in: 0...15000, step: 1){
                        } minimumValueLabel: {Text("0")} maximumValueLabel: {Text("15K")}
                            .background(Color.gray.brightness(0.4))
                            .padding(.horizontal, 50)
                    }
                },
                menuId: "propertyInfo",
                expandedMenuId: $expandedMenuId
            )
            
            // 财务信息菜单,传入唯一ID和绑定的展开状态
            Collapsible(
                label: { Text("Financials").font(.largeTitle) },
                content: {
                    VStack {
                        Text("请在下方输入您的财务数据。")
                        
                        Group {
                            Text("Down Payment")
                            Slider(value: $downPaymentValue, in: 0...10000000, step: 1)
                                .padding(.horizontal, 50)
                            Text("\(downPaymentValue, specifier: "%.0f")")
                        }
                        
                        Group {
                            Text("Loan Amount")
                            Slider(value: $loanValue, in: 0...10000000, step: 1)
                                .padding(.horizontal, 50)
                            Text("\(loanValue, specifier: "%.0f")")
                        }
                        
                        Group {
                            Text("PMI")
                            Slider(value: $pmiValue, in: 0...5000, step: 1)
                                .padding(.horizontal, 50)
                            Text("\(pmiValue, specifier: "%.0f")")
                        }
                        
                        Group {
                            Text("Interest Rate")
                            Slider(value: $interestValue, in: 0...1, step: 0.01)
                                .padding(.horizontal, 50)
                            Text("\(interestValue, specifier: "%.0f")")
                        }
                        
                        Group {
                            Text("Closing Cost/Fees")
                            Slider(value: $closingValue, in: 0...30000, step: 1)
                                .padding(.horizontal, 50)
                            Text("\(closingValue, specifier: "%.0f")")
                        }
                    }
                },
                menuId: "financials",
                expandedMenuId: $expandedMenuId
            )
            
            Spacer()
        }
    }
}

struct AnalyzeView_Previews: PreviewProvider {
    static var previews: some View {
        AnalyzeView()
    }
}

关键修改点说明

  • 通用组件设计:Collapsible不再维护自身的展开状态,通过@Binding接收父视图的expandedMenuId,对比自身menuId判断是否展开。
  • 单一状态控制:父视图的expandedMenuId唯一标识当前展开的菜单,点击菜单时更新该值,其他菜单因ID不匹配自动折叠。
  • 动画优化:恢复了平滑的展开/折叠动画,提升交互体验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 06:36:26