Skip to content

Commit

Permalink
use anchor point during direct manipulation
Browse files Browse the repository at this point in the history
Reviewers: O4 Material Motion Apple platform reviewers, O2 Material Motion, featherless

Reviewed By: O4 Material Motion Apple platform reviewers, O2 Material Motion, featherless

Subscribers: featherless

Tags: #material_motion

Differential Revision: http://codereview.cc/D1686
  • Loading branch information
rcameron committed Oct 11, 2016
1 parent 02b31b6 commit 67e8985
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 25 deletions.
10 changes: 5 additions & 5 deletions Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
PODS:
- MaterialMotionDirectManipulationFamily (1.0.0):
- MaterialMotionRuntime
- MaterialMotionRuntime (2.0.1)
- MaterialMotionRuntime (3.0.1)

DEPENDENCIES:
- MaterialMotionDirectManipulationFamily (from `./`)
- MaterialMotionRuntime (from `https://github.com/material-motion/material-motion-runtime-objc.git`, branch `develop`)

EXTERNAL SOURCES:
MaterialMotionDirectManipulationFamily:
:path: "./"
:path: ./
MaterialMotionRuntime:
:branch: develop
:git: https://github.com/material-motion/material-motion-runtime-objc.git

CHECKOUT OPTIONS:
MaterialMotionRuntime:
:commit: f31cbc6d9b78576e85410b82942a25a53a4e5f6e
:commit: 2adef827e4077c164e70631d0be1eb4ea8258a2e
:git: https://github.com/material-motion/material-motion-runtime-objc.git

SPEC CHECKSUMS:
MaterialMotionDirectManipulationFamily: e547c763a71c47a35698deaa51ceba64757a7857
MaterialMotionRuntime: 142f75ce2cf2682458aa9631541b8644b653ef34
MaterialMotionRuntime: 056ed241097e4f63b7d25a34685d031703689a22

PODFILE CHECKSUM: 23dca2af7e74ddb9199b8ddeed5318201ed1e33c
PODFILE CHECKSUM: 086b501692f3b14effa8ecd8730d0d0a7dfd6f3d

