Auto layout and UIViewAnimation - Gap between two connected views

I created a "tooltip" view that has a UILabel and an UIView with a CAShapeLayer for a triangle shape. I have setup the "tooltip" so that the label is on top, with the triangle attached to the bottom and centered on the UILabel.

When I show the "tooltip", I use the UIViewAnimation with Spring Damping and Spring Velocity to give it a "pop" animation. This works great with one exception, a small gap can be noticed between the triangle and the UILabel during the beginning of the animation (which is then fixed when the animation ends.

Any suggestions on how to fix this?

Here is the view/constraint setup:

    let containerView = UIView()
    containerView.alpha = 1.0
    containerView.layer.cornerRadius = 5.0
    containerView.clipsToBounds = true
    containerView.backgroundColor = UIColor.orangeColor()
    self.addSubview(containerView)
    self.containerView = containerView

    let titleLabel = UILabel()
    titleLabel.font = UIFont.systemFontOfSize(14.0)
    titleLabel.textColor = UIColor.whiteColor()
    titleLabel.numberOfLines = 0
    titleLabel.adjustsFontSizeToFitWidth = true
    containerView.addSubview(titleLabel)
    self.titleLabel = titleLabel

    let triangleView = UIView()
    self.addSubview(triangleView)
    self.triangleView = triangleView

    let views: [String: UIView] = [
        "containerView"  : containerView,
        "titleLabel"  : titleLabel,
        "triangleView"  : triangleView,
    ]
    let metrics = [String:AnyObject]()
    for (_, view) in views {
        view.translatesAutoresizingMaskIntoConstraints = false
    }

    NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|-8-[titleLabel]-8-|", options: [], metrics: metrics, views: views))
    NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-8-[titleLabel]-8-|", options: [], metrics: metrics, views: views))

    let widthConstraint = NSLayoutConstraint(item: self, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 0)
    widthConstraint.active = true
    self.widthConstraint = widthConstraint

    let heightConstraint = NSLayoutConstraint(item: self, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 0)
    heightConstraint.active = true
    self.heightConstraint = heightConstraint

    let trianglePath = UIBezierPath()
    trianglePath.moveToPoint(CGPoint(x: 0, y: 0))
    trianglePath.addLineToPoint(CGPoint(x: 8.0, y: 10.0))
    trianglePath.addLineToPoint(CGPoint(x: 16.0, y: 0))
    trianglePath.closePath()

    let mask = CAShapeLayer()
    mask.frame = triangleView.bounds
    mask.path = trianglePath.CGPath
    triangleView.layer.mask = mask

    NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[containerView][triangleView]|", options: [], metrics: metrics, views: views))
    NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[containerView]|", options: [], metrics: metrics, views: views))
    NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|-(>=8)-[self]-(>=8)-|", options: [], metrics: metrics, views: views))
    NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-(>=8)-[self][anchorView]", options: [], metrics: metrics, views: views))

    NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("[triangleView(>=16)]", options: [], metrics: metrics, views: views))
    NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[triangleView(>=10)]", options: [], metrics: metrics, views: views))

    NSLayoutConstraint(item: self.triangleView, attribute: .CenterX, relatedBy: .Equal, toItem: self.anchorView, attribute: .CenterX, multiplier: 1.0, constant: 1.0).active = true

    let centerXConstraint = NSLayoutConstraint(item: triangleView, attribute: .CenterX, relatedBy: .Equal, toItem: containerView, attribute: .CenterX, multiplier: 1.0, constant: 0.0)
    centerXConstraint.priority = UILayoutPriorityDefaultLow // Required to allow tooltip to grow beyond anchorView bounds without changing the anchorView constraints.
    centerXConstraint.active = true

Here is the animation script:

    self.layoutIfNeeded() // Set starting position for tooltip before animation

    self.widthConstraint.active = false
    self.heightConstraint.active = false

    UIView.animateWithDuration(0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.5, options: .CurveEaseInOut, animations: { () -> Void in
        self.alpha = 1.0
        self.layoutIfNeeded()
        }, completion: nil)

