Custom collapsible view

Viewed 140

I'm trying to create a custom collapsible view. The code works but in Collapsible<Content: View> the VStack has strange behavior: the elements overlap when the component is closed. To note this, try disable clipped() as shown in the image.

overlap content

Is it a bug or something so stupid that I am not noticing? Thanks in advance

FIXED CODE:

struct Collapsible<Content: View>: View {
    var label: String
    var content: () -> Content
    init(label: String, @ViewBuilder _ content: @escaping () -> Content) {
        self.label = label
        self.content = content
    }
    @State private var collapsed: Bool = true
    
    var body: some View {
        VStack(spacing: 0) {
            Button(action: {
                withAnimation(.easeInOut) {
                    self.collapsed.toggle()
                }
            }, label: {
                    HStack {
                        Text(label)
                        Spacer(minLength: 0)
                        Image(systemName: self.collapsed ? "chevron.down" : "chevron.up")
                    }
                    .padding()
                    .background(Color.white.opacity(0.1))
                }
            )
            .buttonStyle(PlainButtonStyle())
            self.content()
            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: collapsed ? 0 : .none, alignment: .top) // <- added `alignment` here
            .clipped() // Comment to see the overlap
            .animation(.easeOut)
            .transition(.slide)
        }
    }
}

struct CollapsibleDemoView: View {
    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            Collapsible(label: "Collapsible") {
                Text("Content")
                .padding()
                .background(Color.red)
            }
            Spacer(minLength: 0)
        }
        .padding()
        .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}
1 Answers

The .frame modifier has a parameter alignment which defaults to center which results in the behaviour you're seeing: the layout bounds are set to zero height but the content is rendered vertically centered beyond the bounds (if not clipped). You can fix this by adding the alignment:

.frame(maxHeight: 0, alignment: .top)
Related