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

Commit

Permalink
Flatten the Interaction types down to a single Interaction protocol.
Browse files Browse the repository at this point in the history
Summary:
This new protocol is abstract over the target type, T. This diff flattens ViewInteraction and PropertyInteraction into a single protocol, greatly simplifying the overall type system and also making it possible for interactions to support novel target types.

This change introduces a feature regression: interactions can no longer support multiple target types via overloads. In practice, I found that this actually cleaned up runtime.add call sites (e.g. springs can't be added to views anymore, they must be explicitly added to a property), so I'm not particularly concerned about this regression.

If we do find that it's important for an interaction to support multiple distinct target types, then we can introduce variant Interaction types, e.g. InteractionVariant1, InteractionVariant2, etc... with corresponding runtime APIs.

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

Reviewed By: O4 Material Apple platform reviewers, chuga

Subscribers: chuga

Tags: #material_motion

Differential Revision: http://codereview.cc/D2827
  • Loading branch information
Jeff Verkoeyen committed Mar 10, 2017
1 parent 9c346d4 commit 53352b9
Show file tree
Hide file tree
Showing 17 changed files with 49 additions and 66 deletions.
8 changes: 4 additions & 4 deletions examples/ContextualTransitionExample.swift
Original file line number Diff line number Diff line change
Expand Up @@ -333,15 +333,15 @@ private class PushBackTransition: Transition {

// TODO: The need here is we want to hide a given view will the transition is active. This
// implementation does not register a stream with the runtime.
private class Hidden: ViewInteraction {
private class Hidden: Interaction {
deinit {
for view in hiddenViews {
view.isHidden = false
}
}
func add(to reactiveView: ReactiveUIView, withRuntime runtime: MotionRuntime) {
reactiveView.view.isHidden = true
hiddenViews.insert(reactiveView.view)
func add(to view: UIView, withRuntime runtime: MotionRuntime) {
view.isHidden = true
hiddenViews.insert(view)
}
var hiddenViews = Set<UIView>()
}
2 changes: 1 addition & 1 deletion examples/DragSourceExample.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,6 @@ public class DragSourceExampleViewController: UIViewController {

let spring = Spring<CGPoint>(threshold: 1, system: coreAnimation)
runtime.connect(tossable.spring.destination, to: spring.destination)
runtime.add(spring, to: square2)
runtime.add(spring, to: runtime.get(square2.layer).position)
}
}
8 changes: 4 additions & 4 deletions examples/FabTransitionExample.swift
Original file line number Diff line number Diff line change
Expand Up @@ -170,15 +170,15 @@ private class CircularRevealTransition: Transition {

// TODO: The need here is we want to hide a given view will the transition is active. This
// implementation does not register a stream with the runtime.
private class Hidden: ViewInteraction {
private class Hidden: Interaction {
deinit {
for view in hiddenViews {
view.isHidden = false
}
}
func add(to reactiveView: ReactiveUIView, withRuntime runtime: MotionRuntime) {
reactiveView.view.isHidden = true
hiddenViews.insert(reactiveView.view)
func add(to view: UIView, withRuntime runtime: MotionRuntime) {
view.isHidden = true
hiddenViews.insert(view)
}
var hiddenViews = Set<UIView>()
}
8 changes: 4 additions & 4 deletions examples/SwipeExample.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ enum TossDirection {
case right
}

class TossableStackedCard: ViewInteraction {
class TossableStackedCard: Interaction {
public let tossDirection = createProperty("tossDirection", withInitialValue: TossDirection.none)

init(relativeView: UIView, previousCard: TossableStackedCard? = nil, rotation: CGFloat) {
Expand All @@ -34,11 +34,11 @@ class TossableStackedCard: ViewInteraction {
self.dragGesture = UIPanGestureRecognizer()
}

func add(to reactiveView: ReactiveUIView, withRuntime runtime: MotionRuntime) {
func add(to view: UIView, withRuntime runtime: MotionRuntime) {
let reactiveView = runtime.get(view)
let position = reactiveView.centerX
self.position = position

let view = reactiveView.view
view.addGestureRecognizer(dragGesture)

let destination = createProperty("destination", withInitialValue: relativeView.bounds.midX)
Expand Down Expand Up @@ -145,7 +145,7 @@ public class SwipeExampleViewController: UIViewController {
alpha: 1)

let interaction = TossableStackedCard(relativeView: view, previousCard: queue.last, rotation: rotation)
runtime.add(interaction, to: runtime.get(card))
runtime.add(interaction, to: card)

lastRotation = rotation

Expand Down
11 changes: 3 additions & 8 deletions src/Interaction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,7 @@ public protocol StatefulInteraction {
var state: MotionObservable<MotionState> { get }
}

public protocol ViewInteraction {
/** Connect all streams with the provided runtime. */
func add(to reactiveView: ReactiveUIView, withRuntime runtime: MotionRuntime)
}

public protocol PropertyInteraction {
associatedtype T
func add(to property: ReactiveProperty<T>, withRuntime runtime: MotionRuntime)
public protocol Interaction {
associatedtype Target
func add(to target: Target, withRuntime runtime: MotionRuntime)
}
16 changes: 4 additions & 12 deletions src/MotionRuntime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,9 @@ public final class MotionRuntime {
self.containerView = containerView
}

public func add(_ interaction: ViewInteraction, to view: UIView) {
add(interaction, to: get(view))
}

public func add<I: PropertyInteraction, P: ReactivePropertyConvertible>(_ interaction: I, to property: P) where I.T == P.T {
interaction.add(to: property.asProperty(), withRuntime: self)
}

public func add(_ interaction: ViewInteraction, to reactiveView: ReactiveUIView) {
interaction.add(to: reactiveView, withRuntime: self)
viewInteractions.append(interaction)
public func add<I: Interaction>(_ interaction: I, to target: I.Target) {
interaction.add(to: target, withRuntime: self)
interactions.append(interaction)
}

public func connect<O: MotionObservableConvertible>(_ stream: O, to property: ReactiveProperty<O.T>) {
Expand Down Expand Up @@ -160,5 +152,5 @@ public final class MotionRuntime {
}

private var subscriptions: [Subscription] = []
private var viewInteractions: [ViewInteraction] = []
private var interactions: [Any] = []
}
7 changes: 3 additions & 4 deletions src/interactions/AdjustsAnchorPoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,8 @@ public class AdjustsAnchorPoint {
}
}

extension AdjustsAnchorPoint: ViewInteraction {
public func add(to reactiveView: ReactiveUIView, withRuntime runtime: MotionRuntime) {
let view = reactiveView.view
extension AdjustsAnchorPoint: Interaction {
public func add(to view: UIView, withRuntime runtime: MotionRuntime) {
var anchorPointStreams = gestureRecognizers.map {
runtime.get($0)
.onRecognitionState(.began)
Expand All @@ -42,7 +41,7 @@ extension AdjustsAnchorPoint: ViewInteraction {
})

for stream in anchorPointStreams {
runtime.connect(stream, to: reactiveView.reactiveLayer.anchorPointAdjustment)
runtime.connect(stream, to: runtime.get(view.layer).anchorPointAdjustment)
}
}
}
6 changes: 3 additions & 3 deletions src/interactions/ArcMove.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ public class ArcMove {
fileprivate let system: PathTweenToStream<CGPoint>
}

extension ArcMove: ViewInteraction {
public func add(to reactiveView: ReactiveUIView, withRuntime runtime: MotionRuntime) {
extension ArcMove: Interaction {
public func add(to view: UIView, withRuntime runtime: MotionRuntime) {
let tween = PathTween(system: system, timeline: timeline)
runtime.connect(arcMove(from: from, to: to), to: tween.path)
runtime.connect(duration, to: tween.duration)

runtime.add(tween, to: reactiveView.reactiveLayer.position)
runtime.add(tween, to: runtime.get(view.layer).position)
}
}

Expand Down
12 changes: 6 additions & 6 deletions src/interactions/DirectlyManipulable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ public class DirectlyManipulable: NSObject {
}
}

extension DirectlyManipulable: ViewInteraction {
public func add(to reactiveView: ReactiveUIView, withRuntime runtime: MotionRuntime) {
extension DirectlyManipulable: Interaction {
public func add(to view: UIView, withRuntime runtime: MotionRuntime) {
for gestureRecognizer in [draggable.nextGestureRecognizer,
rotatable.nextGestureRecognizer,
scalable.nextGestureRecognizer] {
Expand All @@ -48,10 +48,10 @@ extension DirectlyManipulable: ViewInteraction {

let adjustsAnchorPoint = AdjustsAnchorPoint(gestureRecognizers: [rotatable.nextGestureRecognizer,
scalable.nextGestureRecognizer])
runtime.add(adjustsAnchorPoint, to: reactiveView)
runtime.add(draggable, to: reactiveView)
runtime.add(rotatable, to: reactiveView)
runtime.add(scalable, to: reactiveView)
runtime.add(adjustsAnchorPoint, to: view)
runtime.add(draggable, to: view)
runtime.add(rotatable, to: view)
runtime.add(scalable, to: view)
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/interactions/Draggable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ import Foundation
public class Draggable: Gesturable<UIPanGestureRecognizer> {
}

extension Draggable: ViewInteraction {
public func add(to reactiveView: ReactiveUIView, withRuntime runtime: MotionRuntime) {
extension Draggable: Interaction {
public func add(to view: UIView, withRuntime runtime: MotionRuntime) {
let reactiveView = runtime.get(view)
let gestureRecognizer = dequeueGestureRecognizer(withReactiveView: reactiveView)
let position = reactiveView.reactiveLayer.position
runtime.connect(runtime.get(gestureRecognizer).translated(from: position), to: position)
Expand Down
2 changes: 1 addition & 1 deletion src/interactions/PathTween.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public final class PathTween: TogglableInteraction, StatefulInteraction {
fileprivate let _state = createProperty("PathTween._state", withInitialValue: MotionState.atRest)
}

extension PathTween: PropertyInteraction {
extension PathTween: Interaction {
public func add(to property: ReactiveProperty<CGPoint>, withRuntime runtime: MotionRuntime) {
runtime.connect(asStream(), to: property)
}
Expand Down
5 changes: 3 additions & 2 deletions src/interactions/Rotatable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ import Foundation
public class Rotatable: Gesturable<UIRotationGestureRecognizer> {
}

extension Rotatable: ViewInteraction {
public func add(to reactiveView: ReactiveUIView, withRuntime runtime: MotionRuntime) {
extension Rotatable: Interaction {
public func add(to view: UIView, withRuntime runtime: MotionRuntime) {
let reactiveView = runtime.get(view)
let gestureRecognizer = dequeueGestureRecognizer(withReactiveView: reactiveView)
let rotation = reactiveView.reactiveLayer.rotation
runtime.connect(runtime.get(gestureRecognizer).rotated(from: rotation), to: rotation)
Expand Down
5 changes: 3 additions & 2 deletions src/interactions/Scalable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ import Foundation
public class Scalable: Gesturable<UIPinchGestureRecognizer> {
}

extension Scalable: ViewInteraction {
public func add(to reactiveView: ReactiveUIView, withRuntime runtime: MotionRuntime) {
extension Scalable: Interaction {
public func add(to view: UIView, withRuntime runtime: MotionRuntime) {
let reactiveView = runtime.get(view)
let gestureRecognizer = dequeueGestureRecognizer(withReactiveView: reactiveView)
let scale = reactiveView.reactiveLayer.scale
runtime.connect(runtime.get(gestureRecognizer).scaled(from: scale), to: scale)
Expand Down
8 changes: 1 addition & 7 deletions src/interactions/Spring.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import IndefiniteObservable
This class defines the expected shape of a Spring for use in creating a Spring source.
*/
public class Spring<T: Zeroable>: PropertyInteraction, ViewInteraction, TogglableInteraction, StatefulInteraction {
public class Spring<T: Zeroable>: Interaction, TogglableInteraction, StatefulInteraction {
/** Creates a spring with the provided properties and an initial velocity. */
public init(threshold: CGFloat, system: @escaping SpringToStream<T>) {
self.threshold = createProperty("Spring.threshold", withInitialValue: threshold)
Expand Down Expand Up @@ -67,12 +67,6 @@ public class Spring<T: Zeroable>: PropertyInteraction, ViewInteraction, Togglabl
fileprivate let system: SpringToStream<T>
fileprivate let _state = createProperty("Spring._state", withInitialValue: MotionState.atRest)

public func add(to reactiveView: ReactiveUIView, withRuntime runtime: MotionRuntime) {
if let castedSelf = self as? Spring<CGPoint> {
castedSelf.add(to: reactiveView.reactiveLayer.position, withRuntime: runtime)
}
}

public func add(to property: ReactiveProperty<T>, withRuntime runtime: MotionRuntime) {
let shadow = SpringShadow(of: self, initialValue: property)
runtime.connect(shadow.state.dedupe(), to: ReactiveProperty(initialValue: .atRest) { state in
Expand Down
2 changes: 1 addition & 1 deletion src/interactions/Tap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class Tap {
}
}

extension Tap: PropertyInteraction {
extension Tap: Interaction {
public func add(to property: ReactiveProperty<CGPoint>, withRuntime runtime: MotionRuntime) {
let gestureRecognizer: UITapGestureRecognizer

Expand Down
8 changes: 4 additions & 4 deletions src/interactions/Tossable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ public class Tossable {
}
}

extension Tossable: ViewInteraction {
public func add(to reactiveView: ReactiveUIView, withRuntime runtime: MotionRuntime) {
let position = reactiveView.reactiveLayer.position
extension Tossable: Interaction {
public func add(to view: UIView, withRuntime runtime: MotionRuntime) {
let position = runtime.get(view.layer).position

let gesture = runtime.get(draggable.nextGestureRecognizer)

Expand All @@ -48,6 +48,6 @@ extension Tossable: ViewInteraction {
runtime.enable(spring, whenAtRest: gesture)
runtime.add(spring, to: position)

runtime.add(draggable, to: reactiveView)
runtime.add(draggable, to: view)
}
}
2 changes: 1 addition & 1 deletion src/interactions/Tween.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import Foundation

/** A tween describes a potential interpolation from one value to another. */
public final class Tween<T>: PropertyInteraction, TogglableInteraction, StatefulInteraction {
public final class Tween<T>: Interaction, TogglableInteraction, StatefulInteraction {

/** The duration of the animation in seconds. */
public let duration: ReactiveProperty<CGFloat>
Expand Down

0 comments on commit 53352b9

Please sign in to comment.