DragGesture onChange infinite loops with values 0.250 and -0.250

I ran into a strange situation (bug?) today trying to resize the relative height of two views in a VStack on macOS. I accomplished this by inserting a thin rectangle between the two views and applying a DragGesture to it. It works fine... until it doesn't. It takes a lot of changing of the view heights before I hit the bug, but if I drag & release enough times, I will eventually hit it.

At some point, the code goes into an infinite loop and the program effectively freezes. os_log() is showing the .onChange value.height continuously flip flopping between -0.25 and +0.25.

I found a kludge/work-around. If the onChange's absolute value is less than 1, I don't update the frame hight. Then the code runs like a champ.

Am I doing something wrong? Is this a bug I should file?

Here is the SwiftUI test code:

import SwiftUI
import os.log

struct ResizableViews: View {
    @State private var height: CGFloat = 200
    let minHeight:CGFloat = 30
    let maxHeight: CGFloat = 400
    
    var body: some View {
        Group {
            VStack (spacing: 0) {
                Rectangle()
                    .fill(Color.blue)
                    .frame(height: height)
                
                // The draggable rectangle/handle
                Rectangle()
                    .background(Color.gray)
                    .frame(height: 10)
                    .gesture(
                        DragGesture()
                            .onChanged { value in
                                os_log("CHANGE \(value.translation.height)")
                                
                                // abs() > 1 is a kludge to avoid infinite loop
                                // with value changes flipping between -0.25 and
                                // 0.25
                                if abs(value.translation.height) >= 1 {
                                    let tmpHeight = height + value.translation.height
                                    height = min(max(tmpHeight, minHeight), maxHeight)
                                }
                            }
                            .onEnded{ value in
                                os_log("END \(value.translation.height)")
                            }
                    )
                
                Rectangle()
                    .fill(Color.red)
            }
        }
    }
}

Here is the console output showing the value.translation.height flip flopping between -0.25 and +0.25.

  • Hardware: MacBook Pro with Apple M1 Pro
  • OS: Ventura 13.5.1
Answered by DTS Engineer in 764123022

Well, there is VSplitView: https://developer.apple.com/documentation/SwiftUI/VSplitView. Is that something you could use instead?

You aren't really telling what you are trying to do. No screenshot? Why are you applying the height value you get from the gray rectangle to the other?

I think you should file a bug report about this (and post the feedback number here in this thread for reference).

My guess is that you're hitting an edge case when the drag amount is much less than 1 point, and the reported distances are getting rounded to (apparently) multiples of 0.25. Perhaps rounding in one direction (plus or minus) causes rounding in the opposite direction the next time. (I'm just speculating, of course.)

Here is a screenshot showing the output of the SwiftUI view sample code above. I can grab the dark bar/rect in the middle and drag it up and down to adjust which pane (blue or red) gets more space.

Also, if there is a more elegant/standard way of doing this, I'd appreciate any pointers. I tried a Divider() instead of a rect, but I had problems selecting the divider with my mouse.

Here is how I am using this in actual code. The top view is a list of organizations running code on my Mac. The bottom pane provides details about that team. The horizontal bar lets me adjust which view gets more space - the list view or the details view.

Good point. I've added some additional detail below. (I'm hoping there is a standard way of doing this, and I can just use that technique instead of my drag gesture code)

Accepted Answer

Well, there is VSplitView: https://developer.apple.com/documentation/SwiftUI/VSplitView. Is that something you could use instead?

DragGesture onChange infinite loops with values 0.250 and -0.250
 
 
Q