COCOAPODS: 1.0.1
157 changes: 137 additions & 20 deletions src/DirectManipulationMotionFamily.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import MaterialMotionRuntime
/// A plan that enables a target to be dragged.
public final class Draggable: NSObject, Plan {
public let panGestureRecognizer: UIPanGestureRecognizer
public var shouldAdjustAnchorPointOnGestureStart = false

public init(withGestureRecognizer recognizer: UIPanGestureRecognizer = UIPanGestureRecognizer()) {
self.panGestureRecognizer = recognizer
Expand All @@ -36,7 +37,7 @@ public final class Draggable: NSObject, Plan {
}

/// A gesture performer that enables its target to be dragged.
final class DraggablePerformer: NSObject, PlanPerforming {
final class DraggablePerformer: NSObject, PlanPerforming, ComposablePerforming {
let target: UIView

private var previousTranslation = CGPoint.zero
Expand All @@ -57,10 +58,14 @@ final class DraggablePerformer: NSObject, PlanPerforming {
if recognizer.view == nil {
target.addGestureRecognizer(recognizer)
}

if plan.shouldAdjustAnchorPointOnGestureStart {
recognizer.addTarget(self, action: #selector(modifyAnchorPoint(using:)))
}
}

func handle(gesture: UIPanGestureRecognizer) {
var translation = gesture.translation(in: target)
var translation = gesture.translation(in: target.superview)

if gesture.state == .began {
previousTranslation = CGPoint.zero
Expand All @@ -71,13 +76,29 @@ final class DraggablePerformer: NSObject, PlanPerforming {
translation.y -= previousTranslation.y
previousTranslation = originalTranslation

target.transform = target.transform.translatedBy(x: translation.x, y: translation.y)
target.center.x += translation.x
target.center.y += translation.y
}

func modifyAnchorPoint(using gesture: UIGestureRecognizer) {
if gesture.state != .began { return }

let transaction = makeAnchorPointAdjustmentTransaction(using: gesture, on: target)
emitter.emit(transaction: transaction)
}

/// Emitter setup
fileprivate var emitter: TransactionEmitting!

func set(transactionEmitter: TransactionEmitting) {
emitter = transactionEmitter
}
}

/// A plan that enables a target to be scaled by pinching.
public final class Pinchable: NSObject, Plan {
public let pinchGestureRecognizer: UIPinchGestureRecognizer
public var shouldAdjustAnchorPointOnGestureStart = true

public init(withGestureRecognizer recognizer: UIPinchGestureRecognizer = UIPinchGestureRecognizer()) {
self.pinchGestureRecognizer = recognizer
Expand All @@ -94,7 +115,7 @@ public final class Pinchable: NSObject, Plan {
}

/// A gesture performer that enables its target to be scaled by pinching.
private final class PinchablePerformer: NSObject, PlanPerforming {
private final class PinchablePerformer: NSObject, PlanPerforming, ComposablePerforming {
let target: UIView

private var previousScale: CGFloat = 1
Expand All @@ -115,6 +136,10 @@ private final class PinchablePerformer: NSObject, PlanPerforming {
if recognizer.view == nil {
target.addGestureRecognizer(recognizer)
}

if plan.shouldAdjustAnchorPointOnGestureStart {
recognizer.addTarget(self, action: #selector(modifyAnchorPoint(using:)))
}
}

func handle(gesture: UIPinchGestureRecognizer) {
Expand All @@ -126,11 +151,26 @@ private final class PinchablePerformer: NSObject, PlanPerforming {
target.transform = target.transform.scaledBy(x: newScale, y: newScale)
previousScale = gesture.scale
}

func modifyAnchorPoint(using gesture: UIGestureRecognizer) {
if gesture.state != .began { return }

let transaction = makeAnchorPointAdjustmentTransaction(using: gesture, on: target)
emitter.emit(transaction: transaction)
}

/// Emitter setup
fileprivate var emitter: TransactionEmitting!

func set(transactionEmitter: TransactionEmitting) {
emitter = transactionEmitter
}
}

/// A plan that enables a target to be rotated using a two-finger rotation gesture.
public final class Rotatable: NSObject, Plan {
public let rotationGestureRecognizer: UIRotationGestureRecognizer
public var shouldAdjustAnchorPointOnGestureStart = true

public init(withGestureRecognizer recognizer: UIRotationGestureRecognizer = UIRotationGestureRecognizer()) {
self.rotationGestureRecognizer = recognizer
Expand All @@ -147,7 +187,7 @@ public final class Rotatable: NSObject, Plan {
}

/// A gesture performer that enables its target to be rotated using a two-finger rotation gesture.
private final class RotatablePerformer: NSObject, PlanPerforming {
private final class RotatablePerformer: NSObject, PlanPerforming, ComposablePerforming {
let target: UIView

private var previousRotation: CGFloat = 0
Expand All @@ -168,11 +208,16 @@ private final class RotatablePerformer: NSObject, PlanPerforming {
if recognizer.view == nil {
target.addGestureRecognizer(recognizer)
}

if plan.shouldAdjustAnchorPointOnGestureStart {
recognizer.addTarget(self, action: #selector(modifyAnchorPoint(using:)))
}
}

func handle(gesture: UIGestureRecognizer) {
guard let gesture = gesture as? UIRotationGestureRecognizer else { return }

// Apply transform
if gesture.state == .began {
previousRotation = 0
}
Expand All @@ -181,8 +226,25 @@ private final class RotatablePerformer: NSObject, PlanPerforming {
target.transform = target.transform.rotated(by: rotation)
previousRotation = gesture.rotation
}

func modifyAnchorPoint(using gesture: UIGestureRecognizer) {
if gesture.state != .began { return }

let transaction = makeAnchorPointAdjustmentTransaction(using: gesture, on: target)
emitter.emit(transaction: transaction)
}

/// Emitter setup
fileprivate var emitter: TransactionEmitting!

func set(transactionEmitter: TransactionEmitting) {
emitter = transactionEmitter
}

}

// MARK: - Directly Manipulable

/// A plan that enables its target to be dragged, pinched and rotated simultaneously.
public final class DirectlyManipulable: NSObject, Plan {
public var panGestureRecognizer: UIPanGestureRecognizer {
Expand All @@ -199,15 +261,8 @@ public final class DirectlyManipulable: NSObject, Plan {
fileprivate let pinchable: Pinchable
fileprivate let rotatable: Rotatable

public override convenience init() {
self.init(draggable: Draggable(), pinchable: Pinchable(), rotatable: Rotatable())
}

/// A private init to assist in making copies
///
/// Note that we can't provide defaults for the parameters,
/// else it will collide with the convenience init()
private init(draggable: Draggable, pinchable: Pinchable, rotatable: Rotatable) {
/// Initializes a DirectlyManipulable plan using user-provided subplans, if provided.
public init(draggable: Draggable = Draggable(), pinchable: Pinchable = Pinchable(), rotatable: Rotatable = Rotatable()) {
self.draggable = draggable
self.pinchable = pinchable
self.rotatable = rotatable
Expand Down Expand Up @@ -249,18 +304,19 @@ final class DirectlyManipulablePerformer: NSObject, PlanPerforming, ComposablePe
transaction.add(plan: plan.pinchable, to: target)
transaction.add(plan: plan.rotatable, to: target)

// Set ourselves as each recognizer's delegate, if possible,
// in order to allow simultaneous recognition
for recognizer in gestureRecognizers {
if recognizer.delegate == nil {
recognizer.delegate = self
}
// Set ourselves as each recognizer's delegate, if possible,
// in order to allow simultaneous recognition
if recognizer.delegate == nil {
recognizer.delegate = self
}
}

emitter.emit(transaction: transaction)
}

private var emitter: TransactionEmitting!
/// Emitter setup
fileprivate var emitter: TransactionEmitting!

func set(transactionEmitter: TransactionEmitting) {
emitter = transactionEmitter
Expand All @@ -273,3 +329,64 @@ extension DirectlyManipulablePerformer: UIGestureRecognizerDelegate {
return gestureRecognizers.contains(otherGestureRecognizer)
}
}

// MARK: - Anchor Point Handling

/// A plan that modifies the anchor point of its target
public final class ChangeAnchorPoint: NSObject, Plan {
let anchorPoint: CGPoint

public init(withAnchorPoint anchorPoint: CGPoint) {
self.anchorPoint = anchorPoint
super.init()
}

public func performerClass() -> AnyClass {
return AnchorPointPerformer.self
}

public func copy(with zone: NSZone? = nil) -> Any {
return ChangeAnchorPoint(withAnchorPoint: anchorPoint)
}
}

private final class AnchorPointPerformer: NSObject, PlanPerforming {
let target: UIView

init(target: Any) {
self.target = target as! UIView
super.init()
}

func add(plan: Plan) {
guard let plan = plan as? ChangeAnchorPoint else {
fatalError("AnchorPointPerformer can only add ChangeAnchorPoint plans.")
}

let newPosition = CGPoint(x: plan.anchorPoint.x * target.layer.bounds.width,
y: plan.anchorPoint.y * target.layer.bounds.height)

let positionInSuperview = target.convert(newPosition, to: target.superview)

target.layer.anchorPoint = plan.anchorPoint
target.layer.position = positionInSuperview
}
}

/// Creates and returns a Transaction consisting of a ChangeAnchorPoint plan.
///
/// - Parameter gestureRecognizer: Recognizer used to determine touch location
/// - Parameter target: The view that will have its anchor point changed
///
/// - Returns: A Transaction consisting of a ChangeAnchorPoint plan
private func makeAnchorPointAdjustmentTransaction(using gestureRecognizer: UIGestureRecognizer, on target: UIView) -> Transaction {
// Determine the new anchor point
let locationInView = gestureRecognizer.location(in: target)
let anchorPoint = CGPoint(x: locationInView.x / target.bounds.width, y: locationInView.y / target.bounds.height)

// Create a transaction around the ChangeAnchorPoint plan
let transaction = Transaction()
transaction.add(plan: ChangeAnchorPoint(withAnchorPoint: anchorPoint), to: target)

return transaction
}

0 comments on commit 67e8985

Please sign in to comment.