2

I'm trying to create a "reveal" effect from the bottom of an image. I thought it would be as simple as setting the anchorPoint to CGPointMake(0.5, 1.0) or the contentsGravity to kCAGravityTop, but none of these options works.

The current code I have works but animates from top to bottom. here's an idea of the reveal: http://giphy.com/gifs/xTiTnBzItdgaD1xHMc

How would I make this go from bottom-to-top ?

Here's the code

    let path                = UIBezierPath(rect: CGRectMake(0, 0, imgEmptyBase.width, imgEmptyBase.height - 80))
    let mask                = CAShapeLayer()
    mask.anchorPoint        = CGPointMake(0.5, 1.0)
    mask.path               = path.CGPath

    imgEmptyBase.layer.mask = mask

    let anim                = CABasicAnimation(keyPath: "path")

    anim.fromValue          = path.CGPath
    anim.toValue            = UIBezierPath(rect: imgEmptyBase.bounds).CGPath
    anim.duration           = 1.0
    anim.fillMode           = kCAFillModeForwards
    anim.removedOnCompletion = false

    imgEmptyBase.layer.mask.addAnimation(anim, forKey: "anim")
Shai Mishali
  • 9,224
  • 4
  • 56
  • 83

4 Answers4

6

This will achieve exactly the effect you're after:

let path = UIBezierPath(rect: CGRectMake(0, imgEmptyBase.layer.frame.size.height, imgEmptyBase.layer.frame.size.width, imgEmptyBase.layer.frame.size.height))
let mask = CAShapeLayer()
mask.path = path.CGPath

imgEmptyBase.layer.mask = mask

let revealPath = UIBezierPath(rect: CGRectMake(0, 0, imgEmptyBase.layer.frame.size.width, imgEmptyBase.layer.frame.size.height))

let anim                = CABasicAnimation(keyPath: "path")
anim.fromValue          = path.CGPath
anim.toValue            = revealPath.CGPath
anim.duration           = 1.0
anim.fillMode           = kCAFillModeForwards
anim.removedOnCompletion = false

imgEmptyBase.layer.mask.addAnimation(anim, forKey: "anim")

It's all about changing the mask path using the right "from" and "to" values. For example: setting the initial mask path to CGRectMake(0, 0, imgEmptyBase.layer.frame.size.width, 0) and the revealPath to CGRectMake(0, 0, imgEmptyBase.layer.frame.size.width, imgEmptyBase.layer.frame.size.height) would result in a "top to bottom" reveal effect.

Artal
  • 8,933
  • 2
  • 27
  • 30
1

My suggestion is to use the stroke of the path instead of the fill.

enter image description here

You draw a path (the blue line) that starts at the bottom of your layer and ends at the top of it, centered horizontally, and with a line width the same as the layer width.
The red outline represents the stroke outline.

You play an animation on the strokeEnd property from 0 to 1.

I made a playground if you want to see how I managed to make that effect.

I updated the link above.
I put the code in case you still can't get the playground

let aView = UIView(frame:CGRect(x:0 y:0 width:200 height:200))
aView.backgroundColor = UIColor.redColor() // the view that needs to be masked
let shapeLayer = CAShapeLayer()
shapeLayer.bounds = aView.bounds
let bezierPath = UIBezierPath()
bezierPath.moveToPoint(CGPoint(x: shapeLayer.bounds.width/2, y: shapeLayer.bounds.height))
bezierPath.addLineToPoint(CGPoint(x: shapeLayer.bounds.width/2, y: 0))
shapeLayer.path = bezierPath.CGPath
shapeLayer.fillColor = UIColor.clearColor().CGColor
shapeLayer.strokeColor = UIColor.redColor().CGColor
shapeLayer.lineWidth = shapeLayer.bounds.width
shapeLayer.position = CGPoint(x: aView.frame.width/2, y: aView.frame.height/2)
shapeLayer.strokeEnd = 1

let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 4
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
shapeLayer.addAnimation(animation, forKey: "stroneAnimation")
shapeLayer.strokeEnd = 1
aView.layer.mask = shapeLayer
Crazyrems
  • 2,551
  • 22
  • 40
0

There are (2) options that come to mind.

One is to just switch the animation from and to values:

let path                = UIBezierPath(rect: CGRectMake(0, 0, imgEmptyBase.width, imgEmptyBase.height - 80))
let mask                = CAShapeLayer()
mask.anchorPoint        = CGPointMake(0.5, 1.0)
mask.path               = path.CGPath

imgEmptyBase.layer.mask = mask

let anim                = CABasicAnimation(keyPath: "path")

anim.fromValue          = UIBezierPath(rect: imgEmptyBase.bounds).CGPath
anim.toValue            = path.CGPath
anim.duration           = 1.0
anim.fillMode           = kCAFillModeForwards
anim.removedOnCompletion = false

imgEmptyBase.layer.mask.addAnimation(anim, forKey: "anim")

The other is to switch the bezier path declarations themselves:

let path                = UIBezierPath(rect: imgEmptyBase.bounds).CGPath
let mask                = CAShapeLayer()
mask.anchorPoint        = CGPointMake(0.5, 1.0)
mask.path               = path.CGPath

imgEmptyBase.layer.mask = mask

let anim                = CABasicAnimation(keyPath: "path")

anim.fromValue          = path.CGPath
anim.toValue            = UIBezierPath(rect: CGRectMake(0, 0, imgEmptyBase.width, imgEmptyBase.height - 80))
anim.duration           = 1.0
anim.fillMode           = kCAFillModeForwards
anim.removedOnCompletion = false

imgEmptyBase.layer.mask.addAnimation(anim, forKey: "anim")
Dean
  • 939
  • 10
  • 30
0

Check this!!

let path = UIBezierPath(rect: CGRectMake(0, denterImage.frame.origin.y, denterImage.bounds.size.width, denterImage.bounds.size.height - 200))
let mask = CAShapeLayer()
mask.anchorPoint = CGPointMake(0.5, 1.0)
mask.path = path.CGPath

denterImage.layer.mask = mask

let anim = CABasicAnimation(keyPath: "path")

anim.fromValue = path.CGPath
anim.toValue = UIBezierPath(rect: denterImage.bounds).CGPath
anim.duration =  1.5
anim.fillMode = kCAFillModeForwards
anim.removedOnCompletion = false

denterImage.layer.mask.addAnimation(anim, forKey: "anim")
Jen Jose
  • 3,995
  • 2
  • 19
  • 36