iOS仪表盘 Dashboard

运行效果如下:


//
//  AKDashboardView.swift
//  AKSwifty
//
//  Created by wangcong on 01/04/2017.
//  Copyright © 2017 ApterKing. All rights reserved.
//

import UIKit

// 指针
class AKNeedleView: UIView {
    
    public var color = UIColor.red {
        didSet {
            self.setNeedsDisplay()
        }
    }
    
    convenience init() {
        self.init(frame: CGRect.zero)
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.backgroundColor = UIColor.clear
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        
        let context = UIGraphicsGetCurrentContext()
        context?.setFillColor(UIColor.clear.cgColor)
        context?.setStrokeColor(color.cgColor)
        
        // 绘制第一条线
        context?.setLineWidth(1)
        context?.move(to: CGPoint(x: 0, y: rect.height / 2.0))
        context?.addLine(to: CGPoint(x: rect.width * 2.0 / 3.0, y: rect.height / 2.0))
        context?.drawPath(using: CGPathDrawingMode.fillStroke)
        
        // 绘制第二条线
        context?.setLineWidth(2)
        context?.setLineCap(CGLineCap.round)
        context?.move(to: CGPoint(x: rect.width * 2.0 / 3.0, y: rect.height / 2.0))
        context?.addLine(to: CGPoint(x: rect.width - 12, y: rect.height / 2.0))
        context?.drawPath(using: CGPathDrawingMode.fillStroke)
        
        // 绘制小圆圈
        context?.setLineWidth(2)
        context?.addArc(center: CGPoint(x: rect.width - 8, y: rect.height / 2.0), radius: 4, startAngle: 0, endAngle: CGFloat(Double.pi * 2), clockwise: true)
        context?.drawPath(using: CGPathDrawingMode.fillStroke)
        
        // 绘制小圆边缘阴影
        context?.setStrokeColor(color.withAlphaComponent(0.5).cgColor)
        context?.addArc(center: CGPoint(x: rect.width - 8, y: rect.height / 2.0), radius: 6, startAngle: 0, endAngle: CGFloat(Double.pi * 2), clockwise: true)
        context?.strokePath()    }
    
}

class AKDashboardView: UIView {
    
    // Edge
    private lazy var outEdgeLayer: CAShapeLayer = {
        let shapeLayer = CAShapeLayer()
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeColor = UIColor.white.cgColor
        shapeLayer.lineWidth = 2.0
        shapeLayer.lineCap = kCALineCapButt
        return shapeLayer
    }()
    
    private lazy var inEdgeLayer: CAShapeLayer = {
        let shapeLayer = CAShapeLayer()
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeColor = UIColor.white.cgColor
        shapeLayer.lineWidth = 2.0
        shapeLayer.lineCap = kCALineCapButt
        return shapeLayer
    }()
    
    public var edgeColor = UIColor.white {
        didSet {
            self.outEdgeLayer.strokeColor = edgeColor.cgColor
            self.inEdgeLayer.strokeColor = edgeColor.cgColor
        }
    }
    
    public var edgeWidth: CGFloat = 2.0 {
        didSet {
            self.outEdgeLayer.lineWidth = edgeWidth
            self.resetAll()
        }
    }
    
    public var edgeSpacing: CGFloat = 40 {
        didSet {
            self.maskLayer.lineWidth = edgeSpacing
            self.longScaleLayer.lineWidth = edgeSpacing / 3
            self.shortScaleLayer.lineWidth = edgeSpacing / 4
            self.resetAll()
        }
    }
    
    // 长刻度
    private lazy var longScaleLayer: CAShapeLayer = {
        let shapeLayer = CAShapeLayer()
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeColor = UIColor.white.cgColor
        shapeLayer.lineWidth = 40 / 6
        shapeLayer.lineCap = kCALineCapButt
        return shapeLayer
    }()
    public var longScaleColor = UIColor.white {
        didSet {
            longScaleLayer.strokeColor = longScaleColor.cgColor
        }
    }
    public var longScales: Int = 5 {
        didSet {
            resetLongScaleLayer()
            resetLongScaleTextLayer()
        }
    }
    
    // 长刻度文字
    private var longScaleTextLayer: CALayer = CALayer()
    public var longScaleTextColor = UIColor.white {
        didSet {
            guard let sublayers = longScaleTextLayer.sublayers else {
                return
            }
            for sublayer in sublayers {
                if let textLayer = sublayer as? CATextLayer {
                    textLayer.foregroundColor = longScaleTextColor.cgColor
                }
            }
        }
    }
    public var longScaleTexts: [String] = [] {
        didSet {
            resetLongScaleTextLayer()
        }
    }
    
