SwiftUI实现单菜单展开时自动折叠其他菜单技术问询
SwiftUI 实现单一展开的可折叠菜单
问题说明
当前的可折叠菜单各自独立维护展开状态,存在以下问题:
- 初始状态下Property Information菜单展开
- 用户可同时展开Property Information和Financials两个菜单,导致界面需要滚动才能查看全部内容
需要实现每次仅能展开一个菜单,其余自动折叠的功能,以最大化利用屏幕空间。
解决方案思路
- 合并重复组件:删除原有的
Collapsible_main和Collapsible_other,创建通用的Collapsible组件,通过外部状态控制展开/折叠。 - 统一状态管理:在父视图
AnalyzeView中用一个状态变量记录当前展开的菜单标识,点击菜单时更新该标识,让其他菜单自动折叠。 - 组件响应外部状态:通用
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
相关产品推荐
相关产品推荐

