1

I have a SwiftUI application. It has a ScrollView and a Text and I want the Text to display the position of the elements in the ScrollView.

struct LinkedScrolling: View {
    @State var scrollPosition: CGFloat = 0

    var body: some View {
        VStack {
            Text("\(self.scrollPosition)")
            ScrollContent(scrollPosition: self.$scrollPosition)
        }
    }
}

This View contains the Text and the ScrollContent. This is ScrollContent:

struct ScrollContent: View {
    @Binding var scrollPosition: CGFloat

    var body: some View {
        ScrollView(.horizontal) {
            GeometryReader { geometry -> AnyView in
                self.scrollPosition = geometry.frame(in: .global).minX

                let view = AnyView(HStack(spacing: 20) {
                        Rectangle()
                            .fill(Color.blue)
                            .frame(width: 400, height: 200)
                        Rectangle()
                            .fill(Color.red)
                            .frame(width: 400, height: 200)
                        Rectangle()
                        .fill(Color.green)
                            .frame(width: 400, height: 200)
                        Rectangle()
                        .fill(Color.orange)
                            .frame(width: 400, height: 200)
                        Rectangle()
                        .fill(Color.pink)
                            .frame(width: 400, height: 200)

                })
                return view
            }.frame(width: 5*400+4*20)
        }
    }
}

The State variable scrollPosition gets updated every time the elements in the ScrollView move. When using the app in an iOS 14.2 Simulator, scrollPosition does not change and the console logs [SwiftUI] Modifying state during view update, this will cause undefined behavior..

What really confuses me, is that it works in the Xcode preview canvas and the State variable and the Text change like I want them to.

Is it possible to change the State variable this way? If yes, how can I try to make it work on the Simulator? If no, is there any other way to achieve my goal?

Thank you for your help!

Adam
  • 173
  • 9
  • I have doubts it is right way, at least it will conflict with user interaction and potentially have future bugs. Next should be helpful (it is for vertical, but for horizontal the approach is the same) https://stackoverflow.com/a/65871577/12299030. – Asperi Feb 03 '21 at 08:14

1 Answers1

3

You can use DispatchQueue.main.async.

DispatchQueue.main.async {
    self.scrollPosition = geometry.frame(in: .global).minX
}

Or better way is to use .onReceive

Like this

struct ScrollContent: View {
    @Binding var scrollPosition: CGFloat

    var body: some View {
        ScrollView(.horizontal) {
            GeometryReader { geometry in
                HStack(spacing: 20) {
                        Rectangle()
                            .fill(Color.blue)
                            .frame(width: 400, height: 200)
                        Rectangle()
                            .fill(Color.red)
                            .frame(width: 400, height: 200)
                        Rectangle()
                        .fill(Color.green)
                            .frame(width: 400, height: 200)
                        Rectangle()
                        .fill(Color.orange)
                            .frame(width: 400, height: 200)
                        Rectangle()
                        .fill(Color.pink)
                            .frame(width: 400, height: 200)

                }.onReceive(Just(geometry), perform: { _ in //<-- Here
                    self.scrollPosition = geometry.frame(in: .global).minX
                })
            }.frame(width: 5*400+4*20)
        }
    }
}

Raja Kishan
  • 16,767
  • 2
  • 26
  • 52