Skip to content

Commit

Permalink
Change layer's position after animation stops.
Browse files Browse the repository at this point in the history
Add setAnchorPoint, willStop, withDisableActions.
Add layer dragging feature.
  • Loading branch information
rhcad committed Feb 6, 2015
1 parent abaf829 commit a69079a
Show file tree
Hide file tree
Showing 17 changed files with 325 additions and 61 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -6,6 +6,7 @@
#
# Pods/

build
.DS_Store
xcuserdata
*.xccheckout
Binary file added Documentation/drag.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -44,6 +44,7 @@ SVG animation development with [SVGKit](https://github.com/SVGKit/SVGKit) happen
![Jumping Ball](Documentation/jumpball.gif)

![Animation with Sliders](Documentation/ellipse_slider.gif)
![Drag Layers](Documentation/drag.gif)

## Usage

Expand Down
8 changes: 8 additions & 0 deletions ShapeAnimation.xcodeproj/project.pbxproj
Expand Up @@ -7,6 +7,8 @@
objects = {

/* Begin PBXBuildFile section */
02147CA11A836E2A001E5617 /* MasterViewController+Drag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02147CA01A836E2A001E5617 /* MasterViewController+Drag.swift */; };
02147CAC1A847EA3001E5617 /* CALayer+Drag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02147CAB1A847EA3001E5617 /* CALayer+Drag.swift */; };
022E98131A8071A70028A198 /* ShapeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022E98091A8071A70028A198 /* ShapeView.swift */; };
022E98141A8071A70028A198 /* AnimationLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022E980B1A8071A70028A198 /* AnimationLayer.swift */; };
022E98151A8071A70028A198 /* AnimationPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022E980C1A8071A70028A198 /* AnimationPair.swift */; };
Expand Down Expand Up @@ -100,6 +102,8 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
02147CA01A836E2A001E5617 /* MasterViewController+Drag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MasterViewController+Drag.swift"; sourceTree = "<group>"; };
02147CAB1A847EA3001E5617 /* CALayer+Drag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CALayer+Drag.swift"; sourceTree = "<group>"; };
022E98091A8071A70028A198 /* ShapeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShapeView.swift; sourceTree = "<group>"; };
022E980B1A8071A70028A198 /* AnimationLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationLayer.swift; sourceTree = "<group>"; };
022E980C1A8071A70028A198 /* AnimationPair.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationPair.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -175,6 +179,7 @@
isa = PBXGroup;
children = (
022E980E1A8071A70028A198 /* CALayer+Animation.swift */,
02147CAB1A847EA3001E5617 /* CALayer+Drag.swift */,
022E98101A8071A70028A198 /* CAShapeLayer+Animation.swift */,
022E980B1A8071A70028A198 /* AnimationLayer.swift */,
022E980F1A8071A70028A198 /* CALayer+Pause.swift */,
Expand Down Expand Up @@ -210,6 +215,7 @@
children = (
02CD3F081A70907600C83B5C /* AppDelegate.swift */,
02CD3F0A1A70907600C83B5C /* MasterViewController.swift */,
02147CA01A836E2A001E5617 /* MasterViewController+Drag.swift */,
02CD3F0C1A70907600C83B5C /* DetailViewController.swift */,
02E8A7EB1A7A3B9500EB03C3 /* EllipseViewController.swift */,
02CD3F0E1A70907600C83B5C /* Main.storyboard */,
Expand Down Expand Up @@ -489,6 +495,7 @@
buildActionMask = 2147483647;
files = (
02CD3F0D1A70907600C83B5C /* DetailViewController.swift in Sources */,
02147CA11A836E2A001E5617 /* MasterViewController+Drag.swift in Sources */,
02E8A7EC1A7A3B9500EB03C3 /* EllipseViewController.swift in Sources */,
02CD3F0B1A70907600C83B5C /* MasterViewController.swift in Sources */,
02CD3F091A70907600C83B5C /* AppDelegate.swift in Sources */,
Expand All @@ -512,6 +519,7 @@
022E98161A8071A70028A198 /* AnimationPrivate.swift in Sources */,
022E98131A8071A70028A198 /* ShapeView.swift in Sources */,
022E98191A8071A70028A198 /* CAShapeLayer+Animation.swift in Sources */,
02147CAC1A847EA3001E5617 /* CALayer+Drag.swift in Sources */,
AEE57A151A81168F00420D54 /* ShapeView+Image.swift in Sources */,
022E98151A8071A70028A198 /* AnimationPair.swift in Sources */,
022E98201A8072270028A198 /* Gradient+Layer.swift in Sources */,
Expand Down
43 changes: 35 additions & 8 deletions ShapeAnimation/Animation/AnimationPair.swift
Expand Up @@ -53,7 +53,7 @@ public func applyAnimations(animations:[AnimationPair], completion:(() -> Void)?
public class AnimationPair {
public let layer:CALayer
public let animation:CAAnimation
public var key:String
public let key:String

init(_ layer:CALayer, _ animation:CAAnimation, key:String) {
self.layer = layer
Expand All @@ -71,11 +71,6 @@ public class AnimationPair {
return self
}

public func setKey(key:String) -> AnimationPair {
self.key = key
return self
}

public func setDuration(d:CFTimeInterval) -> AnimationPair {
animation.duration = d
if let group = animation as? CAAnimationGroup {
Expand All @@ -87,6 +82,10 @@ public class AnimationPair {
return self
}

public func setBeginTime(gap:CFTimeInterval) -> AnimationPair {
return setBeginTime(1, gap:gap)
}

public func setBeginTime(index:Int, gap:CFTimeInterval) -> AnimationPair {
animation.beginTime = CACurrentMediaTime() + Double(index) * gap
return self
Expand All @@ -100,10 +99,16 @@ public class AnimationPair {

public func apply() {
if !CAAnimation.isStopping {
layer.addAnimation(animation, forKey:key)
if let gradientLayer = layer.gradientLayer {
gradientLayer.addAnimation(animation, forKey:key)
let anim2 = animation.copy() as CAAnimation
anim2.delegate = AnimationDelagate()
if let layerid = layer.identifier {
anim2.setValue(layerid + "_gradient", forKey:"layerID")
}
gradientLayer.addAnimation(anim2, forKey:key)
}
animation.setValue(layer.identifier, forKey:"layerID")
layer.addAnimation(animation, forKey:key)
}
}

Expand All @@ -123,3 +128,25 @@ public class AnimationPair {
apply()
}
}

// MARK: setAnchorPoint just change anchorPoint, and not change position

public extension CALayer {
public func setAnchorPoint(point:CGPoint, fromLayer:CALayer? = nil) {
let oldframe = self.frame
if let fromLayer = fromLayer {
let pt = convertPoint(point, fromLayer:fromLayer) / bounds.size
self.anchorPoint = pt
} else {
self.anchorPoint = point
}
self.frame = oldframe
}
}

public extension AnimationPair {
public func setAnchorPoint(point:CGPoint, fromLayer:CALayer? = nil) -> AnimationPair {
layer.setAnchorPoint(point, fromLayer:fromLayer)
return self
}
}
40 changes: 40 additions & 0 deletions ShapeAnimation/Animation/AnimationPrivate.swift
Expand Up @@ -14,18 +14,31 @@ public class AnimationDelagate : NSObject {

public var didStart:((CAAnimation!) -> Void)?
public var didStop :(() -> Void)?
public var willStop :(() -> Void)?
public var finished = true

override public func animationDidStart(anim:CAAnimation!) {
didStart?(anim)
}

override public func animationDidStop(anim:CAAnimation!, finished:Bool) {
/*
let keypath = (anim as? CAPropertyAnimation)?.keyPath
let name = keypath != nil ? keypath! : anim.description
if let layerid = anim.valueForKey("layerID") as? String {
println("animationDidStop \(layerid) \(name)")
} else {
println("animationDidStop \(name)")
}*/

self.finished = finished
if finished {
self.willStop?()
self.didStop?()
} else {
stopping++
self.willStop?()
self.didStop?()
stopping--
}
Expand Down Expand Up @@ -78,6 +91,23 @@ public extension CAAnimation {
}
}

public var willStop:(() -> Void)? {
get {
let delegate = self.delegate as? AnimationDelagate
return delegate?.willStop
}
set {
if let delegate = self.delegate as? AnimationDelagate {
delegate.willStop = newValue
}
else if newValue != nil {
var delegate = AnimationDelagate()
delegate.willStop = newValue
self.delegate = delegate
}
}
}

public var finished:Bool {
get {
if let delegate = self.delegate as? AnimationDelagate {
Expand All @@ -104,3 +134,13 @@ public extension CAAnimation {

public class var isStopping:Bool { return stopping > 0 }
}

public func withDisableActions(layer:CALayer, animation:CAAnimation, block:() -> Void) {
let forwards = animation.fillMode == kCAFillModeForwards || animation.fillMode == kCAFillModeBoth
if !animation.autoreverses && forwards {
let old = CATransaction.disableActions()
CATransaction.setDisableActions(true)
block()
CATransaction.setDisableActions(old)
}
}
72 changes: 50 additions & 22 deletions ShapeAnimation/Animation/CALayer+Animation.swift
Expand Up @@ -10,26 +10,30 @@ import SwiftGraphics

public extension CALayer {
typealias Radians = CGFloat
typealias RelativePoint = CGPoint

func opacityAnimation(#from:Float, to:Float, didStop:(() -> Void)? = nil) -> AnimationPair {
let animation = CABasicAnimation(keyPath:"opacity")
animation.duration = 0.8
animation.fromValue = from
animation.toValue = to
animation.didStop = didStop
animation.removedOnCompletion = false
animation.fillMode = kCAFillModeForwards
animation.didStop = didStop
animation.willStop = {
withDisableActions(self, animation) {
self.opacity = to
}
self.removeAnimationForKey("opacity")
}
return AnimationPair(self, animation, key:"opacity")
}

func flashAnimation(repeatCount n:Float = 2, didStop:(() -> Void)? = nil) -> AnimationPair {
let anim = opacityAnimation(from:0, to:1, didStop:didStop)
anim.key = "flash"
return anim.set {
$0.repeatCount = n
$0.autoreverses = true
$0.fillMode = kCAFillModeRemoved
$0.removedOnCompletion = true
$0.duration = 0.2
}
}
Expand All @@ -39,16 +43,24 @@ public extension CALayer {
animation.duration = 0.8
animation.fromValue = from
animation.toValue = to
animation.didStop = didStop
animation.removedOnCompletion = false
animation.fillMode = kCAFillModeForwards
animation.didStop = didStop
animation.willStop = {
withDisableActions(self, animation) {
let xf = CGAffineTransform(scale:CGFloat(to))
self.setAffineTransform(self.affineTransform() + xf)
}
self.removeAnimationForKey("scale")
}
return AnimationPair(self, animation, key:"scale")
}

func scaleAnimation(#from:Float, to:Float, repeatCount:Float, didStop:(() -> Void)? = nil) -> AnimationPair {
return scaleAnimation(from:from, to:to, didStop:didStop).set {
$0.repeatCount=repeatCount
$0.autoreverses = repeatCount > 1
$0.fillMode = kCAFillModeRemoved
}
}

Expand All @@ -62,14 +74,16 @@ public extension CALayer {
animation.additive = true
animation.fromValue = 0.0
animation.toValue = angle
animation.didStop = {
let old = CATransaction.disableActions()
CATransaction.setDisableActions(true)
self.setAffineTransform(CGAffineTransform(rotation:angle))
CATransaction.setDisableActions(old)
didStop?()
}
animation.removedOnCompletion = false
animation.fillMode = kCAFillModeForwards
animation.didStop = didStop
animation.willStop = {
withDisableActions(self, animation) {
let xf = CGAffineTransform(rotation:angle)
self.setAffineTransform(self.affineTransform() + xf)
}
self.removeAnimationForKey("rotation")
}
return AnimationPair(self, animation, key:"rotation")
}

Expand All @@ -80,25 +94,30 @@ public extension CALayer {
animation.duration = 0.8
animation.additive = true
animation.didStop = didStop
animation.removedOnCompletion = false
animation.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut)
return AnimationPair(self, animation, key:"shake")
}

func moveAnimation(#to:RelativePoint) -> AnimationPair {
return moveAnimation(from:CGPoint.zeroPoint, to:to)
func moveAnimation(#to:CGPoint, relative:Bool = true) -> AnimationPair {
return moveAnimation(from:relative ? CGPoint.zeroPoint : position, to:to, relative:relative)
}

func moveAnimation(#from:RelativePoint, to:RelativePoint, didStop:(() -> Void)? = nil) -> AnimationPair {
func moveAnimation(#from:CGPoint, to:CGPoint, relative:Bool = true, didStop:(() -> Void)? = nil) -> AnimationPair {
let animation = CABasicAnimation(keyPath:"position")
animation.duration = 0.3
animation.additive = true
animation.additive = relative
animation.fromValue = NSValue(CGPoint: from)
animation.toValue = NSValue(CGPoint: to)
animation.didStop = didStop
animation.removedOnCompletion = false
animation.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut)
animation.fillMode = kCAFillModeForwards
animation.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseOut)
animation.didStop = didStop
animation.willStop = {
withDisableActions(self, animation) {
self.position = relative ? self.position + to : to
}
self.removeAnimationForKey("move")
}
return AnimationPair(self, animation, key:"move")
}

Expand All @@ -107,13 +126,23 @@ public extension CALayer {
animation.keyPath = "position"
animation.path = path
animation.duration = 0.8
animation.didStop = didStop
animation.removedOnCompletion = false
animation.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut)
animation.fillMode = kCAFillModeForwards
animation.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut)
if autoRotate {
animation.rotationMode = kCAAnimationRotateAuto
}
animation.didStop = didStop
animation.willStop = {
self.removeAnimationForKey("moveOnPath")
withDisableActions(self, animation) {
self.position = path.endPoint
if autoRotate {
let xf = CGAffineTransform(rotation:path.endTangent.direction)
self.setAffineTransform(self.affineTransform() + xf)
}
}
}
return AnimationPair(self, animation, key:"moveOnPath")
}

Expand All @@ -125,7 +154,6 @@ public extension CALayer {
slide.duration = 0.8
slide.didStop = didStop
slide.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
slide.fillMode = kCAFillModeRemoved
return AnimationPair(self, slide, key:"slide")
}

Expand Down
25 changes: 25 additions & 0 deletions ShapeAnimation/Animation/CALayer+Drag.swift
@@ -0,0 +1,25 @@
//
// CALayer+Drag.swift
// ShapeAnimation
//
// Created by Zhang Yungui on 15/2/6.
// Copyright (c) 2015 github.com/rhcad. All rights reserved.
//

import SwiftGraphics

public extension CALayer {
func constrainCenterToSuperview(center:CGPoint) {
let kEdgeBuffer:CGFloat = 4
var constrain = superlayer.bounds.insetted(dx:kEdgeBuffer, dy:kEdgeBuffer)
constrain.inset(dx: frame.width / 2, dy: frame.height / 2)
let pt = constrain.isEmpty ? superlayer.bounds.mid : center.clamped(constrain)
moveAnimation(to: pt, relative:false).apply()
}

func bringOnScreen() {
if !superlayer.bounds.contains(frame) {
constrainCenterToSuperview(position)
}
}
}

0 comments on commit a69079a

Please sign in to comment.