Search code examples
iosswiftswiftuiscrollview

SwiftUI: Dynamically resizing ScrollView height


I'm trying to create a view in which the user can tap a button and the ScrollView featured in the bottom will be pulled upwards. I can't seem to find an adequate solution to for resizing the ScrollView.

It could be done by fixing the .frame() of the ScrollView and playing around with its position, but I don't want to do this.

Is there some way to dynamically change the height of the ScrollView (without using the .frame() view modifier)? Ideally I would like it to be that the ScrollView is off screen and is only pulled up. The ScrollView should wrap below the end of the screen.

enter image description here

struct ContentView: View {
    @State private var scrollViewExpanded = false
    
    let data = ["Data 1", "Data 2", "Data 3", "Data 4", "Data 5", "Data 6", "Data 7", "Data 8"]
    
    
    
    var body: some View {
        VStack {
            
            
            Circle()
                .stroke(.red, lineWidth: 30)
                .padding()
                .scaleEffect(scrollViewExpanded ? 0.5 : 1)
                .offset(y: scrollViewExpanded ? -70 : 0)
            
            
            DataView(data: "Data 0")
                .frame(height: 100)
                .padding(.vertical)
                .offset(y: scrollViewExpanded ? -140 : 0)
            
            
            HStack {
                Spacer()
                Button {
                    withAnimation {
                        scrollViewExpanded.toggle()
                    }
                } label: {
                    Image(systemName: "chevron.up.circle")
                        .rotationEffect(Angle(degrees: scrollViewExpanded ? -180 : 0))
                }
                
            }
            .padding()
            .offset(y: scrollViewExpanded ? -140 : 0)
            
            
            ScrollView {
                ForEach(data, id: \.self) { data in
                    DataView(data: data)
                        .frame(height: 100)
                    
                }
            }
            .offset(y: scrollViewExpanded ? -140 : 0)
            
            Spacer()
        }
        .padding()
    }
}


struct DataView: View {
    let data: String
    
    var body: some View {
        
        ZStack {
            RoundedRectangle(cornerRadius: 12).foregroundColor(Color(UIColor.secondarySystemFill))
            HStack {
                Text(data).font(.title3)
                Spacer()
                Image(systemName: "house")
            }
            .padding(.horizontal)
            
        }
        
    }
}

Solution

  • When you scale the circle at the top, the space it uses in the layout does not get scaled automatically. You have tried to compensate for the reduced height by shifting all the elements using y-offset, but this doesn't really work.

    The fix is to control the height of the scaled circle using frame. Then you can take out all of the y-offset adjustments and the ScrollView will automatically fill the bottom region:

        var body: some View {
            VStack {
    
    
                Circle()
                    .stroke(.red, lineWidth: 30)
                    .frame(width: 250, height: 250) // <- ADDED
                    .padding()
                    .scaleEffect(scrollViewExpanded ? 0.5 : 1)
                    .frame(height: scrollViewExpanded ? 140 : 280) // <- ADDED
    //                .offset(y: scrollViewExpanded ? -70 : 0)
    
    
                DataView(data: "Data 0")
                    .frame(height: 100)
                    .padding(.vertical)
    //                .offset(y: scrollViewExpanded ? -140 : 0)
    
    
                HStack {
                    Spacer()
                    Button {
                        withAnimation {
                            scrollViewExpanded.toggle()
                        }
                    } label: {
                        Image(systemName: "chevron.up.circle")
                            .rotationEffect(Angle(degrees: scrollViewExpanded ? -180 : 0))
                    }
    
                }
                .padding()
    //            .offset(y: scrollViewExpanded ? -140 : 0)
    
    
                ScrollView {
                    ForEach(data, id: \.self) { data in
                        DataView(data: data)
                            .frame(height: 100)
    
                    }
                }
    //            .offset(y: scrollViewExpanded ? -140 : 0)
    
                Spacer()
            }
            .padding()
        }
    

    Screenshot