Swift每日一练:圆形滑动条

最近遇到了好多人问我会不会Swift,虽然我很早就进行了Swift的学习,但是苹果对于Swift的更新真是日新月异,和我当时beta版本学习的时候大大不同了,所以,从今天起准备督促自己进行每日一练。

今天想要做的是一个360度的圆型滑动条。

预览效果

细节方面还有欠缺,比如,没有进行旋转圈数的控制,同时也没有进行拖拽范围的控制。

效果实现

我们首先来大致看看怎么做分解:

- 一个圆环
- 一个渐变色
- 一个控制球
- 一个显示文字

首先看看圆环怎么实现的,很简单,我们利用Core Graphic使用如下代码即可以画出一个圆圈,只要加上描边宽度,就可以形成圆环效果。

CGContextAddArc(
    ctx,
    self.frame.size.width/2,
    self.frame.size.height/2,
    self.radius,
    0,
    2.0 * M_PI,
    0
)
UIColor(red: 0, green: 0, blue: 0, alpha: 1.0).set()
CGContextSetLineWidth(ctx, 72)
CGContextSetLineCap(ctx, kCGLineCapButt)
CGContextDrawPath(ctx, kCGPathStroke)

然后我们的圆环背景色是渐变的,这可怎么办?我们直接使用CAGradientLayer吗?当然那是其中之一的方法,不过这次我们试试用Mask + 背景色的方式实现。什么是Mask呢?简单来理解就是,如果你想做出非常复杂的形状,你可以将构造一个包含这种形状的图片,需要显示形状的地方用非白色的颜色来填充,不显示的地方用白色填充。因此,我们构建一个从上到下的渐变色背景,然后配合黑色的圆环进行Mask,就可以得到带有渐变色背景的。

CGContextSaveGState(ctx)     
CGContextClipToMask(ctx, self.bounds, mask)

let startColorComponents = CGColorGetComponents(startColor.CGColor)
let endColorComponents = CGColorGetComponents(endColor.CGColor)
let components = [
  startColorComponents[0], startColorComponents[1], startColorComponents[2], 1.0,
  endColorComponents[0], endColorComponents[1], endColorComponents[2], 1.0
]
let gradient = CGGradientCreateWithColorComponents(CGColorSpaceCreateDeviceRGB(), components, nil, 2)

CGContextDrawLinearGradient(
    ctx,
    gradient,
    CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect)),
    CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect)),
    0
);

CGContextRestoreGState(ctx);

到此,绘画的工作基本就结束了,画白色小球的原理很简单,也不赘述了。我们下面来说一下根据手势来控制白色小球旋转位置,主要的思路还是控制位置来计算弧度。

override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent)     {    
    //println("Touches Moved")

    if let touch = touches.first as? UITouch {
        var location = touch.locationInView(self)
        moveHandle(location)
    }
}

func computeAngle(p1:CGPoint , p2:CGPoint) -> Double {
    var delta = CGPoint(x: p2.x - p1.x, y: p1.y - p2.y)

    let radians = Double(atan2(delta.y, delta.x))
    let result = toDegree(radians)
    return result >= 0 ? result : result + 360
}

其中,有一点我们要特别注意
var delta = CGPoint(x: p2.x - p1.x, y: p1.y - p2.y),在Y轴的计算上,我们需要进行上下翻转的计算,原因就在于我们通过UITouch拿到的location的Y坐标,是UIKit系的坐标体系,Y轴原点在上方,而我们通过绘图的时候需要进行进行Y轴的上下颠倒。

此外,根据计算的弧度,来更新小球的位置,由于我们是逆时针的slider,所以我们需要用 360 减去我们实际计算得到的弧度。

一些感受

真的,Swift中的类型安全太恶心了,不能忍,于是只好玩起了操作符重载

func -(lhs:CGFloat, rhs:Double) -> CGFloat {
    return lhs - CGFloat(rhs)
}

func +(lhs:Double, rhs:CGFloat) -> CGFloat {
    return CGFloat(lhs) + rhs
}

func +(lhs:CGFloat, rhs:Double) -> CGFloat {
    return lhs + CGFloat(rhs)
}

func *(lhs:Double, rhs:CGFloat) -> CGFloat {
    return CGFloat(lhs) * rhs
}

func *(lhs:CGFloat, rhs:Double) -> CGFloat {
    return lhs * CGFloat(rhs)
}

func /(lhs:Double, rhs:CGFloat) -> CGFloat {
    return CGFloat(lhs) / rhs
}

func /(lhs:CGFloat, rhs:Double) -> CGFloat {
    return lhs / CGFloat(rhs)
}

最后附上项目地址

好了,不多说了,我要升级El Captain了。