Search code examples
formslayoutswiftui

Dynamic row height in a SwiftUI form


I'm adding controls to a SwiftUI Form to assist the user enter data (and constrain the entries!). Although there is a lot to like about Forms, I've discovered that things that work nicely outside this container do very unexpected things inside it and it's not always obvious how to compensate for this.

The plan is to have the data field displayed as a single row. When the row is tapped, the control slides out from behind the data field - the row will need to expand (height) to accommodate the control.

I'm using Swift Playgrounds to develop the proof of concept (or failure in my case). The idea is to use a ZStack which will allow a nice sliding animation by overlaying the views and giving them a different zIndex and applying the offset when the data field view is tapped. Sounds simple but of course the Form row does not expand when the ZStack is expanded.

Adjusting the frame of the ZStack while expanding causes all sorts of weird changes in padding (or at least it looks like it) which can be compensated for by counter-offsetting the "top" view but this causes other unpredictable behaviour. Pointers and ideas gratefully accepted.

import SwiftUI

struct MyView: View {
    @State var isDisclosed = false

    var body: some View {
        Form { 
            Spacer()

            VStack { 
                ZStack(alignment: .topLeading) {
                    Rectangle()
                        .fill(Color.red)
                        .frame(width: 100, height: 100)
                        .zIndex(1)
                        .onTapGesture { self.isDisclosed.toggle() }

                    Rectangle()
                        .fill(Color.blue)
                        .frame(width: 100, height: 100)
                        .offset(y: isDisclosed ? 50 : 0)
                        .animation(.easeOut)
                }
            }

            Spacer()
        }
    }
}

Collapsed stack Collapsed stack

Expanded stack - view overlaps adjacent row Expanded stack - view overlaps adjacent row

Result when adjusting ZStack vertical frame when expanded - top padding increases Result when adjusting ZStack vertical frame when expanded - top padding increases


Solution

  • Use alignmentGuide instead of offset.

    ...
    //.offset(y: isDisclosed ? 50 : 0)
    .alignmentGuide(.top, computeValue: { dimension in dimension[.top] - (self.isDisclosed ? 50 : 0) })
    ...
    

    offset doesn't affect its view's frame. that's why Form doesn't react as expected. On the contrary, alignmentGuide does.