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

Commit

Permalink
When no gesture recognizer is provided to a gestural interaction that…
Browse files Browse the repository at this point in the history
… expects one, the interaction now does nothing.

Summary:
Gestural interactions configured to expect a gesture recognizer will no longer connect any streams if a gesture recognizer is not provided.

The `withFirstGestureIn:` now uses this new behavior when it can't find a gesture recognizer that qualifies for the interaction. Prior to this change, if the withFirstGestureIn initializer couldn't find a gesture recognizer it would default to `registerNewRecognizerToTargetView` behavior. This can cause superfluous gesture recognizers to be registered to a view.

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

Subscribers: markwei

Tags: #material_motion

Differential Revision: http://codereview.cc/D3077
  • Loading branch information
Jeff Verkoeyen committed Apr 21, 2017
1 parent 3e47189 commit d43a5f6
Show file tree
Hide file tree
Showing 8 changed files with 56 additions and 37 deletions.
17 changes: 9 additions & 8 deletions examples/ModalDialogExample.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,19 +82,20 @@ class ModalDialogTransition: SelfDismissingTransition {

let draggable = Draggable(withFirstGestureIn: ctx.gestureRecognizers)

let gesture = runtime.get(draggable.nextGestureRecognizer)
let centerY = ctx.containerView().bounds.height / 2.0

runtime.add(ChangeDirection(withVelocityOf: draggable.nextGestureRecognizer, whenNegative: .forward),
to: ctx.direction)

runtime.connect(gesture
.velocityOnReleaseStream()
.y()
.thresholdRange(min: -100, max: 100)
.rewrite([.within: position.y().threshold(centerY).rewrite([.below: .forward,
.above: .backward])]),
to: ctx.direction)
if let gesture = draggable.nextGestureRecognizer {
runtime.connect(runtime.get(gesture)
.velocityOnReleaseStream()
.y()
.thresholdRange(min: -100, max: 100)
.rewrite([.within: position.y().threshold(centerY).rewrite([.below: .forward,
.above: .backward])]),
to: ctx.direction)
}

let movement = TransitionSpring(back: backPosition,
fore: forePosition,
Expand Down
7 changes: 5 additions & 2 deletions src/interactions/ChangeDirection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public final class ChangeDirection: Interaction {
/**
The gesture recognizer that will be observed by this interaction.
*/
public let gesture: UIPanGestureRecognizer
public let gesture: UIPanGestureRecognizer?

/**
The minimum absolute velocity required change the transition's direction.
Expand All @@ -57,7 +57,7 @@ public final class ChangeDirection: Interaction {
/**
- parameter minimumVelocity: The minimum absolute velocity required to change the transition's direction.
*/
public init(withVelocityOf gesture: UIPanGestureRecognizer, minimumVelocity: CGFloat = 100, whenNegative: TransitionDirection = .backward, whenPositive: TransitionDirection = .backward) {
public init(withVelocityOf gesture: UIPanGestureRecognizer?, minimumVelocity: CGFloat = 100, whenNegative: TransitionDirection = .backward, whenPositive: TransitionDirection = .backward) {
self.gesture = gesture
self.minimumVelocity = minimumVelocity
self.whenNegative = whenNegative
Expand All @@ -80,6 +80,9 @@ public final class ChangeDirection: Interaction {
}

public func add(to direction: ReactiveProperty<TransitionDirection>, withRuntime runtime: MotionRuntime, constraints axis: Axis?) {
guard let gesture = gesture else {
return
}
let axis = axis ?? .y
let chooseAxis: (MotionObservable<CGPoint>) -> MotionObservable<CGFloat>
switch axis {
Expand Down
8 changes: 4 additions & 4 deletions src/interactions/DirectlyManipulable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,17 @@ public final class DirectlyManipulable: NSObject, Interaction, Togglable, Statef
for gestureRecognizer in [draggable.nextGestureRecognizer,
rotatable.nextGestureRecognizer,
scalable.nextGestureRecognizer] {
if gestureRecognizer.delegate == nil {
gestureRecognizer.delegate = self
if gestureRecognizer?.delegate == nil {
gestureRecognizer?.delegate = self
}
}

runtime.connect(enabled, to: draggable.enabled)
runtime.connect(enabled, to: rotatable.enabled)
runtime.connect(enabled, to: scalable.enabled)

let adjustsAnchorPoint = AdjustsAnchorPoint(gestureRecognizers: [rotatable.nextGestureRecognizer,
scalable.nextGestureRecognizer])
let anchorPointRecognizers = [rotatable.nextGestureRecognizer, scalable.nextGestureRecognizer].flatMap { $0 }
let adjustsAnchorPoint = AdjustsAnchorPoint(gestureRecognizers: anchorPointRecognizers)
runtime.add(adjustsAnchorPoint, to: view)

aggregateState.observe(state: draggable.state, withRuntime: runtime)
Expand Down
11 changes: 8 additions & 3 deletions src/interactions/Draggable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ public final class Draggable: Gesturable<UIPanGestureRecognizer>, Interaction, T
withRuntime runtime: MotionRuntime,
constraints applyConstraints: ConstraintApplicator<CGPoint>? = nil) {
let reactiveView = runtime.get(view)
let gestureRecognizer = dequeueGestureRecognizer(withReactiveView: reactiveView)
guard let gestureRecognizer = dequeueGestureRecognizer(withReactiveView: reactiveView) else {
return
}
let position = reactiveView.reactiveLayer.position

runtime.connect(enabled, to: ReactiveProperty(initialValue: gestureRecognizer.isEnabled) { enabled in
Expand Down Expand Up @@ -75,17 +77,20 @@ public final class Draggable: Gesturable<UIPanGestureRecognizer>, Interaction, T
CGPoint constraints may be applied to this interaction.
*/
public final class DraggableFinalVelocity: Interaction {
fileprivate init(gestureRecognizer: UIPanGestureRecognizer) {
fileprivate init(gestureRecognizer: UIPanGestureRecognizer?) {
self.gestureRecognizer = gestureRecognizer
}

public func add(to target: ReactiveProperty<CGPoint>,
withRuntime runtime: MotionRuntime,
constraints applyConstraints: ConstraintApplicator<CGPoint>? = nil) {
guard let gestureRecognizer = gestureRecognizer else {
return
}
let gesture = runtime.get(gestureRecognizer)
runtime.connect(gesture.velocityOnReleaseStream(in: runtime.containerView),
to: target)
}

let gestureRecognizer: UIPanGestureRecognizer
let gestureRecognizer: UIPanGestureRecognizer?
}
4 changes: 3 additions & 1 deletion src/interactions/Rotatable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ public final class Rotatable: Gesturable<UIRotationGestureRecognizer>, Interacti
withRuntime runtime: MotionRuntime,
constraints applyConstraints: ConstraintApplicator<CGFloat>? = nil) {
let reactiveView = runtime.get(view)
let gestureRecognizer = dequeueGestureRecognizer(withReactiveView: reactiveView)
guard let gestureRecognizer = dequeueGestureRecognizer(withReactiveView: reactiveView) else {
return
}
let rotation = reactiveView.reactiveLayer.rotation

runtime.connect(enabled, to: ReactiveProperty(initialValue: gestureRecognizer.isEnabled) { enabled in
Expand Down
4 changes: 3 additions & 1 deletion src/interactions/Scalable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ public final class Scalable: Gesturable<UIPinchGestureRecognizer>, Interaction,
withRuntime runtime: MotionRuntime,
constraints applyConstraints: ConstraintApplicator<CGFloat>? = nil) {
let reactiveView = runtime.get(view)
let gestureRecognizer = dequeueGestureRecognizer(withReactiveView: reactiveView)
guard let gestureRecognizer = dequeueGestureRecognizer(withReactiveView: reactiveView) else {
return
}
let scale = reactiveView.reactiveLayer.scale

runtime.connect(enabled, to: ReactiveProperty(initialValue: gestureRecognizer.isEnabled) { enabled in
Expand Down
7 changes: 5 additions & 2 deletions src/interactions/SlopRegion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ public final class SlopRegion: Interaction {
/**
The gesture recognizer that will be observed by this interaction.
*/
public let gesture: UIPanGestureRecognizer
public let gesture: UIPanGestureRecognizer?

/**
The size of the slop region.
*/
public let size: CGFloat

public init(withTranslationOf gesture: UIPanGestureRecognizer, size: CGFloat) {
public init(withTranslationOf gesture: UIPanGestureRecognizer?, size: CGFloat) {
self.gesture = gesture
self.size = size
}
Expand All @@ -57,6 +57,9 @@ public final class SlopRegion: Interaction {
}

public func add(to direction: ReactiveProperty<TransitionDirection>, withRuntime runtime: MotionRuntime, constraints axis: Axis?) {
guard let gesture = gesture else {
return
}
let axis = axis ?? .y
let chooseAxis: (MotionObservable<CGPoint>) -> MotionObservable<CGFloat>
switch axis {
Expand Down
35 changes: 19 additions & 16 deletions src/protocols/Gesturable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ public enum GesturableConfiguration <T: UIGestureRecognizer> {
case registerNewRecognizerTo(UIView)

/**
The interaction will make use of the provided gesture recognizer.
The interaction will make use of the provided gesture recognizer, if provided.

If no gesture recognizer is provided then this interaction will do nothing.

The interaction will not associate this gesture recognizer with any view.
*/
case withExistingRecognizer(T)
case withExistingRecognizer(T?)
}

/**
Expand Down Expand Up @@ -68,11 +70,7 @@ public class Gesturable<T: UIGestureRecognizer> {
break
}
}
if let first = first {
self.init(.withExistingRecognizer(first))
} else {
self.init()
}
self.init(.withExistingRecognizer(first))
}

public init(_ config: GesturableConfiguration<T>) {
Expand All @@ -81,7 +79,11 @@ public class Gesturable<T: UIGestureRecognizer> {
let initialState: MotionState
switch self.config {
case .withExistingRecognizer(let recognizer):
initialState = (recognizer.state == .began || recognizer.state == .changed) ? .active : .atRest
if let recognizer = recognizer {
initialState = (recognizer.state == .began || recognizer.state == .changed) ? .active : .atRest
} else {
initialState = .atRest
}
default: ()
initialState = .atRest
}
Expand All @@ -95,20 +97,21 @@ public class Gesturable<T: UIGestureRecognizer> {
This property may change after the interaction has been added to a view depending on the
interaction's configuration.
*/
public var nextGestureRecognizer: T {
public var nextGestureRecognizer: T? {
if let nextGestureRecognizer = _nextGestureRecognizer {
return nextGestureRecognizer
}

let gestureRecognizer: T
let gestureRecognizer: T?

switch config {
case .registerNewRecognizerToTargetView:
gestureRecognizer = T()

case .registerNewRecognizerTo(let view):
gestureRecognizer = T()
view.addGestureRecognizer(gestureRecognizer)
let recognizer = T()
view.addGestureRecognizer(recognizer)
gestureRecognizer = recognizer

case .withExistingRecognizer(let existingGestureRecognizer):
gestureRecognizer = existingGestureRecognizer
Expand All @@ -121,18 +124,18 @@ public class Gesturable<T: UIGestureRecognizer> {
/**
Prepares and returns the gesture recognizer that should be used to drive this interaction.
*/
func dequeueGestureRecognizer(withReactiveView reactiveView: ReactiveUIView) -> T {
func dequeueGestureRecognizer(withReactiveView reactiveView: ReactiveUIView) -> T? {
let gestureRecognizer = self.nextGestureRecognizer
_nextGestureRecognizer = nil

switch config {
case .registerNewRecognizerToTargetView:
reactiveView.view.addGestureRecognizer(gestureRecognizer)
reactiveView.view.addGestureRecognizer(gestureRecognizer!)
default: ()
}

gestureRecognizer.view?.isUserInteractionEnabled = true
gestureRecognizer.isEnabled = enabled.value
gestureRecognizer?.view?.isUserInteractionEnabled = true
gestureRecognizer?.isEnabled = enabled.value

return gestureRecognizer
}
Expand Down

0 comments on commit d43a5f6

Please sign in to comment.