swiftui ScrollViewReader scrollTo not correct working

3k Views Asked by At

I am developing an application with swiftui. After zooming, when I say scroll to the corner with the ScrollViewReader, it goes out of the screen. my code is below. It fails after trying a few times. it doesn't do it every time.

 import SwiftUI

struct ContentView: View {
    @State var zoomIn = false
    
    var body: some View {
        GeometryReader { g in
            ScrollViewReader { reader in
                
                ScrollView([.horizontal,.vertical], showsIndicators: false) {
                    
                    VStack(spacing: 20) {
                        ForEach(0 ..< 11, id:\.self) { row in
                            HStack(spacing: 20) {
                                ForEach(0 ..< 11, id:\.self) { column in
                                    Text("Item \(row) \(column)")
                                        .foregroundColor(.white)
                                        .frame(width: zoomIn ? 70 : 35, height: zoomIn ? 70 : 35)
                                        .background(Color.red)
                                        .id("\(row)\(column)")
                                        .onTapGesture {
                                            withAnimation {
                                                reader.scrollTo( ["00", "010","100","1010"].randomElement()!)
                                            }
                                        }
                                }
                            }
                        }
                    }
                    
                    Button("Zoom") {
                        withAnimation {
                            zoomIn.toggle()
                        }
                    }
                }
            }
        }
    }
    
}



struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

my home screen.

enter image description here

after scrollTo

enter image description here

1

There are 1 best solutions below

6
On BEST ANSWER

I think the issue you are having is that the 2 ForEach just don't seem to work well with the ScrollReader. I took a different approach and used an identifiable struct and a LazyVGrid. That allowed me to use one ForEach and id the individual squares with a UUID. I then used the same UUID in the scrollTo(). The only difficulty I ran into was that ScrollReader didn't know what to do with a bidirectional ScrollView, so I made a function that returned the UnitPoint and used that as an anchor in the scrollTo(). That seems to have done the trick and it works very reliably.

struct BiDirectionScrollTo: View {
    
    let scrollItems: [ScrollItem] = Array(0..<100).map( { ScrollItem(name: $0.description) })
    
    let columns = [
        // Using 3 grid items forces there to be 3 columns
        GridItem(.fixed(80)),
        GridItem(.fixed(80)),
        GridItem(.fixed(80)),
        GridItem(.fixed(80)),
        GridItem(.fixed(80)),
        GridItem(.fixed(80)),
        GridItem(.fixed(80)),
        GridItem(.fixed(80)),
        GridItem(.fixed(80)),
        GridItem(.fixed(80))
    ]
    
    init() {
        
    }
    var body: some View {
        ScrollViewReader { reader in
            ScrollView([.horizontal,.vertical], showsIndicators: false) {
                LazyVGrid(columns: columns, spacing: 20) {
                    ForEach(scrollItems, id: \.id) { item in
                        Text("Item \(item.name)")
                            .foregroundColor(.white)
                            .frame(width: 80, height: 80)
                            .background(Color.red)
                            .id(item.id)
                            .onTapGesture {
                                withAnimation {
                                    if let index = [0, 9, 55, 90, 99].randomElement() {
                                        print(index)
                                        reader.scrollTo(scrollItems[index].id, anchor: setUnitPoint(index))
                                    }
                                }
                            }
                    }
                }
            }
        }
    }
    private func setUnitPoint(_ index:Int) -> UnitPoint {
        switch true {
        case index % 10 < 2 && index / 10 < 2:
             return .topLeading
         case index % 10 >= 7 && index / 10 < 7:
            return .topTrailing
        case index % 10 < 2 && index / 10 >= 7:
            return .bottomLeading
        case index % 10 >= 2 && index / 10 >= 7:
            return .bottomTrailing
        default:
            return .center
        }
    }
}


struct ScrollItem: Identifiable {
    let id = UUID()
    var name: String
}