Video:

Answers


One way to approach it would be to have a custom UILabel

class ToolTip: UILabel {
    var roundRect:CGRect!
    override func drawTextInRect(rect: CGRect) {
        super.drawTextInRect(roundRect)
    }
    override func drawRect(rect: CGRect) {
        roundRect = CGRect(x: rect.minX, y: rect.minY, width: rect.width, height: rect.height * 4 / 5)
        let roundRectBez = UIBezierPath(roundedRect: roundRect, cornerRadius: 10.0)
        let triangleBez = UIBezierPath()
        triangleBez.moveToPoint(CGPoint(x: roundRect.minX + roundRect.width / 2.5, y:roundRect.maxY))
        triangleBez.addLineToPoint(CGPoint(x:rect.midX,y:rect.maxY))
        triangleBez.addLineToPoint(CGPoint(x: roundRect.maxX - roundRect.width / 2.5, y:roundRect.maxY))
        triangleBez.closePath()
        roundRectBez.appendPath(triangleBez)
        let bez = roundRectBez
        UIColor.lightGrayColor().setFill()
        bez.fill()
        super.drawRect(rect)
    }
}

and then perform layout and animation like so:

import UIKit

class ViewController: UIViewController {

    var label:ToolTip!
    var labelTransform:CGAffineTransform!
    let buttonHeight:CGFloat = 100
    let buttonWidth:CGFloat = 200

    override func viewDidLoad() {
        super.viewDidLoad()

        let button = UIButton()
        button.setTitle("Push Me", forState: .Normal)
        button.addTarget(self, action: Selector("buttonPushed"), forControlEvents: .TouchUpInside)
        button.backgroundColor = UIColor.orangeColor()
        view.addSubview(button)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor).active = true
        button.centerYAnchor.constraintEqualToAnchor(view.centerYAnchor).active = true
        button.heightAnchor.constraintEqualToConstant(buttonHeight).active = true
        button.widthAnchor.constraintEqualToConstant(buttonWidth).active = true

        label = ToolTip()

        view.insertSubview(label, belowSubview: button)
        label.translatesAutoresizingMaskIntoConstraints = false
        label.heightAnchor.constraintEqualToConstant(buttonHeight).active = true
        label.widthAnchor.constraintEqualToConstant(buttonWidth).active = true


        label.bottomAnchor.constraintEqualToAnchor(button.topAnchor).active = true
        label.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor).active = true
        label.text = "This button is orange!"
        label.textColor = UIColor.whiteColor()
        label.textAlignment = .Center
        let trans1 =  CGAffineTransformMakeScale(0, 0)
        let trans2 =  CGAffineTransformMakeTranslation(0, buttonHeight)
        labelTransform = CGAffineTransformConcat(trans1, trans2)
        label.transform = labelTransform

    }

    func buttonPushed() {

        if label.transform.ty > 0 {
            UIView.animateWithDuration(0.75, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.5, options: .CurveEaseInOut, animations: { () -> Void in

                self.label.transform = CGAffineTransformIdentity

                }, completion:    nil)

        }
        else {
            UIView.animateWithDuration(0.5, delay: 0, options: .CurveEaseInOut, animations:  { () -> Void in
                self.label.alpha = 0

                }, completion:    {_ in
                    self.label.transform = self.labelTransform
                    self.label.alpha = 1
            })        }



    }

}

which creates the follow effect:


Need Your Help

Are there data sources for high fidelity cloud cover data?

weather weather-api

Is there anything near the resolution of Himawari-8 for the entire globe?

How to Use Captcha Plugin in CodeIgniter

codeigniter captcha

I followed this page, and the code is not working as it should in my project. I've tried to find other tutorial on how to execute what I need, however, it does not work as it should in my project e...