Skip to content

SpringAnimations #600

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 5 additions & 9 deletions C4/UI/C4Gradient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,10 @@
import UIKit

///The C4Gradient class draws a color gradient over its background color, filling the shape of the view (including rounded corners).
public class C4Gradient : C4View {
class GradientView : UIView {
public class C4Gradient: C4View {
class GradientView: UIView {
var gradientLayer: C4GradientLayer {
get {
return self.layer as! C4GradientLayer
}
return self.layer as! C4GradientLayer
}

override class func layerClass() -> AnyClass {
Expand All @@ -35,13 +33,11 @@ public class C4Gradient : C4View {

///The background layer of the receiver.
public var gradientLayer: C4GradientLayer {
get {
return self.gradientView.gradientLayer
}
return gradientView.gradientLayer
}

var gradientView: GradientView {
return self.view as! GradientView
return view as! GradientView
}

///An array of C4Color objects defining the color of each gradient stop. Animatable.
Expand Down
8 changes: 6 additions & 2 deletions C4/UI/C4GradientLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,13 @@ public class C4GradientLayer: CAGradientLayer {
if C4ShapeLayer.disableActions == true {
return nil
}


let animation = C4ViewAnimation.stack.first as? C4ViewAnimation

let shouldSpring = animation?.spring == nil ? false : true

if key == "colors" {
let animation = CABasicAnimation(keyPath: key)
let animation = shouldSpring ? CASpringAnimation(keyPath: key) : CABasicAnimation(keyPath: key)
animation.configureOptions()
animation.fromValue = self.colors
return animation;
Expand Down
7 changes: 7 additions & 0 deletions C4/UI/C4Image.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ public class C4Image: C4View, NSCopying {

//MARK: Initializers

/// Initializes an empty C4Image
public override init() {
super.init()
let uiimage = UIImage()
self.view = ImageView(image: uiimage)
}

/// Initializes a new C4Image using the specified filename from the bundle (i.e. your project), it will also grab images
/// from the web if the filename starts with http.
///
Expand Down
6 changes: 5 additions & 1 deletion C4/UI/C4ImageLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,12 @@ public class C4ImageLayer: CALayer {
return nil
}

let animation = C4ViewAnimation.stack.first as? C4ViewAnimation

let shouldSpring = animation?.spring == nil ? false : true

if key == "contents" {
let animation = CABasicAnimation(keyPath: key)
let animation = shouldSpring ? CASpringAnimation(keyPath: key) : CABasicAnimation(keyPath: key)
animation.configureOptions()
animation.fromValue = self.contents
return animation;
Expand Down
51 changes: 49 additions & 2 deletions C4/UI/C4Shape.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import UIKit

/// C4Shape is a concrete subclass of C4View that draws a bezier path in its coordinate space.
public class C4Shape: C4View {

internal class ShapeView : UIView {
var shapeLayer: C4ShapeLayer {
get {
Expand Down Expand Up @@ -56,6 +55,9 @@ public class C4Shape: C4View {
lineWidth = 1
lineCap = .Round
lineJoin = .Round

let image = UIImage.createWithColor(UIColor.clearColor(), size: CGSize(width: 1, height: 1)).CGImage
shapeLayer.contents = image
}

/// Initializest a new C4Shape from a specified C4Path.
Expand All @@ -71,7 +73,49 @@ public class C4Shape: C4View {
updatePath()
adjustToFitPath()
}


public convenience init(_ shape: C4Shape) {
self.init()
let disable = C4ShapeLayer.disableActions
C4ShapeLayer.disableActions = true
self.path = shape.path
shapeLayer.path = path?.CGPath
self.frame = shape.frame
self.lineWidth = shape.lineWidth
self.lineDashPhase = shape.lineDashPhase
self.lineCap = shape.lineCap
self.lineJoin = shape.lineJoin
self.lineDashPattern = shape.lineDashPattern
self.fillColor = shape.fillColor
self.strokeColor = shape.strokeColor
self.strokeStart = shape.strokeStart
self.strokeEnd = shape.strokeEnd
self.miterLimit = shape.miterLimit
updatePath()
adjustToFitPath()
C4ShapeLayer.disableActions = disable
}

public var gradientFill: C4Gradient? {
didSet {
if let fill = gradientFill {
if mask == nil {
let m = C4Shape(self)
m.fillColor = black
m.strokeColor = black
mask = m
}
fillColor = nil
shapeLayer.contents = fill.render()?.cgimage
} else {
let image = UIImage.createWithColor(UIColor.clearColor(), size: CGSize(width: 1, height: 1)).CGImage
shapeLayer.contents = image
return
}

}
}

/// The path defining the shape to be rendered. If the path extends outside the layer bounds it will not automatically
/// be clipped to the layer. Defaults to nil. Animatable.
public var path: C4Path? {
Expand Down Expand Up @@ -133,6 +177,9 @@ public class C4Shape: C4View {
}
set(color) {
shapeLayer.fillColor = color?.CGColor
if color != nil {
gradientFill = nil
}
}
}

Expand Down
42 changes: 34 additions & 8 deletions C4/UI/C4ShapeLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,47 +40,56 @@ public class C4ShapeLayer: CAShapeLayer {
if C4ShapeLayer.disableActions == true {
return nil
}


let animation = C4ViewAnimation.stack.first as? C4ViewAnimation

let shouldSpring = animation?.spring == nil ? false : true

if key == "lineWidth" {
let animation = CABasicAnimation(keyPath: key)
let animation = shouldSpring ? CASpringAnimation(keyPath: key) : CABasicAnimation(keyPath: key)
animation.configureOptions()
animation.fromValue = self.lineWidth
return animation;
}
else if key == "strokeEnd" {
let animation = CABasicAnimation(keyPath: key)
let animation = shouldSpring ? CASpringAnimation(keyPath: key) : CABasicAnimation(keyPath: key)
animation.configureOptions()
animation.fromValue = self.strokeEnd
return animation;
}
else if key == "strokeStart" {
let animation = CABasicAnimation(keyPath: key)
let animation = shouldSpring ? CASpringAnimation(keyPath: key) : CABasicAnimation(keyPath: key)
animation.configureOptions()
animation.fromValue = self.strokeStart
return animation;
}
else if key == "strokeColor" {
let animation = CABasicAnimation(keyPath: key)
let animation = shouldSpring ? CASpringAnimation(keyPath: key) : CABasicAnimation(keyPath: key)
animation.configureOptions()
animation.fromValue = self.strokeColor
return animation;
}
else if key == "path" {
let animation = CABasicAnimation(keyPath: key)
let animation = shouldSpring ? CASpringAnimation(keyPath: key) : CABasicAnimation(keyPath: key)
animation.configureOptions()
animation.fromValue = self.path
return animation;
}
else if key == "fillColor" {
let animation = CABasicAnimation(keyPath: key)
let animation = shouldSpring ? CASpringAnimation(keyPath: key) : CABasicAnimation(keyPath: key)
animation.configureOptions()
animation.fromValue = self.fillColor
return animation;
} else if key == "lineDashPhase" {
let animation = CABasicAnimation(keyPath: key)
let animation = shouldSpring ? CASpringAnimation(keyPath: key) : CABasicAnimation(keyPath: key)
animation.configureOptions()
animation.fromValue = self.lineDashPhase
return animation;
} else if key == "contents" {
let animation = CABasicAnimation(keyPath: key)
animation.configureOptions()
animation.fromValue = self.contents
return animation;
}

return super.actionForKey(key)
Expand All @@ -100,3 +109,20 @@ extension CABasicAnimation {
self.removedOnCompletion = false
}
}

extension CASpringAnimation {
/// Configures basic options for a CABasicAnimation.
///
/// The options set in this method are favorable for the inner workings of C4's animation behaviours.
public override func configureOptions() {
super.configureOptions()
if let animation = C4ViewAnimation.stack.last as? C4ViewAnimation,
let spring = animation.spring {

self.mass = CGFloat(spring.mass)
self.damping = CGFloat(spring.damping)
self.stiffness = CGFloat(spring.stiffness)
self.initialVelocity = CGFloat(spring.initialVelocity)
}
}
}
96 changes: 95 additions & 1 deletion C4/UI/C4View.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,104 @@ extension NSValue {
}
}

public class C4Layer: CALayer {
static let rotationKey = "rotation"

private var _rotation = 0.0

public dynamic var rotation: Double {
return _rotation
}

public override init() {
super.init()
}

public override init(layer: AnyObject) {
super.init(layer: layer)
if let layer = layer as? C4Layer {
_rotation = layer._rotation
}
}

public required init?(coder: NSCoder) {
super.init(coder: coder)
}

public override func setValue(value: AnyObject?, forKey key: String) {
super.setValue(value, forKey: key)
if key == C4Layer.rotationKey {
_rotation = value as? Double ?? 0.0
}
}

public override func actionForKey(key: String) -> CAAction? {
if key == C4Layer.rotationKey {
let animation = CABasicAnimation(keyPath: key)
animation.configureOptions()
if let layer = presentationLayer() as? C4Layer {
animation.fromValue = layer.valueForKey(key)
}
return animation
}
return super.actionForKey(key)
}

public override class func needsDisplayForKey(key: String) -> Bool {
if key == C4Layer.rotationKey {
return true
}
return super.needsDisplayForKey(key)
}

public override func display() {
guard let presentation = presentationLayer() as? C4Layer else {
return
}
setValue(presentation._rotation, forKeyPath: "transform.rotation.z")
}
}

/// The C4View class defines a rectangular area on the screen and the interfaces for managing visual content in that area. The C4View class itself provides basic behavior for filling its rectangular area with a background color. More sophisticated content can be presented by subclassing UIView and implementing the necessary drawing and event-handling code yourself. The C4 framework also includes a set of standard subclasses that range from simple shapes to movies and images that can be used as-is.
public class C4View : NSObject {
/// A UIView. Internally, C4View wraps and provides access to an internal UIView.
public var view : UIView = UIView()
public var view : UIView = LayerView()

/// The current rotation value of the view. Animatable.
///
/// - returns: A Double value representing the cumulative rotation of the view, measured in Radians.
public var rotation: Double {
get {
if let number = animatableLayer.valueForKeyPath(C4Layer.rotationKey) as? NSNumber {
return number.doubleValue
}
return 0.0
}
set {
animatableLayer.setValue(newValue, forKeyPath: C4Layer.rotationKey)
}
}

internal var layerView: LayerView {
return self.view as! LayerView
}

internal class LayerView: UIView {
var animatableLayer: C4Layer {
return self.layer as! C4Layer
}

override class func layerClass() -> AnyClass {
return C4Layer.self
}
}

/// The view's primary layer.
///
/// - returns: A C4Layer, whose properties are animatable (e.g. rotation)
public var animatableLayer: C4Layer {
return self.layerView.animatableLayer
}

/// Initializes a C4View.
public override init() {
Expand Down
Loading