Created
March 19, 2016 20:03
-
-
Save Jesse-calkin/ae437a5e97905738ded2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // 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