Search code examples
swiftuiviewcore-animationcalayer

UIView with custom shaped frame: Animation not behaving as expected


I want to animate a drawer with a custom shape, however the expected popOut() animation appear incorrect.

What part did I missed to make it transition without a blink ?
Why is popIn() displaying ok but not popOut() ?

Edit:

I tried animating the layer with

        //pop
        self.layer.transform = CATransform3DMakeScale(1, 0, 1)

and

        //unPop
        let transform = CATransform3DConcat(
            CATransform3DMakeScale(1, 0, 1),
            CATransform3DMakeTranslation(0, 0 - self.frame.height / 2, 0))
        self.layer.transform = transform

It looks great,but I would like the top of my frame to be fixed, and only animate it by making it grow from the bottom...

Initial Result:

pic

Initial Code:

//   Points reference
//   1-------5
//   |       |
//   |       |
//   |       |
//   |       |
//   |       |
//   |       |
//   |       |
//   2\     /4
//     \   /
//      \ /
//       3



class PowerUpDrawer : UIView {
    var triangleHeight : CGFloat
    var orignalHeight : CGFloat = 0
    var orignalY : CGFloat = 0
    var isPoped : Bool = true {
        didSet {
            if (isPoped) {
                pop()
            } else {
                unPop()
            }
        }
    }


    //XXX
    override init(frame: CGRect) {
        triangleHeight = 25
        super.init(frame: frame)
    }

    //XXX
    required init(coder aDecoder: NSCoder) {
        triangleHeight = 25
        super.init(coder: aDecoder)
        orignalHeight = frame.height
        orignalY = frame.origin.y
    }


    override var frame: CGRect {
        didSet {
            let bezierpath = UIBezierPath()
            bezierpath.moveToPoint(CGPoint(x: 0, y: 0)) // 1
            bezierpath.addLineToPoint(CGPoint(x: 0, y:  frame.height - triangleHeight)) // 2
            bezierpath.addLineToPoint(CGPoint(x: frame.width / 2, y: frame.height))// 3
            bezierpath.addLineToPoint(CGPoint(x: frame.width, y: frame.height - triangleHeight))// 4
            bezierpath.addLineToPoint(CGPoint(x: frame.width, y: 0))// 5
            let mask = CAShapeLayer()
            mask.frame = bounds
            mask.path = bezierpath.CGPath
            self.layer.mask = mask
        }
    }


    @IBAction func toggle() {
        isPoped = !isPoped
    }


    func pop() {
        UIView.animateWithDuration(1, delay: 0.5, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.8, options: nil, animations: { () -> Void in
            self.frame = CGRectMake(self.frame.origin.x, self.orignalY, self.frame.width, self.orignalHeight)
            }) { (Bool) -> Void in
        };
    }


     func unPop() {
        UIView.animateWithDuration(1, delay: 0.5, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.8, options: nil, animations: { () -> Void in
            self.frame = CGRectMake(self.frame.origin.x, self.orignalY, self.frame.width, self.triangleHeight)
            }) { (Bool) -> Void in
        };
    }
}

Solution

  • This was an interesting problem. Can you try this as your unPop() function. I am offsetting enough height to make it feel like the top end is staying still. You can try any scaleFactor.

    func unPop() {
    
       UIView.animateWithDuration(1, delay: 0.5, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.8, options: nil, animations: { () -> Void in
    
            let scaleFactor:CGFloat = 0.25
            let offset = -(self.frame.height * (1 - scaleFactor))/2
            let transform = CATransform3DConcat(
                CATransform3DMakeScale(1, scaleFactor, 1),
                CATransform3DMakeTranslation(0, offset, 0))
            self.layer.transform = transform
        }) { (Bool) -> Void in
        };
    }