    // 短刻度
    private lazy var shortScaleLayer: CAShapeLayer = {
        let shapeLayer = CAShapeLayer()
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeColor = UIColor.white.cgColor
        shapeLayer.lineWidth = 40 / 8
        shapeLayer.lineCap = kCALineCapButt
        return shapeLayer
    }()
    public var shortScaleColor = UIColor.white {
        didSet {
            shortScaleLayer.strokeColor = shortScaleColor.cgColor
        }
    }
    
    public var shortScales: Int = 1 { // 短的刻度是:在长刻度区间内的分度值
        didSet {
            resetShortScaleLayer()
        }
    }
    
    // 进度条
    private lazy var maskLayer: CAShapeLayer = {
        let shapeLayer = CAShapeLayer()
        shapeLayer.lineWidth = 40
        shapeLayer.strokeStart = 0.0
        shapeLayer.strokeEnd = 0.0
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeColor = UIColor.white.cgColor
        return shapeLayer
    }()
    
    private lazy var gradientLayer: CAGradientLayer = {
        let gradientLayer = CAGradientLayer()
        gradientLayer.colors = [UIColor.green.cgColor, UIColor.orange.cgColor, UIColor.red.cgColor]
        gradientLayer.startPoint = CGPoint(x: 0, y: 0)
        gradientLayer.endPoint = CGPoint(x: 1, y: 0)
        return gradientLayer
    }()
    
    public var gradientColors: [UIColor] = [UIColor.green, UIColor.orange, UIColor.red] {
        didSet {
            var cgColors = [CGColor]()
            for color in gradientColors {
                cgColors.append(color.cgColor)
            }
            gradientLayer.colors = cgColors
        }
    }
    
    // 指针
    private var needleView = AKNeedleView()
    public var needleColor: UIColor = UIColor.white {
        didSet {
            needleView.color = needleColor
        }
    }
    
    // 设置刻度
    public var dashScale: CGFloat = 0.0 {
        didSet {
            setDashSacle(dashScale, animated: false)
        }
    }
    
    convenience init() {
        self.init(frame: CGRect.zero)
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        gradientLayer.mask = maskLayer
        self.layer.addSublayer(gradientLayer)
        
        shortScaleLayer.lineWidth = edgeSpacing / 4
        self.layer.addSublayer(shortScaleLayer)
        
        longScaleLayer.lineWidth = edgeSpacing / 3
        self.layer.addSublayer(longScaleLayer)
        
        self.layer.addSublayer(outEdgeLayer)
        self.layer.addSublayer(inEdgeLayer)
        
        needleView.layer.anchorPoint = CGPoint(x: 1, y: 0.5)
        self.addSubview(needleView)
        
        self.layer.addSublayer(longScaleTextLayer)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        gradientLayer.frame = self.bounds
        longScaleTextLayer.frame = self.bounds
        resetAll()
    }
    
