Skip to content

Instantly share code, notes, and snippets.

@Jesse-calkin
Created March 19, 2016 20:03
Show Gist options
  • Save Jesse-calkin/ae437a5e97905738ded2 to your computer and use it in GitHub Desktop.
Save Jesse-calkin/ae437a5e97905738ded2 to your computer and use it in GitHub Desktop.
//
// DonutView.swift
//
// Created by jesse calkin on 3/15/16.
// Copyright © 2016 jesse calkin. All rights reserved.
//
import UIKit
func percentToRadians(percent: CGFloat) -> CGFloat {
return CGFloat(percent * 360) * CGFloat(M_PI) / 180.0
}
@IBDesignable class DonutView: UIView {
//MARK: - Internal
//MARK: _ Properties
let numberLabel = UILabel(frame: CGRectZero)
let percentLabel = UILabel(frame: CGRectZero)
var percent: CGFloat = 0.45 {
didSet {
updateValue()
}
}
//MARK: _ IB
@IBInspectable var foregroundColor: UIColor = UIColor(red: 53.0 / 255.0, green: 63.0 / 255.0, blue: 91.0 / 255.0, alpha: 1.0)
@IBInspectable var valueColor: UIColor = UIColor.cyanColor()
//MARK: - Private
//MARK: _ Properties
//MARK: __ Layers
private let pieLayer = CAShapeLayer()
private let innerCircle = CAShapeLayer()
private let outerCircle = CAShapeLayer()
//MARK: __ Drop Shadow
private let shadowRadius: CGFloat = 5.0
private let shadowOpacity: Float = 0.5
private var radius: CGFloat {
return bounds.size.width / 3
}
//MARK: _ Methods
private func updatePercentForLocation(location: CGPoint) {
percent = min(1.0, max(0.0,location.x) / bounds.width)
}
private func updateValue() {
let piePath = UIBezierPath()
piePath.moveToPoint(CGPoint(x: bounds.midX, y: bounds.midY))
piePath.addArcWithCenter(CGPoint(x: bounds.midX, y: bounds.midY), radius: radius, startAngle: 0, endAngle: percentToRadians(percent), clockwise: true)
piePath.addLineToPoint(CGPoint(x: bounds.midX, y: bounds.midY))
piePath.closePath()
pieLayer.path = piePath.CGPath
numberLabel.text = "\(Int(percent * 100.0))"
}
private func updateLayers() {
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: bounds.minX, y: bounds.midX))
path.addLineToPoint(bounds.origin)
path.addLineToPoint(CGPoint(x: bounds.maxX, y: bounds.minY))
path.addLineToPoint(CGPoint(x: bounds.maxX, y: bounds.midY))
let p2 = UIBezierPath()
p2.moveToPoint(CGPoint(x: bounds.minX, y: bounds.midX))
p2.addLineToPoint(CGPoint(x: bounds.minX, y: bounds.maxY))
p2.addLineToPoint(CGPoint(x: bounds.maxX, y: bounds.maxY))
p2.addLineToPoint(CGPoint(x: bounds.maxX, y: bounds.midY))
let p3 = UIBezierPath()
p3.moveToPoint(CGPoint(x: bounds.midX + radius, y: bounds.midY))
p3.addArcWithCenter(CGPoint(x: bounds.midX, y: bounds.midY), radius: radius, startAngle: 0, endAngle: 180 * CGFloat(M_PI) / 180.0, clockwise: false)
path.appendPath(p3)
path.appendPath(p2)
let p4 = UIBezierPath()
p4.moveToPoint(CGPoint(x: bounds.midX + radius, y: bounds.midY))
p4.addArcWithCenter(CGPoint(x: bounds.midX, y: bounds.midY), radius: radius, startAngle: 0, endAngle: 180 * CGFloat(M_PI) / 180.0, clockwise: true)
path.appendPath(p4)
let rectForInnerCircle = CGRectInset(bounds, bounds.size.width / 4, bounds.size.height / 4)
let pathForInnerCircle = UIBezierPath(ovalInRect: rectForInnerCircle)
innerCircle.bounds = rectForInnerCircle
outerCircle.path = path.CGPath
innerCircle.path = pathForInnerCircle.CGPath
}
private func setupLayers() {
layer.masksToBounds = true
pieLayer.fillColor = valueColor.CGColor
pieLayer.shadowRadius = shadowRadius
pieLayer.shadowOpacity = shadowOpacity
pieLayer.shadowColor = valueColor.CGColor
pieLayer.bounds = bounds
pieLayer.position = CGPoint(x: bounds.midX, y: bounds.midY)
layer.addSublayer(pieLayer)
outerCircle.fillColor = foregroundColor.CGColor
outerCircle.shadowOpacity = shadowOpacity
outerCircle.shadowOffset = CGSizeZero
outerCircle.shadowRadius = shadowRadius
layer.addSublayer(outerCircle)
innerCircle.fillColor = foregroundColor.CGColor
innerCircle.shadowOpacity = shadowOpacity
innerCircle.shadowOffset = CGSizeZero
innerCircle.shadowRadius = shadowRadius
innerCircle.position = CGPoint(x: bounds.midX, y: bounds.midY)
layer.addSublayer(innerCircle)
}
private func setupLabels() {
var font = UIFont(name: "HelveticaNeue-UltraLight", size: 80.0)
numberLabel.font = font
numberLabel.textAlignment = NSTextAlignment.Center
numberLabel.textColor = valueColor
numberLabel.shadowColor = UIColor.blackColor()
numberLabel.text = "100"
numberLabel.sizeToFit()
numberLabel.text = "0"
//FIXME: Update to use NSLayoutConstraints
numberLabel.center = CGPoint(x: bounds.midX, y: bounds.midY)
numberLabel.layer.masksToBounds = true
font = UIFont(name: "HelveticaNeue-Light", size: 16.0)
percentLabel.font = font
percentLabel.textAlignment = NSTextAlignment.Center
percentLabel.textColor = valueColor
percentLabel.shadowColor = UIColor.blackColor()
percentLabel.text = "%"
percentLabel.sizeToFit()
//FIXME: Update to use NSLayoutConstraints
percentLabel.center = CGPoint(x: bounds.midX, y: bounds.midY + 55.0)
addSubview(numberLabel)
addSubview(percentLabel)
}
//MARK: - Overrides
//MARK: _ View Lifecycle
override init(frame: CGRect) {
super.init(frame: frame)
setupLayers()
setupLabels()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupLayers()
setupLabels()
}
override func drawRect(rect: CGRect) {
super.drawRect(rect)
updateLayers()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setupLayers()
setupLabels()
percent = 0.45
}
//MARK: _ Touches
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesMoved(touches, withEvent: event)
guard let location = touches.first?.locationInView(self) else { return }
updatePercentForLocation(location)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesBegan(touches, withEvent: event)
guard let location = touches.first?.locationInView(self) else { return }
updatePercentForLocation(location)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment