Home > Software engineering >  Expand single menu and automatically collapse other menus in SwiftUI
Expand single menu and automatically collapse other menus in SwiftUI

Time:08-29

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 #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 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()
            
        }
        
    }
    
}
  • Related