    // 重置Layer 及View
    fileprivate func resetAll() {
        
        resetLongScaleLayer()
        resetLongScaleTextLayer()
        resetShortScaleLayer()
        
        let radius = frame.size.width / 2 > frame.size.height ?
            frame.size.height - edgeWidth / 2: frame.size.width / 2 - edgeWidth / 2
        
        maskLayer.path = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2, y: frame.size.height), radius: radius - edgeWidth / 2 - edgeSpacing / 2, startAngle: CGFloat(Float.pi), endAngle: CGFloat(Float.pi * 2), clockwise: true).cgPath
        
        outEdgeLayer.path = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2, y: frame.size.height), radius: radius, startAngle: CGFloat(Float.pi), endAngle: CGFloat(Float.pi * 2), clockwise: true).cgPath
        
        inEdgeLayer.path = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2, y: frame.size.height), radius: radius - edgeWidth / 2 - edgeSpacing, startAngle: CGFloat(Float.pi), endAngle: CGFloat(Float.pi * 2), clockwise: true).cgPath
        
        needleView.frame = CGRect(x: frame.size.width / 2 - radius + edgeSpacing - 2 * edgeWidth, y: frame.size.height - 15 / 2, width: radius - edgeSpacing + 2 * edgeWidth, height: 15)
    }
    
    fileprivate func resetLongScaleLayer() {
        let radius = frame.size.width / 2 > frame.size.height ?
            frame.size.height - edgeWidth / 2: frame.size.width / 2 - edgeWidth / 2
        let longScaleRadius = radius - edgeWidth / 2 - edgeSpacing / 6
        let C = CGFloat.pi * longScaleRadius
        longScaleLayer.path = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2, y: frame.size.height), radius: longScaleRadius, startAngle: CGFloat(Float.pi), endAngle: CGFloat(Float.pi * 2), clockwise: true).cgPath
        longScaleLayer.lineDashPattern = [2, NSNumber(value: Float((C - CGFloat(2 * longScales)) / CGFloat(longScales - 1)))]
    }
    
    fileprivate func resetLongScaleTextLayer() {
        
        if let sublayers = self.longScaleTextLayer.sublayers {
            for sublayer in sublayers {
                sublayer.removeFromSuperlayer()
            }
        }
        
        let textSize: CGFloat = 12
        let radius = frame.size.width / 2 > frame.size.height ?
            frame.size.height - edgeWidth / 2: frame.size.width / 2 - edgeWidth / 2
        let longScaleTextRadius = radius - edgeWidth / 2 - edgeSpacing / 6 - textSize
        let angleSpacing = CGFloat.pi / CGFloat(longScales - 1)
        
        var angle = CGFloat.pi
        for index in 0 ..< longScales {
            let textLayer = CATextLayer()
            textLayer.foregroundColor = longScaleTextColor.cgColor
            textLayer.fontSize = 10
            textLayer.string = index >= longScaleTexts.count ? "" : longScaleTexts[index % longScaleTexts.count]
            textLayer.allowsEdgeAntialiasing = true
            textLayer.contentsScale = UIScreen.main.scale
            textLayer.alignmentMode = kCAAlignmentCenter
            
            let x = longScaleTextRadius * cos(angle)
            let y = longScaleTextRadius * sin(angle)
            textLayer.frame = CGRect(origin: CGPoint(x: self.frame.size.width / 2.0 + x - textSize / 2.0, y: self.frame.size.height + y - (index == 0 || index == longScales - 1 ? textSize : textSize / 2.0)), size: CGSize(width: 12, height: 12))
            angle += angleSpacing
            
            self.longScaleTextLayer.addSublayer(textLayer)
        }
        
    }
    
    fileprivate func resetShortScaleLayer() {
        let radius = frame.size.width / 2 > frame.size.height ?
            frame.size.height - edgeWidth / 2: frame.size.width / 2 - edgeWidth / 2
        let shortScaleRadius = radius - edgeWidth / 2 - edgeSpacing / 8
        let C = CGFloat.pi * shortScaleRadius
        let realShortScales = (longScales - 1) * (shortScales + 1) + 1
        shortScaleLayer.path = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2, y: frame.size.height), radius: shortScaleRadius, startAngle: CGFloat(Float.pi), endAngle: CGFloat(Float.pi * 2), clockwise: true).cgPath
        shortScaleLayer.lineDashPattern = [2, NSNumber(value: Float((C - CGFloat(2 * realShortScales)) / CGFloat(realShortScales - 1)))]
    }
    
    // MARK: 设置dashScale
    public func setDashSacle(_ scale: CGFloat, animated: Bool) {
        let realScale = scale > 1.0 ? 1.0 : (scale < 0 ? 0 : scale)
        if animated {
            let strokeAnim = CABasicAnimation(keyPath: "strokeEnd")
            strokeAnim.duration = 0.25
            strokeAnim.toValue = realScale
            strokeAnim.fillMode = kCAFillModeForwards
            strokeAnim.isRemovedOnCompletion = false
            maskLayer.add(strokeAnim, forKey: "strokeEnd")
        } else {
            self.maskLayer.strokeEnd = realScale
        }
        
        let rotatedAnim = CABasicAnimation(keyPath: "transform.rotation")
        rotatedAnim.duration = animated ? 0.25 : 0.01
        rotatedAnim.toValue = realScale * CGFloat.pi
        rotatedAnim.isRemovedOnCompletion = false
        rotatedAnim.fillMode = kCAFillModeForwards
        needleView.layer.add(rotatedAnim, forKey: "transform.rotation")
    }
}

** 用法 **

        let dashboardView = AKDashboardView(frame: CGRect(x: (UIScreen.main.bounds.size.width - 300) / 2.0, y: 200, width: 300, height: 150))
        dashboardView.gradientColors = [UIColor.green, UIColor.orange, UIColor.red]
        dashboardView.needleColor = UIColor.white
        dashboardView.shortScaleColor = UIColor.white.withAlphaComponent(0.6)
        dashboardView.shortScales = 5
        dashboardView.longScales = 11
        dashboardView.longScaleColor = UIColor.white
        dashboardView.longScaleTexts = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
        dashboardView.setDashSacle(0.8, animated: true)

Demo下载地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容