Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
  • Loading branch information
muukii committed Jul 21, 2024
1 parent 7e01317 commit e6e1bb4
Show file tree
Hide file tree
Showing 3 changed files with 300 additions and 16 deletions.
205 changes: 205 additions & 0 deletions Sources/SwiftUISnapDraggingModifier/CustomGesture.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import SwiftUI

@available(iOS 18, *)
struct CustomGesture: UIGestureRecognizerRepresentable {

struct Value: Equatable {
let location: CGPoint
let translation: CGSize
let velocity: CGSize
}

final class Coordinator: NSObject, UIGestureRecognizerDelegate {

@objc
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {

guard let _gestureRecognizer = gestureRecognizer as? ScrollViewDragGestureRecognizer else {
assertionFailure("\(gestureRecognizer)")
return false
}

guard !(otherGestureRecognizer is UIScreenEdgePanGestureRecognizer) else {
return false
}

// switch configuration.scrollViewOption.scrollViewDetection {
// case .noTracking:
// return false
// case .automatic:
let result = _gestureRecognizer.trackingScrollView == otherGestureRecognizer.view
return result
// case .specific(let scrollView):
// return otherGestureRecognizer.view == scrollView
// }

}

}

private let coordinateSpaceInDragging: CoordinateSpaceProtocol
private let coordinateSpaceInView: CoordinateSpaceProtocol
private let _onChange: (Value) -> Void
private let _onEnd: (Value) -> Void

init(
coordinateSpaceInDragging: CoordinateSpaceProtocol,
coordinateSpaceInView: CoordinateSpaceProtocol,
onChange: @escaping (Value) -> Void,
onEnd: @escaping (Value) -> Void
) {
self.coordinateSpaceInDragging = coordinateSpaceInDragging
self.coordinateSpaceInView = coordinateSpaceInView
self._onChange = onChange
self._onEnd = onEnd
}

func makeCoordinator(converter: CoordinateSpaceConverter) -> Coordinator {
return .init()
}

func makeUIGestureRecognizer(context: Context) -> ScrollViewDragGestureRecognizer {
let gesture = ScrollViewDragGestureRecognizer()
gesture.delegate = context.coordinator
return gesture
}

func handleUIGestureRecognizerAction(_ recognizer: ScrollViewDragGestureRecognizer, context: Context) {

let location = context.converter.location(in: coordinateSpaceInDragging)
let pointInView = context.converter.location(in: coordinateSpaceInView)

let resolvedTranslation = CGSize(
width: (location.x - pointInView.x),
height: (location.y - pointInView.y)
)

print(resolvedTranslation)

let value = Value(
location: context.converter.location(in: coordinateSpaceInDragging),
translation: resolvedTranslation,
velocity: { .init(width: $0.x, height: $0.y) }(context.converter.velocity(in: coordinateSpaceInDragging) ?? .zero)
)

switch recognizer.state {
case .possible:
break
case .began, .changed:
_onChange(value)
case .ended:
_onEnd(value)
case .cancelled:
_onEnd(value)
case .failed:
_onEnd(value)
@unknown default:
break
}

}

}

final class ScrollViewDragGestureRecognizer: UIPanGestureRecognizer {

weak var trackingScrollView: UIScrollView?

init() {
super.init(target: nil, action: nil)
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
trackingScrollView = event.findVerticalScrollView()
super.touchesBegan(touches, with: event)
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {

super.touchesMoved(touches, with: event)
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event)
}

override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesCancelled(touches, with: event)
}

}

extension UIEvent {

fileprivate func findVerticalScrollView() -> UIScrollView? {

guard
let firstTouch = allTouches?.first,
let targetView = firstTouch.view
else { return nil }

let scrollView = Array(sequence(first: targetView, next: { $0.next }))
.last {
guard let scrollView = $0 as? UIScrollView else {
return false
}

func isHorizontal(scrollView: UIScrollView) -> Bool {

let contentInset: UIEdgeInsets

if #available(iOS 11.0, *) {
contentInset = scrollView.adjustedContentInset
} else {
contentInset = scrollView.contentInset
}

return (scrollView.bounds.width - (contentInset.right + contentInset.left) < scrollView.contentSize.width)
}

func isScrollable(scrollView: UIScrollView) -> Bool {

let contentInset: UIEdgeInsets

if #available(iOS 11.0, *) {
contentInset = scrollView.adjustedContentInset
} else {
contentInset = scrollView.contentInset
}

return (scrollView.bounds.width - (contentInset.right + contentInset.left) <= scrollView.contentSize.width) || (scrollView.bounds.height - (contentInset.top + contentInset.bottom) <= scrollView.contentSize.height)
}

return isScrollable(scrollView: scrollView) && !isHorizontal(scrollView: scrollView)
}

return (scrollView as? UIScrollView)
}

}

@available(iOS 18, *)
#Preview("Scroll") {
@Previewable @State var offset: CGSize = .zero

ZStack {

ScrollView {
VStack {
ForEach(0..<20) { index in
HStack {
Spacer()
Text("Item \(index)")
Spacer()
}
}
}
}
.frame(width: 200, height: 200)
.background(Color.green.secondary)
.padding()
.background(Color.green.tertiary)
.modifier(SnapDraggingModifier(offset: $offset))

}
}
1 change: 0 additions & 1 deletion Sources/SwiftUISnapDraggingModifier/File.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ public struct SheetModifier<DisplayContent: View>: ViewModifier {
}

}

#Preview {

struct SheetContent: View {
Expand Down
110 changes: 95 additions & 15 deletions Sources/SwiftUISnapDraggingModifier/SnapDraggingModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,22 +161,50 @@ public struct SnapDraggingModifier: ViewModifier {

let addingGesture = dragGesture.simultaneously(with: gesture)

Group {
switch gestureMode {
case .normal:
base
.gesture(addingGesture, including: .all)
case .highPriority:
base
.highPriorityGesture(addingGesture, including: .all)
if #available(iOS 18, *) {

Group {
switch gestureMode {
case .normal:
base
.gesture(_gesture)
.simultaneousGesture(gesture)
// .gesture(addingGesture, including: .all)
case .highPriority:
base
.gesture(_gesture)
.simultaneousGesture(gesture)
// .highPriorityGesture(addingGesture, including: .all)
}
}
}
.animatableOffset(x: currentOffset.width, y: currentOffset.height)
.coordinateSpace(name: _CoordinateSpaceTag.transition)
.onChange(of: isTracking) { newValue in
if newValue {
handler.onStartDragging()
.animatableOffset(x: currentOffset.width, y: currentOffset.height)
.coordinateSpace(name: _CoordinateSpaceTag.transition)
.onChange(of: isTracking) { newValue in
if newValue {
handler.onStartDragging()
}
}

} else {

Group {
switch gestureMode {
case .normal:
base
.gesture(addingGesture, including: .all)
case .highPriority:
base
.highPriorityGesture(addingGesture, including: .all)
}
}
.animatableOffset(x: currentOffset.width, y: currentOffset.height)
.coordinateSpace(name: _CoordinateSpaceTag.transition)
.onChange(of: isTracking) { newValue in
if newValue {
handler.onStartDragging()
}
}

}

}
Expand Down Expand Up @@ -263,11 +291,63 @@ public struct SnapDraggingModifier: ViewModifier {
)
}

@available(iOS 18.0, *)
@available(macOS, unavailable)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
@available(visionOS, unavailable)
private var _gesture: some UIGestureRecognizerRepresentable {
CustomGesture(
coordinateSpaceInDragging: .named(_CoordinateSpaceTag.transition),
coordinateSpaceInView: .named(_CoordinateSpaceTag.pointInView),
onChange: { value in

// if self.isActive || isInActivation(startLocation: value.startLocation) {
//
// self.isActive = true

let resolvedTranslation = CGSize(
width: (value.location.x - pointInView.x),
height: (value.location.y - pointInView.y)
)


// TODO: stop the current animation when dragging restarted.
withAnimation(.interactiveSpring()) {
if axis.contains(.horizontal) {
currentOffset.width = rubberBand(
value: resolvedTranslation.width,
min: horizontalBoundary.min,
max: horizontalBoundary.max,
bandLength: horizontalBoundary.bandLength
)
}
if axis.contains(.vertical) {
currentOffset.height = rubberBand(
value: resolvedTranslation.height,
min: verticalBoundary.min,
max: verticalBoundary.max,
bandLength: verticalBoundary.bandLength
)
}
}
// }
}, onEnd: { value in
onEnded(
velocity: .init(
dx: value.velocity.width,
dy: value.velocity.height
)
)
})
}

private var dragGesture: some Gesture {

DragGesture(
minimumDistance: activation.minimumDistance,
coordinateSpace: .named(_CoordinateSpaceTag.transition)
)
)
.updating(
$isTracking,
body: { _, state, _ in
Expand Down

0 comments on commit e6e1bb4

Please sign in to comment.