4

I want to create a custom scroll that snaps views to middle But I can't figure out how to set the offset correctly.

This is the sample of the scroll:

struct contentView: View {

    @State var startOffset: CGFloat = 0
    @State var translationWidth: CGFloat = 0
    @State var offset: CGFloat = 0
    @State var index: Int = 0


    var body: some View  {
        GeometryReader { geo in
            HStack {

                ForEach(0..<5, id: \.self) { num in
                    Button(action: {
                    // some action
                    }) {
                        Circle()
                    }
                    .frame(width: geo.size.width)
                    .offset(x: -self.offset*CGFloat(self.index) + self.translationWidth)

                }

            }.frame(width: geo.size.width, height: 200)
                .offset(x: self.startOffset )
                .highPriorityGesture (
                    DragGesture(minimumDistance: 0.1, coordinateSpace: .global)
                    .onChanged({ (value) in
                         self.translationWidth = value.translation.width
                     })

                     .onEnded({ (value) in
                         if self.translationWidth > 40 {
                                self.index -= 1

                        } else if self.translationWidth < -40 {
                            self.index += 1
                        }
                        self.translationWidth = 0
                    })
            )

            .onAppear {
                withAnimation(.none) {
                    let itemsWidth: CGFloat = CGFloat(geo.size.width*5) - (geo.size.width)
                    self.offset = geo.size.width
                    self.startOffset = itemsWidth/2
                }
            }
        }
    }
}

It works but it is slightly off the middle, it doesn't scroll exactly in the middle and I don't understand why.

The problem occurs with the onAppear closure:

.onAppear {
    withAnimation(.none) {
        let itemsWidth: CGFloat = CGFloat(geo.size.width*5) - (geo.size.width)
        self.offset = geo.size.width
        self.startOffset = itemsWidth/2
    }
}

I might be missing some small pixels in my calculations.

UPDATE

So Apparently the HStack has a default value for spacing between views. so to fix it you should remove it like so:

 HStack(spacing: 0) {
    ....
 }
Kevin
  • 1,103
  • 10
  • 33

2 Answers2

1

So after a bit of experimenting, I realized the HStack is using a default spacing which is not equal to zero. It means that in my calculations I didn't include the spacing between items

so to fix this all you need to add is:

 HStack(spacing: 0) {
    ...
 }
Kevin
  • 1,103
  • 10
  • 33
1

TabView's PageTabViewStyle() is another option for scrolling views in SwiftUI that snap to the middle:

struct ContentView: View {
    
    var body: some View {
        TabView {
            ForEach(0..<5) { _ in
                Button(action: {
                    // some action
                }) {
                    Circle().frame(width: 200, height: 200)
                }
            }
        }
        .tabViewStyle(PageTabViewStyle())
    }
}

The result looks like this:

enter image description here

Marcy
  • 4,611
  • 2
  • 34
  • 52