swiftuiswiftui-scrollviewscrollviewreader

swiftui ScrollViewReader scrollTo not correct working


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


Solution

  • 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
    }