SwiftUI Gestures prevent scrolling with iOS 18

I added gesture support to my app that supports iOS 16 and 17 and have never had issues with it.

However, when I compiled my app with Xcode 16 I immediately noticed a problem with the app when I ran it in the simulator. I couldn't scroll up or down. I figured out it’s because of my gesture support.

My gesture support is pretty simple.

        let myDragGesture = DragGesture()
            .onChanged { gesture in
                self.offset = gesture.translation
            }
            .onEnded { _ in
                    if self.offset.width > threshold {
                       ...some logic
                    } else if self.offset.width < -threshold {
                       ...some other logic
                    }
                              
                logitUI.debug("drag gesture width was \(self.offset.width)")
                self.offset = .zero
            }

If I pass nil to .gesture instead of myDragGesture then scrolling starts working again.

Here’s some example output when I’m trying to scroll down. These messages do NOT appear when I run my app on an iOS 16/17 simulator with Xcode 15.

  • drag gesture width was 5.333328
  • drag gesture width was -15.333344
  • drag gesture width was -3.000000
  • drag gesture width was -24.333328
  • drag gesture width was -30.666656

I opened FB14205678 about this.

https://github.com/feedback-assistant/reports/issues/542

Here is a workaround which helped me to fix the issue. Hope this helps.

Perhaps this will be useful to someone.

We made custom gestures for iOS 18 using the new UIGestureRecogniserRepresentable protocol. This gesture avoids locking scrolling in ScrollView if the user puts their finger on an item using this custom gesture. But at the same time activate the drag gesture if the user does not scroll and long presses on a block using this gesture, at which point it blocks the drag action of the ScrollView (as opposed to using .simultaneousGesture()).

And yes, as noted above, it's not as nice as the naive SwiftUI.

struct DragGestureWithDelayRecognizer: UIGestureRecognizerRepresentable {
	let delay: TimeInterval
	let onChanged: (CGPoint) -> Void
	let onEnded: () -> Void
	
	
	func makeUIGestureRecognizer(context: Context) -> some UIGestureRecognizer {
		let recognizer = UILongPressGestureRecognizer(target: context.coordinator,
													  action: #selector(Coordinator.handleGesture(_:)))
		recognizer.minimumPressDuration = delay
		recognizer.allowableMovement = .infinity // so that it doesn't get cancelled when moving
		recognizer.delegate = context.coordinator
		return recognizer
	}

	func handleUIGestureRecognizerAction(_ recognizer: UIGestureRecognizerType, context: Context) {}

	func makeCoordinator(converter: CoordinateSpaceConverter) -> Coordinator {
		return Coordinator(converter: converter, onChanged: onChanged, onEnded: onEnded)
	}

	final class Coordinator: NSObject, UIGestureRecognizerDelegate {
		let converter: CoordinateSpaceConverter
		let onChanged: (CGPoint) -> Void
		let onEnded: () -> Void

		init(converter: CoordinateSpaceConverter,
			 onChanged: @escaping (CGPoint) -> Void,
			 onEnded: @escaping () -> Void) {
			self.converter = converter
			self.onChanged = onChanged
			self.onEnded = onEnded
		}

		@objc func handleGesture(_ recognizer: UILongPressGestureRecognizer) {
			let location = recognizer.location(in: recognizer.view)
			let swiftUILocation = converter.convert(globalPoint: location)

			switch recognizer.state {
			case .began, .changed:
				onChanged(swiftUILocation)
			case .ended, .cancelled, .failed:
				onEnded()
			default:
				break
			}
		}

		func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
			return false
		}
	}
}
SwiftUI Gestures prevent scrolling with iOS 18
 
 
Q