2

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

ursan526
  • 485
  • 3
  • 10

1 Answers1

1

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
}
Yrb
  • 8,103
  • 2
  • 14
  • 44
  • It is nice. corners no problem. but this time it goes to the wrong places in the interior. for example the scroll index is 50 but it goes to 70. I don't understand why you did this? I just changed the frame to 300 and let index = Array(0..<(10*10)).randomElement() – ursan526 Dec 01 '21 at 13:51
  • It is an example based on your question. If you want something in the center, just add a case for it in the 'setUnitPoint' function. You can see how it is done. That is why I set the `default` to `.center` to handle the middle cases, but I didn't really code for them in the `switch`. I just divided the view into quadrants, but if you make those more toward the edge, the `.default` will take over for the middle. – Yrb Dec 01 '21 at 14:34
  • I changed all of them to center. scrolls to irrelevant places. There is no problem in the code, why is this happening? I tried it on ios 15 iphone 13 similator and ipad pro 12.9 ios 15 similator. same result – ursan526 Dec 01 '21 at 15:23
  • I updated my answer to show you how to get it to center as well. I think the issue is, if you changed ALL of `func setUnitPoint` to `.center`, you are telling it to try to put the corners in the center which doesn't work. Instead, you need to determine whether one of your squares is in the center, or in a corner, and go there. If you look in the updated `func setUnitPoint()` you can see I changed the test for what makes a corner. You can also add similar tests to make it hit a side. I did not over all cases. You need to decide what cases you want to cover yourself. – Yrb Dec 01 '21 at 19:38
  • I made a mistake while converting rows and columns :( it's working perfectly now. thank you very much @Yrb – ursan526 Dec 04 '21 at 22:07
  • ios 15 scroolview has bug. this solution fixed bug thanks – ursan526 Dec 04 '21 at 22:08