Trying to understand some odd behavior in SwiftUI DragGesture

I'm attempting to implement a star rating view using a drag gesture. (eg user can press on star#1, then 'change their mind' and drag to star 3 and release)

The code below will calculate the inferred star rating based on the width of the Stars HStack and the x value of the current drag position.

The odd behavior I'm seeing is when I drag off the leading (left) edge of the stars. sometimes the app gets non-responsive, repeatedly calling onChanged.

I believe the cause of this behavior is the following:

  1. app detects the user drag has moved to a location to the left of the starts (eg -1, 15)
  2. app updates rating Int to 0 and redraws the text view containing rating "0"
  3. this increases the width of the root HStack, which causes the stars hstack to move slightly to the left
  4. This effectively moves the drag position to just inside the start (eg 1, 15)
  5. This changes the rating value to 1 which causes the text view value to update to "1"
  6. This causes the stars hStack to move slightly to the right
  7. This causes the drag position to update to be slightly to the left of the start (eg -1, 15)
  8. goto 2

What's not clear to me is why this behavior continues even after the drag is completed.

There are many ways I've found to avoid/prevent this behavior including: fixing the width of the Text containing the rating Int. not letting the rating value be change to 0 when dragging to the left of the stars. But these both feel like hacky work arounds.

Is there a better way to avoid this metastable ping pong behavior?

thanks!

struct ContentView: View {
    @State var rating: Int = 0
    var body: some View {
        HStack {
            GeometryReader { proxy in
                HStack {
                    ForEach(0..<5) { index in
                        Image(systemName: symbolName(for: index))
                    }
                }
                .gesture(
                    DragGesture(minimumDistance: 0)
                        .onChanged { gesture in
                            rating = starValue(for: gesture.location.x, width: proxy.size.width)
                        }
                )
            }
            .frame(width: 145, height: 30)

            Text("\(rating)")
        }
    }
    func starValue(for xPosition: CGFloat, width: CGFloat) -> Int {
        guard xPosition > 0 else {
            return 0
        }
        let fraction = xPosition / width
        let result = Int(ceil(fraction * 5))
        return max(0, min(result, 5))
    }

    func symbolName(for index: Int) -> String {
        if index < rating {
            return "star.fill"
        }
        return "star"
    }
}

A few ideas.

this increases the width of the root HStack, which causes the stars hstack to move slightly to the left

If you are right, then you should force Text to a fixed frame:

Text("\(rating)")
  .frame(width: 20)  // Or whatever width fits

But there may be another issue:

What's not clear to me is why this behavior continues even after the drag is completed.

How long does that continue ? May be a lot of gestures were stacked before ending.

Did you try increasing minimum distance ?

DragGesture(minimumDistance: 5)

Hi Claude, Thanks for responding.

If you are right, then you should force Text to a fixed frame:

I agree fixing the width of the Text() does solve the problem. However I am hoping my stars will be a shareable component that others will use. So I would like it to be more robust and not require my devs to need to remember to fix the frames of this view's more dynamic neighbours.

How long does that continue ? May be a lot of gestures were stacked before ending.

It continues forever. After the app gets into this bad state it is permanently hung

Did you try increasing minimum distance ?

Thanks for the suggestion I hadn't originally tried this. Unfortunately, I tried multiple values between 0 and 10 and they both exhibit the same hanging behavior, when slowly dragging from 1 to 0. and also from 4 to 5

Trying to understand some odd behavior in SwiftUI DragGesture
 
 
Q