2

I've a SwiftUI ForEach loop which contains views that expand when clicked. Something similar to the view shown below.

struct ExpandingView: View {
    
    @State var isExpanded = false
    
    var body: some View {
        VStack {
            Text("Hello!")
            if isExpanded {
                Text("World")
            }
        }
        .onTapGesture {
            withAnimation {
                isExpanded.toggle()
            }
        }
    }
}

In theory, I can use the ScrollViewReader and scrollTo a particular position.

ScrollViewReader { view in
    ScrollView {
        ForEach(0...100, id: \.self) { id in
             ExpandingView()
                 .id(id)
                 .padding()                        
        }
    }
}

However, in practice, I'm not sure how to get the id from within the view (because onTapGesture is in the view) and propagate it up one level.

The other option is to have the onTapGesture in the ForEach loop, but then I'm not sure how to toggle the isExpanded flag for the correct view.

4
  • inside the if isExpanded condition have an only single view? or multiple views for scrolling'? Commented Jun 26, 2021 at 12:27
  • @RajaKishan Inside the if isExpanded there is no scrolling required. The scrolling part is one level up i.e. when the views themselves are displayed with the help of ForEach loop. Commented Jun 26, 2021 at 12:29
  • where you want to click action for scrolling to particular row? Commented Jun 26, 2021 at 12:32
  • @RajaKishan Since the entire row is tappable, I'm flexible putting it out on the ForEach loop as well. But as of now, it is within the view. Commented Jun 26, 2021 at 12:34

1 Answer 1

3

You can pass a ScrollViewProxy to row view and then you can now able to scroll.

struct ExpandingView: View {
    
    @State var isExpanded = false
    var id: Int
    var proxy: ScrollViewProxy
    var body: some View {
        VStack {
            Text("Hello!")
            if isExpanded {
                Text("World")
            }
        }
        .onTapGesture {
            withAnimation {
                isExpanded.toggle()
                proxy.scrollTo(id, anchor: .center)
            }
        }
    }
}

struct TestScrollView: View {
    var body: some View {
        ScrollViewReader { view in
            ScrollView {
                ForEach(0...100, id: \.self) { id in
                    ExpandingView(id: id, proxy: view)
                        .id(id)
                        .padding()
                }
            }
        }
    }
}

enter image description here

Sign up to request clarification or add additional context in comments.

2 Comments

Are there any consequences to this approach, like accidentally leaking memory or anything in particular to be aware of? I'm asking this because I plan to use it in conjunction with LazyVStack on the parent view.
No. We are not doing any heavy or unrelated tasks. We are just passing scroll proxy same as normal param. If your view has images or full-size images then it will spike memory some time else by this code you no need to worry about this.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.