I have a XUIView class as below. When I run animation, it's no effect for folding.
Who can explain me ?
class ViewController: UIViewController {
// Width constraint of XUIView instance
#IBOutlet weak var vwWrapperWidth: NSLayoutConstraint! {
didSet{
self.vwWrapperWidth.constant = UIScreen.main.bounds.width
}
}
#IBAction func btnToggleTouchUp(_ sender: UIButton) {
if(self.vwWrapperWidth.constant == 55) {
// animation effect is OK when expanding
self.vwWrapperWidth.constant = UIScreen.main.bounds.width
UIView.animate(withDuration: 0.5, animations: {
self.view.layoutIfNeeded()
})
}
else {
// animation effect is not OK when folding
self.vwWrapperWidth.constant = 55
UIView.animate(withDuration: 0.5, animations: {
self.view.layoutIfNeeded()
})
}
}
//.....
}
#IBDesignable
class XUIView: UIView {
#IBInspectable
var roundTopLeftCorner: Bool = false
#IBInspectable
var roundBottomLeftCorner: Bool = false
#IBInspectable
var roundTopRightCorner: Bool = false
#IBInspectable
var roundBottomRightCorner: Bool = false
#IBInspectable
var cornerRadius: CGFloat = 0.0
#IBInspectable
var borderWidth: CGFloat = 0.0
#IBInspectable
var borderColor: UIColor?
fileprivate var borderLayer: CAShapeLayer? {
didSet{
self.layer.addSublayer(self.borderLayer!)
}
}
func roundCorners(_ corners: UIRectCorner) {
if(self.borderLayer == nil) { self.borderLayer = CAShapeLayer() }
let bounds = self.bounds
let maskPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: self.cornerRadius, height: self.cornerRadius))
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = maskPath.cgPath
self.layer.mask = maskLayer
self.borderLayer?.frame = bounds
self.borderLayer?.path = maskPath.cgPath
self.borderLayer?.strokeColor = self.borderColor?.cgColor
self.borderLayer?.lineWidth = self.borderWidth
self.borderLayer?.fillColor = nil
}
override func layoutSubviews() {
super.layoutSubviews()
var roundedCorners: UIRectCorner = []
if(roundTopLeftCorner) { roundedCorners.insert(.topLeft) }
if(roundTopRightCorner) { roundedCorners.insert(.topRight) }
if(roundBottomLeftCorner) { roundedCorners.insert(.bottomLeft) }
if(roundBottomRightCorner) { roundedCorners.insert(.bottomRight) }
roundCorners(roundedCorners)
}
}
source code : http://www.mediafire.com/file/n6svp1mk44fc0uf/TestXUIView.zip/file
You are experiencing no animation on the "folding" animation because CALayer's path property is not implicitly animatable. It is mentioned here.
Unlike most animatable properties, path (as with all CGPath animatable properties) does not support implicit animation.
To make matters worst, CALayer's frame property is not implicitly animatable either. It is mentioned here
Note: The frame property is not directly animatable. Instead you should animate the appropriate combination of the bounds, anchorPoint and position properties to achieve the desired result.
This simply means the following lines won't work, just because it is being called inside a UIView.animate block.
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds // This line won't animate!
maskLayer.path = maskPath.cgPath // This line won't animate!
self.layer.mask = maskLayer
Since it is not being animated, the mask is applied as soon as the layoutSubviews method in XUIView is called. View is actually animating, but you just cannot see it because the mask is being set before the animation completes.
This is also what happens in the expand animation. First the mask expands, then the animation occurs.
To prevent this, you might need to find a way to animate the width constraint and the layer frames & mask paths together.
The problem is that you are creating a CAShapeLayer in layoutSubviews which means that every time an animation occurs it gets call.
Try commenting line 91 and you are going to get what you want.
I frequently need to round only two corners in a view, and sometimes need to use gradients. I've found that the common solution of using a CALayerMask is detrimental to performance, so I devised my own solution overriding drawRect(rect: CGRect). It works well, providing an easy way to round some or all corners, draw a border, and use both linear and radial gradient fills, even being able to set color stops for the gradients.
Unfortunately, when I try to animate these properties with UIView.animateWithDuration, my corners, gradients, and borders don't animate. Rather, they look "stretched" in the initial state, then animate to the final state. I've read that this can be solved with CALayer animation, but I'm not quite clear on the nature of the problem. Is there a way I can solve this as the class is now? If not, when is drawRect(rect: CGRect) preferable to drawLayer(layer: CALayer, inContext ctx: CGContext)?
I'm also open to general suggestions on improving this class.
AppocalypseUI.swift (provides support functions for UI operations)
//
// AppocalypseUI.swift
// Soapbox
//
// Created by Joseph Falcone on 6/2/16.
// Copyright © 2016 Joseph Falcone. All rights reserved.
//
import UIKit
class AppocalypseUI: NSObject
{
/// Generates an array of CGFloat values ranging from 0.0-1.0 which represent the color stops in a gradient
class func makeLinearColorStops(numStops:Int) -> [CGFloat]
{
assert(numStops >= 2, "Must have at least two color stops.")
let stepIncrement = 1.0/Double(numStops-1)
var returnArr : [CGFloat] = []
// The first stop is always 0
returnArr += [0.0]
for i in 1 ..< numStops-1
{
let stepVal = stepIncrement*Double(i)
let stepFactor = CGFloat(fmod(stepVal, 1.0))
returnArr += [stepFactor]
}
// The last stop is always 1
returnArr += [1.0]
// Fini
return returnArr
}
/// Returns the stop colors in an array
class func colorsAlongArray(colorArr:[UIColor], steps:Int) -> [UIColor]
{
let arrCount = colorArr.count
let stepIncrement = Double(arrCount)/Double(steps)
var returnArr : [UIColor] = []
for i in 0..<steps
{
let stepVal = stepIncrement*Double(i)
let stepFactor = CGFloat(fmod(stepVal, 1.0))
let stepIndex1 = Int(floor(stepVal/1.0))
var stepIndex2 = Int(ceil(stepVal/1.0))
if(stepIndex2 > arrCount-1)
{stepIndex2 = arrCount-1}
let color1 = colorArr[stepIndex1]
let color2 = colorArr[stepIndex2]
let color = colorByInterpolatingColors(color1, color2: color2, factor: stepFactor)
returnArr += [color]
}
return returnArr
}
/// Returns a color between two colors on a gradient
class func colorByInterpolatingColors(color1:UIColor, color2:UIColor, factor:CGFloat) -> UIColor
{
let startComponent = CGColorGetComponents(color1.CGColor)
let endComponent = CGColorGetComponents(color2.CGColor)
let startAlpha = CGColorGetAlpha(color1.CGColor)
let endAlpha = CGColorGetAlpha(color2.CGColor)
let r = startComponent[0] + (endComponent[0] - startComponent[0]) * factor
let g = startComponent[1] + (endComponent[1] - startComponent[1]) * factor
let b = startComponent[2] + (endComponent[2] - startComponent[2]) * factor
let a = startAlpha + (endAlpha - startAlpha) * factor
return UIColor(red: r, green: g, blue: b, alpha: a)
}
/* No longer needed
class func getFloatArrayFromNSNumbers(numbers:[NSNumber]) -> [CGFloat]
{
var returnArr : [CGFloat] = []
for number in numbers
{
returnArr += [CGFloat(number.floatValue)]
}
return returnArr
}
*/
/// Returns an array containing the RGBA components of an array of colors
class func getFloatArrayFromUIColors(colors:[UIColor]) -> [CGFloat]
{
var returnArr : [CGFloat] = []
for color : UIColor in colors
{
var red : CGFloat = 0.0
var green : CGFloat = 0.0
var blue : CGFloat = 0.0
var alpha : CGFloat = 0.0
color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
/*
// This check and backup should probably be implemented later, but it seems to fail when it shouldn't...probably improper use of optionals
if(color?.getRed(&red, green: &green, blue: &blue , alpha: &alpha) == nil)
{
// If for some reason the above function call fails, try this method of getting RGBA instead
let components = CGColorGetComponents(color?.CGColor)
red = components[0]
green = components[1]
blue = components[2]
alpha = components[3]
}
*/
returnArr += [red, green, blue, alpha]
}
return returnArr
}
/// Returns a path for a rectangle with rounded corners
class func newPathForRoundedRect(rect:CGRect, radiusTL radTL:CGFloat, radiusTR radTR:CGFloat, radiusBL radBL:CGFloat, radiusBR radBR:CGFloat, edges:UIRectEdge = .All) -> CGPathRef
{
let retPath = CGPathCreateMutable()
// Convenience
let rectL = rect.origin.x
let rectR = rect.origin.x+rect.size.width
let rectT = rect.origin.y
let rectB = rect.origin.y+rect.size.height
// Starting from the top left arc, move clockwise
let p1 = CGPointMake(rectL , rectT+radTL)
let p2 = CGPointMake(rectL+radTL, rectT)
let p3 = CGPointMake(rectR-radTR, rectT)
let p4 = CGPointMake(rectR , rectT+radTR)
let p5 = CGPointMake(rectR , rectB-radBR)
let p6 = CGPointMake(rectR-radBR, rectB)
let p7 = CGPointMake(rectL+radBL, rectB)
let p8 = CGPointMake(rectL , rectB-radBL)
let c1 = CGPointMake(rect.origin.x , rect.origin.y)
let c2 = CGPointMake(rect.origin.x+rect.size.width , rect.origin.y)
let c3 = CGPointMake(rect.origin.x+rect.size.width , rect.origin.y+rect.size.height)
let c4 = CGPointMake(rect.origin.x , rect.origin.y+rect.size.height)
if(edges.contains(.All) || (edges.contains(.Left) && edges.contains(.Right) && edges.contains(.Top) && edges.contains(.Bottom)))
{
CGPathMoveToPoint(retPath, nil, p1.x, p1.y)
CGPathAddArcToPoint (retPath, nil, c1.x, c1.y, p2.x, p2.y, radTL)
CGPathAddLineToPoint(retPath, nil, p3.x, p3.y)
CGPathAddArcToPoint (retPath, nil, c2.x, c2.y, p4.x, p4.y, radTR)
CGPathAddLineToPoint(retPath, nil, p5.x, p5.y)
CGPathAddArcToPoint (retPath, nil, c3.x, c3.y, p6.x, p6.y, radBR)
CGPathAddLineToPoint(retPath, nil, p7.x, p7.y)
CGPathAddArcToPoint (retPath, nil, c4.x, c4.y, p8.x, p8.y, radBL)
CGPathAddLineToPoint(retPath, nil, p1.x, p1.y)
CGPathCloseSubpath(retPath)
return retPath
}
if(edges.contains(.Top))
{
CGPathMoveToPoint(retPath, nil, p1.x, p1.y)
CGPathAddArcToPoint (retPath, nil, c1.x, c1.y, p2.x, p2.y, radTL)
CGPathAddLineToPoint(retPath, nil, p3.x, p3.y)
CGPathAddArcToPoint (retPath, nil, c2.x, c2.y, p4.x, p4.y, radTR)
}
if(edges.contains(.Right))
{
CGPathMoveToPoint(retPath, nil, p3.x, p3.y)
CGPathAddArcToPoint (retPath, nil, c2.x, c2.y, p4.x, p4.y, radTR)
CGPathAddLineToPoint(retPath, nil, p5.x, p5.y)
CGPathAddArcToPoint (retPath, nil, c3.x, c3.y, p6.x, p6.y, radBR)
}
if(edges.contains(.Bottom))
{
CGPathMoveToPoint(retPath, nil, p5.x, p5.y)
CGPathAddArcToPoint (retPath, nil, c3.x, c3.y, p6.x, p6.y, radBR)
CGPathAddLineToPoint(retPath, nil, p7.x, p7.y)
CGPathAddArcToPoint (retPath, nil, c4.x, c4.y, p8.x, p8.y, radBL)
}
if(edges.contains(.Left))
{
CGPathMoveToPoint(retPath, nil, p7.x, p7.y)
CGPathAddArcToPoint (retPath, nil, c4.x, c4.y, p8.x, p8.y, radBL)
CGPathAddLineToPoint(retPath, nil, p1.x, p1.y)
CGPathAddArcToPoint (retPath, nil, c1.x, c1.y, p2.x, p2.y, radTL)
}
return retPath
}
}
JFStylishView.swift
//
// JFStylishView.swift
// Soapbox
//
// Created by Joseph Falcone on 6/2/16.
// Copyright © 2016 Joseph Falcone. All rights reserved.
//
import UIKit
enum GradientType
{
case Linear
case Radial
}
private enum BackgroundFillType
{
case Solid
case Gradient
}
class JFStylishView : UIView
{
// Rounded Corners
var cornerTL : CGFloat = 0.0
var cornerTR : CGFloat = 0.0
var cornerBR : CGFloat = 0.0
var cornerBL : CGFloat = 0.0
// Border
var borderWidth : CGFloat = 4.0
var borderColor = UIColor.greenColor()
// Colors
private var trueBackgroundColor = UIColor.clearColor() // The backgroundColor property has to be clear so that the layer doesn't draw behind the clipping area, so we use this to track what the user wants
private var bgColors : [CGFloat] = [] // array of colors used in drawrect
// Gradient points
private var gradientStart = CGPointMake(0.5, 0.0)
private var gradientEnd = CGPointMake(0.5, 1.0)
private var gradientColorStops : [CGFloat] = []
// Gradient type
private var gradientType : GradientType = .Linear
// Background Mode
private var backgroundFillType : BackgroundFillType = .Solid
// var shadowLayer: CAShapeLayer! // Not ready for this yet
// MARK: Initialization
override init(frame: CGRect)
{
super.init(frame:frame)
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder:aDecoder)
}
override func awakeFromNib()
{
super.awakeFromNib()
}
func initStylishStuff()
{
cornerTL = 0.0
}
// MARK: Color
private func getFillType() -> BackgroundFillType
{
// Rather than keeping a variable for this that gets set everywhere, we'll just use this getter to figure out what type we are using.
// Of course, if I get sloppy and don't make the unused elements empty when setting another fill parameter, this could produce a bug.
// RULES
// If using a gradient, trueBackgroundColor will be clear
// If using solid, bgColors will be empty
// If patterns are ever added, the above will be empty
if(bgColors.count == 0)
{return .Solid}
if(trueBackgroundColor == UIColor.clearColor())
{return .Gradient}
// Default
return .Solid
}
override var backgroundColor: UIColor?
{
get
{
return trueBackgroundColor
}
set
{
trueBackgroundColor = backgroundColor!
super.backgroundColor = UIColor.clearColor()
//bgColorArr = []
bgColors = []
backgroundFillType = .Solid
}
/*
// Property observer - whenever the background color is
didSet
{
bgColorArr = []
bgColors = []
// bgColorArr = [backgroundColor!]
// bgColors = AppocalypseUI.getFloatArrayFromUIColors([backgroundColor!, backgroundColor!])
}
*/
}
// // Convenient...maybe we shouldn't include this?
// func setBackgroundGradient(topColor:UIColor, bottomColor:UIColor)
// {
// bgColorArr = [topColor, bottomColor]
// bgColors = AppocalypseUI.getFloatArrayFromUIColors([topColor, bottomColor])
// }
// Default is linear, top to bottom
// startPoint, endPoint should be coordinates of 0.0-1.0
func setBackgroundGradient(colors:[UIColor], stops:[CGFloat]? = nil, startPoint:CGPoint?=nil, endPoint:CGPoint?=nil, type:GradientType = .Linear)
{
assert(colors.count > 1, "At least two colors must be specified.")
// We won't be using the backgroundColor property when drawing a gradient
trueBackgroundColor = UIColor.clearColor()
// Calculate the stops if they were not specified
var stops = stops // arguments are immutable, but we can declare a variable with the same name
if(stops == nil)
{
stops = AppocalypseUI.makeLinearColorStops(colors.count)
}
// Provide default start and end points if necessary
gradientType = type
switch type
{
case .Linear: // top to bottom
gradientStart = startPoint == nil ? CGPointZero : startPoint!
gradientEnd = endPoint == nil ? CGPointMake(0, 1.0) : endPoint!
case .Radial: // center to top
gradientStart = startPoint == nil ? CGPointMake(0.5, 0.5) : startPoint!
gradientEnd = endPoint == nil ? CGPointMake(0.5, 0) : endPoint!
}
assert(colors.count == stops?.count, "The number of colors and stops must be equal.")
//bgColorArr = colors
bgColors = AppocalypseUI.getFloatArrayFromUIColors(colors)
gradientColorStops = stops!
}
/*
override func layoutSubviews()
{
super.layoutSubviews()
if shadowLayer == nil
{
shadowLayer = CAShapeLayer()
shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 12).CGPath
//shadowLayer.fillColor = UIColor.whiteColor().CGColor
shadowLayer.fillColor = UIColor.clearColor().CGColor
shadowLayer.shadowColor = UIColor.darkGrayColor().CGColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowOffset = CGSize(width: 2.0, height: 2.0)
shadowLayer.shadowOpacity = 0.8
shadowLayer.shadowRadius = 2
//layer.insertSublayer(shadowLayer, atIndex: 0)
layer.insertSublayer(shadowLayer, below: nil) // also works
}
}
*/
// MARK: Drawing
// override func drawLayer(layer: CALayer, inContext ctx: CGContext) {
//
// }
override func drawRect(rect: CGRect)
{
// Get the current context
let context = UIGraphicsGetCurrentContext()
// Make the background gradient
let baseSpace = CGColorSpaceCreateDeviceRGB();
let gradient = CGGradientCreateWithColorComponents(baseSpace, bgColors, gradientColorStops, gradientColorStops.count);
// Set the border color and stroke
CGContextSetLineWidth(context, borderWidth);
CGContextSetStrokeColorWithColor(context, borderColor.CGColor);
// Fill in the background, inset by the border
let bgRect = CGRectMake(bounds.origin.x+borderWidth , bounds.origin.y+borderWidth , bounds.size.width-borderWidth*2, bounds.size.height-borderWidth*2);
let borderRect = CGRectMake(bounds.origin.x+borderWidth/2, bounds.origin.y+borderWidth/2, bounds.size.width-borderWidth , bounds.size.height-borderWidth);
let bgPath = AppocalypseUI.newPathForRoundedRect(bgRect, radiusTL: cornerTL, radiusTR: cornerTR, radiusBL: cornerBL, radiusBR: cornerBR)
let borderPath = AppocalypseUI.newPathForRoundedRect(borderRect, radiusTL: cornerTL, radiusTR: cornerTR, radiusBL: cornerBL, radiusBR: cornerBR)
CGContextStrokePath(context)
// Background
CGContextSaveGState(context); // Saves the state from before we clipped to the path
CGContextAddPath(context, bgPath);
CGContextClip(context); // Makes the background fill only the path
switch getFillType()
{
case .Gradient:
let gradientStartInPoints = CGPointMake(gradientStart.x*bounds.size.width, gradientStart.y*bounds.size.height);
let gradientEndInPoints = CGPointMake(gradientEnd.x*bounds.size.width, gradientEnd.y*bounds.size.height);
switch(gradientType)
{
case .Linear:
CGContextDrawLinearGradient(context, gradient, gradientStartInPoints, gradientEndInPoints, []); // Draw a vertical gradient
case .Radial:
// A radial gradient might not fill the layer...first, fill it with the end color
UIColor(red: bgColors[bgColors.count-4], green: bgColors[bgColors.count-3], blue: bgColors[bgColors.count-2], alpha: bgColors[bgColors.count-1]).setFill()
CGContextAddPath(context, bgPath); // Not sure why I need this...TODO: Investigate
CGContextFillPath(context)
let endRadius = hypot(gradientStartInPoints.x-gradientEndInPoints.x, gradientStartInPoints.y-gradientEndInPoints.y)
CGContextDrawRadialGradient(context, gradient, gradientStartInPoints, 0, gradientStartInPoints, endRadius, [])
}
case .Solid:
trueBackgroundColor.setFill()
CGContextFillPath(context)
}
CGContextRestoreGState(context); // Now we are no longer clipped to the path
// Border
CGContextAddPath(context, borderPath);
CGContextStrokePath(context);
}
// MARK: Convenience
func removeAllSubviews()
{
for view in subviews
{view.removeFromSuperview()}
}
}
You can create a CAGradientLayer, then add mask with only two corner radius to it. Then you can animate the transform using Core Animation, or UIView transform animation also should work if you put the layer in UIView.
CAGradientLayer * rectangleGradient = [CAGradientLayer layer];
rectangleGradient.colors = #[(id)[UIColor greenColor].CGColor, (id)[UIColor orangeColor].CGColor];
rectangleGradient.startPoint = CGPointMake(0.5, 0);
rectangleGradient.endPoint = CGPointMake(0.5, 1);
rectangleMask.path = maskPath;
////The animation
CABasicAnimation * theTransformAnim = [CABasicAnimation animationWithKeyPath:#"transform"];
theTransformAnim.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];;
theTransformAnim.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(2, 2, 1)];;
theTransformAnim.duration = 1;
[rectangleGradient addAnimation:theTransformAnim];
I am drawing the lines using the following code. The line edges have dots and i want to show the tooltip when user click on the end dots.
The code snippet is below,
override func drawRect(rect: CGRect) {
// Drawing code
UIColor.brownColor().set()
let context = UIGraphicsGetCurrentContext()
//CGContextSetLineWidth(context, 5)
CGContextMoveToPoint(context, 50, 50)
CGContextAddLineToPoint(context,100, 200)
CGContextStrokePath(context)
// now add the circle on at the line edges
var point = CGPoint(x:50 , y:50)
point.x -= 5.0/2
point.y -= 5.0/2
var circle = UIBezierPath(ovalInRect: CGRect(origin: point, size: CGSize(width: 5.0,height: 5.0)))
circle.fill()
point = CGPoint(x:100 , y:200)
point.x -= 5.0/2
point.y -= 5.0/2
circle = UIBezierPath(ovalInRect: CGRect(origin: point, size: CGSize(width: 5.0,height: 5.0)))
circle.fill()
}
It is currently displaying the following image,
and i want to show the tooltip like below,
Does anybody have an idea how will i recognise that this particular dot is clicked and also how will i show the tooltip.
I am looking for a solution in swift.
Define a struct to hold points you have to touch, and the text to show:
struct TouchPoint {
var point: CGPoint // touch near here to show a tooltip
var tip: String // show this text when touched
}
then in the UIView subclass where you define drawRect, make somewhere to keep them:
var touchPoints: [TouchPoint] = [] // where we have to touch and what tooltip to show
drawRect can be called many times, so start fresh each time:
override func drawRect(rect: CGRect) {
touchPoints = []
// ...
// add a touchPoint for every place to touch
touchPoints.append(TouchPoint(point: point, tip: "point 1"))
}
You need to detect taps on the UIView, so add a gesture recognizer by changing its initialisation:
required init?(coder aDecoder: NSCoder) {
// standard init for a UIView wiht an added gesture recognizer
super.init(coder: aDecoder)
addGestureRecognizer(UITapGestureRecognizer(target: self, action: Selector("touched:")))
}
then you need a method to see whether touches are near touchpoints, and to show the right tooltip:
func touched(sender:AnyObject) {
let tapTolerance = CGFloat(20) // how close to the point to touch to see tooltip
let tipOffset = CGVector(dx: 10, dy: -10) // tooltip offset from point
let myTag = 1234 // random number not used elsewhere
guard let tap:CGPoint = (sender as? UITapGestureRecognizer)?.locationInView(self) else { print("touched: failed to find tap"); return }
for v in subviews where v.tag == myTag { v.removeFromSuperview() } // remove existing tooltips
let hitPoints:[TouchPoint] = touchPoints.filter({CGPointDistance($0.point, to: tap) < tapTolerance}) // list of tooltips near to tap
for h in hitPoints { // for each tooltip to show
let f = CGRect(origin: h.point+tipOffset, size: CGSize(width: 100, height: 20)) // fixed size label :-(
let l = UILabel(frame: f)
l.tag = myTag // just to be able to remove the tooltip later
l.text = h.tip // draw the text
addSubview(l) // add the label to the view
}
}
func CGPointDistanceSquared(from: CGPoint, to: CGPoint) -> CGFloat { return (from.x - to.x) * (from.x - to.x) + (from.y - to.y) * (from.y - to.y) }
func CGPointDistance(from: CGPoint, to: CGPoint) -> CGFloat { return sqrt(CGPointDistanceSquared(from, to: to)) }
and that incidentally uses a new version of the + operator to perform vector addition on CGPoint:
func +(left: CGPoint, right: CGVector) -> CGPoint { return CGPoint(x: left.x+right.dx, y: left.y+right.dy) }
and that works OK for me. Extra tweaks would be to compute the UILabel size from the text string, and move the UILabel so it didn't run off the side of the UIView at the edges. Good Luck!