Description
When a drag of the panel starts on a horizontal scroll view in the content (a row of chips, say), the panel doesn't follow the finger: it holds still for the whole drag if the drag is slow. Related to #76, but that issue was about the two gestures colliding; here the panel waits for the row.
Core.gestureRecognizer(_:shouldRequireFailureOf:) makes the panel's pan wait for any other pan to fail ("Do not begin the pan gesture until these gestures fail"). A scroll view that can't scroll along the panel's axis doesn't fail on a drag along that axis until the touch ends, so the panel waits the whole time.
Expected behavior
A vertical drag that starts on the row moves the panel, and a horizontal one scrolls the row, as with two nested scroll views running in different directions.
Actual behavior
A slow vertical drag that starts on the row leaves the panel where it is, during the drag and after the finger lifts. Recorded at 10 fps: a 300pt drag at 100pt/s from a chip left the surface at the same position in every frame, while the same drag started on an empty part of the panel moved it 10pt per frame with the finger.
Steps to reproduce
- Run the view controller below.
- Put a finger on the row of chips and drag slowly upward.
- The panel doesn't move.
Code example that reproduces the issue
import UIKit
import FloatingPanel
final class DragOverRowViewController: UIViewController {
private let fpc = FloatingPanelController()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGray5
fpc.set(contentViewController: RowViewController())
fpc.addPanel(toParent: self)
}
}
/// A sheet whose content is one horizontally scrolling row of chips, like a list of categories.
final class RowViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let chips = UIStackView(arrangedSubviews: (1...12).map { number in
var configuration = UIButton.Configuration.gray()
configuration.title = "Category \(number)"
configuration.cornerStyle = .capsule
return UIButton(configuration: configuration)
})
chips.spacing = 8
chips.translatesAutoresizingMaskIntoConstraints = false
let row = UIScrollView()
row.showsHorizontalScrollIndicator = false
row.translatesAutoresizingMaskIntoConstraints = false
row.addSubview(chips)
view.addSubview(row)
NSLayoutConstraint.activate([
row.topAnchor.constraint(equalTo: view.topAnchor, constant: 32),
row.leadingAnchor.constraint(equalTo: view.leadingAnchor),
row.trailingAnchor.constraint(equalTo: view.trailingAnchor),
row.frameLayoutGuide.heightAnchor.constraint(equalToConstant: 44),
chips.topAnchor.constraint(equalTo: row.contentLayoutGuide.topAnchor),
chips.bottomAnchor.constraint(equalTo: row.contentLayoutGuide.bottomAnchor),
chips.leadingAnchor.constraint(equalTo: row.contentLayoutGuide.leadingAnchor, constant: 16),
chips.trailingAnchor.constraint(equalTo: row.contentLayoutGuide.trailingAnchor, constant: -16),
chips.heightAnchor.constraint(equalTo: row.frameLayoutGuide.heightAnchor)
])
}
}
How do you display panel(s)?
- Add as child view controllers
How many panels do you displays?
Suggested fix
Two parts, both in Core:
-
Don't wait for a scroll view that can't scroll along the panel's axis. In shouldRequireFailureOf, before the final return true for pans:
if let otherScrollView = otherGestureRecognizer.view as? UIScrollView, !scrollsAlongPanelAxis(otherScrollView) {
return false
}
scrollsAlongPanelAxis(_:) is the content-size / alwaysBounce… check that shouldScrollingContentInMoving(from:to:) already makes per position, pulled out so both can use it.
-
Over such a scroll view, begin only on a drag along the panel's axis. Once the panel stops waiting, it would otherwise take sideways drags from the row too. Add gestureRecognizerShouldBegin(_:) to Core: when the touch started inside a scroll view that doesn't scroll along the panel's axis, begin only if the translation is mostly along that axis. That's what the outer of two nested scroll views does.
With both parts applied, the same drag from a chip moves the panel with the finger, frame for frame as the control does, and a sideways drag on the row still scrolls the row without moving the panel. We've shipped this locally. Happy to open a PR with it if you'd like it upstream.
Environment
Library version
main (de51f81); also 3.2.2
Installation method
- Source, built directly from
Sources/ for the repro
iOS version(s)
iOS 27.0 simulator (iPhone 18 Pro)
Xcode version
27.0
Description
When a drag of the panel starts on a horizontal scroll view in the content (a row of chips, say), the panel doesn't follow the finger: it holds still for the whole drag if the drag is slow. Related to #76, but that issue was about the two gestures colliding; here the panel waits for the row.
Core.gestureRecognizer(_:shouldRequireFailureOf:)makes the panel's pan wait for any other pan to fail ("Do not begin the pan gesture until these gestures fail"). A scroll view that can't scroll along the panel's axis doesn't fail on a drag along that axis until the touch ends, so the panel waits the whole time.Expected behavior
A vertical drag that starts on the row moves the panel, and a horizontal one scrolls the row, as with two nested scroll views running in different directions.
Actual behavior
A slow vertical drag that starts on the row leaves the panel where it is, during the drag and after the finger lifts. Recorded at 10 fps: a 300pt drag at 100pt/s from a chip left the surface at the same position in every frame, while the same drag started on an empty part of the panel moved it 10pt per frame with the finger.
Steps to reproduce
Code example that reproduces the issue
How do you display panel(s)?
How many panels do you displays?
Suggested fix
Two parts, both in
Core:Don't wait for a scroll view that can't scroll along the panel's axis. In
shouldRequireFailureOf, before the finalreturn truefor pans:scrollsAlongPanelAxis(_:)is the content-size /alwaysBounce…check thatshouldScrollingContentInMoving(from:to:)already makes per position, pulled out so both can use it.Over such a scroll view, begin only on a drag along the panel's axis. Once the panel stops waiting, it would otherwise take sideways drags from the row too. Add
gestureRecognizerShouldBegin(_:)toCore: when the touch started inside a scroll view that doesn't scroll along the panel's axis, begin only if the translation is mostly along that axis. That's what the outer of two nested scroll views does.With both parts applied, the same drag from a chip moves the panel with the finger, frame for frame as the control does, and a sideways drag on the row still scrolls the row without moving the panel. We've shipped this locally. Happy to open a PR with it if you'd like it upstream.
Environment
Library version
main(de51f81); also 3.2.2Installation method
Sources/for the reproiOS version(s)
iOS 27.0 simulator (iPhone 18 Pro)
Xcode version
27.0