I have a view with several collapsible menus. In order to maximize space and not need to scroll, I would like the user to expand one menu at a time and the other menus should automatically collapse.
Please see screenshots and code below that does not have this functionality.
Screenshot #1: Property Information menu initially expanded
Screenshot #2: Both Property Information and Financials menus are expanded. Only one menu should be expanded at a time while others are collapsed
// AnalyzeViewModel.swift
import SwiftUI
struct AnalyzeView: View {
@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()
// .aspectRatio(contentMode: .fit)
.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)
}
}
Collapsible_main(
label: { Text("Property Information").font(.largeTitle) },
content: {
VStack(spacing: 20) {
Group {
Text("Enter your property info below. The more data you provide the better we can analyze.").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(.trailing, 50).padding(.leading, 50)
//Text("\(listingValue, specifier: "%.0f")")
Text("Total Units")
Slider(value: $unitsValue, in: 0...20, step: 1){
} minimumValueLabel: {Text("0")} maximumValueLabel: {Text("20")}.background(Color.gray.brightness(0.4)).padding(.trailing, 50).padding(.leading, 50)
//Text("\(unitsValue, specifier: "%.0f")")
Text("Square Feet")
Slider(value: $sqftValue, in: 0...15000, step: 1){
} minimumValueLabel: {Text("0")} maximumValueLabel: {Text("15K")}.background(Color.gray.brightness(0.4)).padding(.trailing, 50).padding(.leading, 50)
//Text("\(sqftValue, specifier: "%.0f")")
}
}
)
Collapsible_other(
label: { Text("Financials").font(.largeTitle) },
content: {
VStack {
Text("Enter your financial data below.")
Group {
Text("Down Payment")
Slider(value: $downPaymentValue, in: 0...10000000, step: 1).padding(.trailing, 50).padding(.leading, 50)
Text("\(downPaymentValue, specifier: "%.0f")")
}
Group {
Text("Loan Amount")
Slider(value: $loanValue, in: 0...10000000, step: 1).padding(.trailing, 50).padding(.leading, 50)
Text("\(loanValue, specifier: "%.0f")")
}
Group {
Text("PMI")
Slider(value: $pmiValue, in: 0...5000, step: 1).padding(.trailing, 50).padding(.leading, 50)
Text("\(pmiValue, specifier: "%.0f")")
}
Group {
Text("Interest Rate")
Slider(value: $interestValue, in: 0...1, step: 0.01).padding(.trailing, 50).padding(.leading, 50)
Text("\(interestValue, specifier: "%.0f")")
}
Group {
Text("Closing Cost/Fees")
Slider(value: $closingValue, in: 0...30000, step: 1).padding(.trailing, 50).padding(.leading, 50)
Text("\(closingValue, specifier: "%.0f")")
}
}
}
)
Spacer()
}
}
}
struct AnalyzeView_Previews: PreviewProvider {
static var previews: some View {
AnalyzeView()
}
}
// CollapsibleViewModel.swift
import SwiftUI
struct Collapsible_main<Content: View>: View {
@State var label: () -> Text
@State var content: () -> Content
@State private var collapsed: Bool = false
var body: some View {
VStack {
Button(
action: { self.collapsed.toggle() },
label: {
HStack {
self.label()
Spacer()
Image(systemName: self.collapsed ? "chevron.down" : "chevron.up")
}
.padding(.bottom, 1)
.background(Color.white.opacity(0.01))
}
)
.buttonStyle(PlainButtonStyle())
VStack {
self.content()
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: collapsed ? 0 : .none)
.clipped()
// .animation(.easeOut)
.transition(.slide)
}
}
}
struct Collapsible_other<Content: View>: View {
@State var label: () -> Text
@State var content: () -> Content
@State private var collapsed: Bool = true
var body: some View {
VStack {
Button(
action: { self.collapsed.toggle() },
label: {
HStack {
self.label()
Spacer()
Image(systemName: self.collapsed ? "chevron.down" : "chevron.up")
}
.padding(.bottom, 1)
.background(Color.white.opacity(0.01))
}
)
.buttonStyle(PlainButtonStyle())
VStack {
self.content()
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: collapsed ? 0 : .none)
.clipped()
// .animation(.easeOut)
.transition(.slide)
}
}
}
CodePudding user response:
Consider this simple approach:
Create a @State
var in your AnalyzeView
called expandedId
that is of type UUID
. Share this on to both of your child views via Binding. In the child views, when you expand, set this id to one stored in your child view. React to changes of this id in your child views and if they don´t match the one stored set the collapsed
var to true.
struct Collapsible_main<Content: View>: View {
@Binding var expandedId: UUID // add this
@State var label: () -> Text
@State var content: () -> Content
@State private var collapsed: Bool = false
private let id = UUID() // add this
var body: some View {
VStack {
Button(
action: {
self.collapsed.toggle()
if !self.collapsed{
self.expandedId = self.id
}
},
label: {
HStack {
self.label()
Spacer()
Image(systemName: self.collapsed ? "chevron.down" : "chevron.up")
}
.padding(.bottom, 1)
.background(Color.white.opacity(0.01))
}
)
.buttonStyle(PlainButtonStyle())
VStack {
self.content()
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: collapsed ? 0 : .none)
.clipped()
// .animation(.easeOut)
.transition(.slide)
}
.onChange(of: expandedId) { newValue in
if newValue != self.id{
self.collapsed = true
}
}
}
}
struct Collapsible_other<Content: View>: View {
@Binding var expandedId: UUID // add this
@State var label: () -> Text
@State var content: () -> Content
@State private var collapsed: Bool = true
private let id = UUID() // add this
var body: some View {
VStack {
Button(
action: {
self.collapsed.toggle()
if !self.collapsed{
expandedId = self.id
}
},
label: {
HStack {
self.label()
Spacer()
Image(systemName: self.collapsed ? "chevron.down" : "chevron.up")
}
.padding(.bottom, 1)
.background(Color.white.opacity(0.01))
}
)
.buttonStyle(PlainButtonStyle())
VStack {
self.content()
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: collapsed ? 0 : .none)
.clipped()
// .animation(.easeOut)
.transition(.slide)
}
.onChange(of: expandedId) { newValue in
if newValue != self.id{
self.collapsed = true
}
}
}
}
struct AnalyzeView: View {
@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
@State private var expandedId = UUID()
var body: some View {
VStack {
ZStack() {
Image("analyze_property")
.resizable()
// .aspectRatio(contentMode: .fit)
.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)
}
}
Collapsible_main(
// pass the id on
expandedId: $expandedId, label: { Text("Property Information").font(.largeTitle) },
content: {
VStack(spacing: 20) {
Group {
Text("Enter your property info below. The more data you provide the better we can analyze.").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(.trailing, 50).padding(.leading, 50)
//Text("\(listingValue, specifier: "%.0f")")
Text("Total Units")
Slider(value: $unitsValue, in: 0...20, step: 1){
} minimumValueLabel: {Text("0")} maximumValueLabel: {Text("20")}.background(Color.gray.brightness(0.4)).padding(.trailing, 50).padding(.leading, 50)
//Text("\(unitsValue, specifier: "%.0f")")
Text("Square Feet")
Slider(value: $sqftValue, in: 0...15000, step: 1){
} minimumValueLabel: {Text("0")} maximumValueLabel: {Text("15K")}.background(Color.gray.brightness(0.4)).padding(.trailing, 50).padding(.leading, 50)
//Text("\(sqftValue, specifier: "%.0f")")
}
}
)
Collapsible_other(
//pass the id on
expandedId: $expandedId, label: { Text("Financials").font(.largeTitle) },
content: {
VStack {
Text("Enter your financial data below.")
Group {
Text("Down Payment")
Slider(value: $downPaymentValue, in: 0...10000000, step: 1).padding(.trailing, 50).padding(.leading, 50)
Text("\(downPaymentValue, specifier: "%.0f")")
}
Group {
Text("Loan Amount")
Slider(value: $loanValue, in: 0...10000000, step: 1).padding(.trailing, 50).padding(.leading, 50)
Text("\(loanValue, specifier: "%.0f")")
}
Group {
Text("PMI")
Slider(value: $pmiValue, in: 0...5000, step: 1).padding(.trailing, 50).padding(.leading, 50)
Text("\(pmiValue, specifier: "%.0f")")
}
Group {
Text("Interest Rate")
Slider(value: $interestValue, in: 0...1, step: 0.01).padding(.trailing, 50).padding(.leading, 50)
Text("\(interestValue, specifier: "%.0f")")
}
Group {
Text("Closing Cost/Fees")
Slider(value: $closingValue, in: 0...30000, step: 1).padding(.trailing, 50).padding(.leading, 50)
Text("\(closingValue, specifier: "%.0f")")
}
}
}
)
Spacer()
}
}
}