Skip to content

Instantly share code, notes, and snippets.

@damienlaughton
Last active April 27, 2017 12:54
Show Gist options
  • Save damienlaughton/e41fbf4f4df48a4313e85224e33390d2 to your computer and use it in GitHub Desktop.
Save damienlaughton/e41fbf4f4df48a4313e85224e33390d2 to your computer and use it in GitHub Desktop.
//
// UIBatteryGuageView.swift & Double+ExpressAsZeroToOnePercentage
// UIBatteryGuageView
//
// Created by Damien Laughton on 30/03/2017.
// No Rights resereved.
//
import UIKit
import Foundation
extension Double {
func expressAsZeroToOnePercentage() -> Double {
var zeroToOneBase = 0.0
if self < 0.0 {
zeroToOneBase = 0.0
} else if self <= 1.0 {
zeroToOneBase = self
} else if self <= 100.0 {
zeroToOneBase = self / 100
} else {
zeroToOneBase = 1.0
}
return zeroToOneBase
}
}
typealias BatteryAnimationCompletionHandler = ((Bool) -> ())
@IBDesignable class UIBatteryGuageView: UIView {
@IBInspectable var borderWidth: CGFloat = 3.0 {
didSet {
self.layer.borderWidth = borderWidth
}
}
@IBInspectable var borderColor: UIColor = UIColor.black {
didSet {
self.layer.borderColor = borderColor.cgColor
}
}
@IBInspectable var paddingWidth: CGFloat = 2.0
@IBInspectable var paddingHeight: CGFloat = 2.0
@IBInspectable var numberOfBlocks: Int = 10
@IBInspectable var blockColor: UIColor = UIColor.red {
didSet {
self.setNeedsDisplay()
}
}
internal var zeroToOneBasedPercentage: Double = 0.0
@IBInspectable var percentage: Double = 0.0 {
didSet {
self.zeroToOneBasedPercentage = percentage.expressAsZeroToOnePercentage()
self.setNeedsDisplay()
}
}
func configure() {
// override XIB defaults here
// self.borderColor = UIColor.red
}
override init (frame : CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.configure()
}
override func awakeFromNib() {
super.awakeFromNib()
self.configure()
}
override func layoutSubviews() {
super.layoutSubviews()
}
override func draw(_ rect: CGRect) {
super.draw(rect)
for v in self.subviews {
v.removeFromSuperview()
}
if self.zeroToOneBasedPercentage == 0.0 { return }
let percentagePerBlock: Double = 1.0 / Double(self.numberOfBlocks)
let numberOfFullBlocks: Int = Int(self.zeroToOneBasedPercentage / percentagePerBlock)
let width = self.frame.size.width
let height = self.frame.size.height
let availableWidth = width - (2 * (self.borderWidth + self.paddingWidth)) - (CGFloat(self.numberOfBlocks - 1) * self.paddingWidth)
let widthOfBlock = availableWidth / CGFloat(self.numberOfBlocks)
let availableHeight = height - (2 * (self.borderWidth + self.paddingHeight))
let heightOfBlock = availableHeight
var currentX = self.borderWidth + self.paddingWidth
let currentY = self.borderWidth + self.paddingHeight
var blockFrame = CGRect(x: currentX, y: currentY, width: widthOfBlock, height: heightOfBlock)
// draw full blocks
for _ in 0 ..< numberOfFullBlocks {
let blockView = UIView(frame: blockFrame)
blockView.backgroundColor = self.blockColor
self.addSubview(blockView)
currentX += widthOfBlock + self.paddingWidth
blockFrame = CGRect(x: currentX, y: currentY, width: widthOfBlock, height: heightOfBlock)
}
// draw partial block
let percentageLeftOverIfAny: Double = self.zeroToOneBasedPercentage - (Double(numberOfFullBlocks) * percentagePerBlock)
if percentageLeftOverIfAny == 0.0 { return }
let widthOfPartialBlock = widthOfBlock * CGFloat(percentageLeftOverIfAny) * CGFloat(self.numberOfBlocks)
let partialBlockFrame = CGRect(x: currentX, y: currentY, width: widthOfPartialBlock, height: heightOfBlock)
let partialBlockView = UIView(frame: partialBlockFrame)
let partialAlpha = CGFloat(percentageLeftOverIfAny) * CGFloat(self.numberOfBlocks)
partialBlockView.backgroundColor = self.blockColor.withAlphaComponent(partialAlpha)
self.addSubview(partialBlockView)
}
internal var finalPercentage: Double = 0.0
internal var numberOfSteps: Int32 = 0
internal var timeIntervalPerStep: TimeInterval = 0.0
internal var completion: ((Bool) -> ())? = .none
func animate(finalPercentage: Double, duration: TimeInterval, completion: BatteryAnimationCompletionHandler? = .none) {
self.finalPercentage = finalPercentage.expressAsZeroToOnePercentage()
self.numberOfSteps = abs(Int32(self.zeroToOneBasedPercentage * 100) - Int32(self.finalPercentage * 100))
self.timeIntervalPerStep = duration / Double(self.numberOfSteps)
self.completion = completion
if self.timeIntervalPerStep <= 0.0 {
self.completion?(false)
return
}
if self.finalPercentage < 0.0 || self.finalPercentage > 1.0 {
self.completion?(false)
return
}
if self.finalPercentage == self.zeroToOneBasedPercentage {
self.completion?(false)
return
}
Timer.scheduledTimer(withTimeInterval: self.timeIntervalPerStep, repeats: false, block: { _ in self.performStep() })
}
internal func performStep() {
DispatchQueue.main.async {
if self.zeroToOneBasedPercentage < self.finalPercentage {
self.zeroToOneBasedPercentage += 0.01
} else {
self.zeroToOneBasedPercentage -= 0.01
}
self.numberOfSteps -= 1
if self.numberOfSteps > 0 {
Timer.scheduledTimer(withTimeInterval: self.timeIntervalPerStep, repeats: false, block: { _ in self.performStep() })
} else {
self.completion?(true)
}
self.setNeedsDisplay()
}
}
}
@damienlaughton
Copy link
Author

An IBDesignable / IBInspectable battery gauge with basic animation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment