Skip to content
This repository has been archived by the owner on Aug 13, 2021. It is now read-only.

Commit

Permalink
Add a resistance property to Draggable.
Browse files Browse the repository at this point in the history
Summary:
This new property makes it possible to configure resistance behaviors on Draggable when the user moves beyond a given perimeter.

This API is preferred over applying a rubberBand constraint to a Tossable interaction because it ensures that resistance is only applied to the draggable output, not the spring (which may re-read the position value and then incorrectly cause the position to jump to the doubly-resisted position).

Reviewers: O2 Material Motion, O4 Material Apple platform reviewers, #material_motion, markwei

Reviewed By: O2 Material Motion, O4 Material Apple platform reviewers, #material_motion, markwei

Tags: #material_motion

Differential Revision: http://codereview.cc/D3097
  • Loading branch information
Jeff Verkoeyen committed Apr 24, 2017
1 parent eaecd86 commit dbff13d
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 0 deletions.
21 changes: 21 additions & 0 deletions src/interactions/Draggable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,25 @@ import UIKit
- `{ $0.yLocked(to: somePosition) }`
*/
public final class Draggable: Gesturable<UIPanGestureRecognizer>, Interaction, Togglable, Manipulation {

/**
When a non-null resistance perimiter is provided, dragging beyond the perimeter will result in
resistance being applied to the position until the max length is reached.
*/
public let resistance = (
/**
The region beyond which resistance should take effect, in absolute coordinates.

If .null, no resistance will be applied to the drag position.
*/
perimeter: createProperty(withInitialValue: CGRect.null),

/**
The maximum distance the drag position is able to move beyond the perimeter.
*/
maxLength: createProperty(withInitialValue: 48)
)

/**
A sub-interaction for writing the next gesture recognizer's final velocity to a property.

Expand Down Expand Up @@ -63,6 +82,8 @@ public final class Draggable: Gesturable<UIPanGestureRecognizer>, Interaction, T
if let applyConstraints = applyConstraints {
stream = applyConstraints(stream)
}
stream = stream.rubberBanded(outsideOf: resistance.perimeter,
maxLength: resistance.maxLength)
runtime.connect(stream, to: position)
}
}
Expand Down
52 changes: 52 additions & 0 deletions src/operators/rubberBanded.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,65 @@ extension MotionObservableConvertible where T == CGPoint {

/**
Applies resistance to values that fall outside of the given range.

Does not modify the value if CGRect is .null.
*/
public func rubberBanded(outsideOf rect: CGRect, maxLength: CGFloat) -> MotionObservable<CGPoint> {
return _map(#function, args: [rect, maxLength]) {
guard rect != .null else {
return $0
}

return CGPoint(x: rubberBand(value: $0.x, min: rect.minX, max: rect.maxX, bandLength: maxLength),
y: rubberBand(value: $0.y, min: rect.minY, max: rect.maxY, bandLength: maxLength))
}
}

/**
Applies resistance to values that fall outside of the given range.

Does not modify the value if CGRect is .null.
*/
public func rubberBanded<O1, O2>(outsideOf rectStream: O1, maxLength maxLengthStream: O2) -> MotionObservable<CGPoint> where O1: MotionObservableConvertible, O1.T == CGRect, O2: MotionObservableConvertible, O2.T == CGFloat {
var lastRect: CGRect?
var lastMaxLength: CGFloat?
var lastValue: CGPoint?
return MotionObservable(self.metadata.createChild(Metadata(#function, type: .constraint, args: [rectStream, maxLengthStream]))) { observer in

let checkAndEmit = {
guard let rect = lastRect, let maxLength = lastMaxLength, let value = lastValue else {
return
}
guard lastRect != .null else {
observer.next(value)
return
}
observer.next(CGPoint(x: rubberBand(value: value.x, min: rect.minX, max: rect.maxX, bandLength: maxLength),
y: rubberBand(value: value.y, min: rect.minY, max: rect.maxY, bandLength: maxLength)))
}

let rectSubscription = rectStream.subscribeToValue { rect in
lastRect = rect
checkAndEmit()
}

let maxLengthSubscription = maxLengthStream.subscribeToValue { maxLength in
lastMaxLength = maxLength
checkAndEmit()
}

let upstreamSubscription = self.subscribeAndForward(to: observer) { value in
lastValue = value
checkAndEmit()
}

return {
rectSubscription.unsubscribe()
maxLengthSubscription.unsubscribe()
upstreamSubscription.unsubscribe()
}
}
}
}

private func rubberBand(value: CGFloat, min: CGFloat, max: CGFloat, bandLength: CGFloat) -> CGFloat {
Expand Down

0 comments on commit dbff13d

Please sign in to comment.