diff --git a/examples/ModalDialogExample.swift b/examples/ModalDialogExample.swift index 4d5aeaa..9756f04 100644 --- a/examples/ModalDialogExample.swift +++ b/examples/ModalDialogExample.swift @@ -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, diff --git a/src/interactions/ChangeDirection.swift b/src/interactions/ChangeDirection.swift index 0e1dfc9..e0c6995 100644 --- a/src/interactions/ChangeDirection.swift +++ b/src/interactions/ChangeDirection.swift @@ -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. @@ -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 @@ -80,6 +80,9 @@ public final class ChangeDirection: Interaction { } public func add(to direction: ReactiveProperty, withRuntime runtime: MotionRuntime, constraints axis: Axis?) { + guard let gesture = gesture else { + return + } let axis = axis ?? .y let chooseAxis: (MotionObservable) -> MotionObservable switch axis { diff --git a/src/interactions/DirectlyManipulable.swift b/src/interactions/DirectlyManipulable.swift index aa16691..72e22a3 100644 --- a/src/interactions/DirectlyManipulable.swift +++ b/src/interactions/DirectlyManipulable.swift @@ -64,8 +64,8 @@ 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 } } @@ -73,8 +73,8 @@ public final class DirectlyManipulable: NSObject, Interaction, Togglable, Statef 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) diff --git a/src/interactions/Draggable.swift b/src/interactions/Draggable.swift index 9338529..503a121 100644 --- a/src/interactions/Draggable.swift +++ b/src/interactions/Draggable.swift @@ -47,7 +47,9 @@ public final class Draggable: Gesturable, Interaction, T withRuntime runtime: MotionRuntime, constraints applyConstraints: ConstraintApplicator? = 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 @@ -75,17 +77,20 @@ public final class Draggable: Gesturable, 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, withRuntime runtime: MotionRuntime, constraints applyConstraints: ConstraintApplicator? = 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? } diff --git a/src/interactions/Rotatable.swift b/src/interactions/Rotatable.swift index 54c64d2..d40257b 100644 --- a/src/interactions/Rotatable.swift +++ b/src/interactions/Rotatable.swift @@ -35,7 +35,9 @@ public final class Rotatable: Gesturable, Interacti withRuntime runtime: MotionRuntime, constraints applyConstraints: ConstraintApplicator? = 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 diff --git a/src/interactions/Scalable.swift b/src/interactions/Scalable.swift index 1df2bd1..5e4235a 100644 --- a/src/interactions/Scalable.swift +++ b/src/interactions/Scalable.swift @@ -35,7 +35,9 @@ public final class Scalable: Gesturable, Interaction, withRuntime runtime: MotionRuntime, constraints applyConstraints: ConstraintApplicator? = 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 diff --git a/src/interactions/SlopRegion.swift b/src/interactions/SlopRegion.swift index 01da615..7544d7b 100644 --- a/src/interactions/SlopRegion.swift +++ b/src/interactions/SlopRegion.swift @@ -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 } @@ -57,6 +57,9 @@ public final class SlopRegion: Interaction { } public func add(to direction: ReactiveProperty, withRuntime runtime: MotionRuntime, constraints axis: Axis?) { + guard let gesture = gesture else { + return + } let axis = axis ?? .y let chooseAxis: (MotionObservable) -> MotionObservable switch axis { diff --git a/src/protocols/Gesturable.swift b/src/protocols/Gesturable.swift index 5857d3f..a833712 100644 --- a/src/protocols/Gesturable.swift +++ b/src/protocols/Gesturable.swift @@ -34,11 +34,13 @@ public enum GesturableConfiguration { 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?) } /** @@ -68,11 +70,7 @@ public class Gesturable { break } } - if let first = first { - self.init(.withExistingRecognizer(first)) - } else { - self.init() - } + self.init(.withExistingRecognizer(first)) } public init(_ config: GesturableConfiguration) { @@ -81,7 +79,11 @@ public class Gesturable { 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 } @@ -95,20 +97,21 @@ public class Gesturable { 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 @@ -121,18 +124,18 @@ public class Gesturable { /** 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 }