Skip to content

Instantly share code, notes, and snippets.

@paulw11
Created November 25, 2017 23:11
Show Gist options
  • Save paulw11/279bcc3569473a73793da87c163502f3 to your computer and use it in GitHub Desktop.
Save paulw11/279bcc3569473a73793da87c163502f3 to your computer and use it in GitHub Desktop.
//
// CircularTimerView.swift
// ProgressView
//
// Created by Paul Wilkinson on 26/11/17.
// Copyright © 2017 Paul Wilkinson. All rights reserved.
//
import UIKit
protocol CircularTimerViewDelegate {
func ended(timerView: CircularTimerView)
}
@IBDesignable class CircularTimerView: UIView {
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
@IBInspectable var duration: TimeInterval = 30.0{
didSet {
if self.timer != nil {
self.startTime = Date()
}
}
}
var timeRemaining: TimeInterval {
get {
return duration - elapsed
}
}
var elapsed: TimeInterval {
guard let start = self.startTime, let current = self.currentTime else {
return 0
}
return current.timeIntervalSince(start)
}
@IBInspectable var timeLabelString: String = "" {
didSet {
setNeedsDisplay()
}
}
@IBInspectable var progressColour: UIColor = UIColor(red: 1, green: 1, blue: 1, alpha: 1) {
didSet {
setNeedsDisplay()
}
}
@IBInspectable var progressBackgroundColour: UIColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.4) {
didSet {
setNeedsDisplay()
}
}
@IBInspectable var progressBackroundLineWidth: CGFloat = 5.0 {
didSet {
setNeedsDisplay()
}
}
@IBInspectable var innerShadow: UIColor = UIColor.black.withAlphaComponent(0.22) {
didSet {
setNeedsDisplay()
}
}
@IBInspectable var innerShadowOffset: CGSize = CGSize(width: 3.1, height: 3.1) {
didSet {
setNeedsDisplay()
}
}
@IBInspectable var innerShadowBlurRadius: CGFloat = 4 {
didSet {
setNeedsDisplay()
}
}
@IBInspectable var progressLineWith: CGFloat = 5 {
didSet {
setNeedsDisplay()
}
}
public var delegate: CircularTimerViewDelegate?
private var startAngle = CGFloat(-90 * Double.pi / 180)
private var endAngle = CGFloat(270 * Double.pi / 180)
private var timer: Timer?
private var startTime: Date?
private var currentTime: Date?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
public func start() {
self.timer?.invalidate()
self.startTime = Date().addingTimeInterval(-self.elapsed)
self.currentTime = Date()
self.timer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true, block: { (timer) in
self.currentTime = Date()
self.setNeedsDisplay()
if self.elapsed >= self.duration {
self.timer?.invalidate()
self.timer = nil
self.delegate?.ended(timerView: self)
}
})
}
public func pause() {
self.timer?.invalidate()
}
public func stop() {
self.timer?.invalidate()
self.timer = nil
self.startTime = nil
self.currentTime = nil
}
override func draw(_ rect: CGRect) {
// General Declarations
guard let context = UIGraphicsGetCurrentContext() else {
return
}
// Background Drawing
let backgroundPath = UIBezierPath(ovalIn: CGRect(x: rect.minX, y: rect.minY, width: rect.width, height: rect.height))
backgroundColor?.setFill()
backgroundPath.fill()
// ProgressBackground Drawing
let progressPadding: CGFloat = 15
let progressBackgroundPath = UIBezierPath(ovalIn: CGRect(x: rect.minX + progressPadding/2, y: rect.minY + progressPadding/2, width: rect.size.width - progressPadding, height: rect.size.height - progressPadding))
progressBackgroundColour.setStroke()
progressBackgroundPath.lineWidth = self.progressBackroundLineWidth
progressBackgroundPath.stroke()
// Progress Drawing
let progressRect = CGRect(x: rect.minX + progressPadding/2, y: rect.minY + progressPadding/2, width: rect.size.width - progressPadding, height: rect.size.height - progressPadding)
let progressPath = UIBezierPath()
// progressPath.addArc(withCenter: CGPoint(x: progressRect.midX, y: progressRect.midY), radius: progressRect.width / 2, startAngle: startAngle, endAngle: (endAngle - startAngle) * CGFloat(1 - elapsed/duration) + startAngle, clockwise: true)
let percentComplete = CGFloat(elapsed / duration)
let arcAngle = (endAngle - startAngle) * percentComplete
progressPath.addArc(withCenter: CGPoint(x: progressRect.midX, y: progressRect.midY), radius: progressRect.width / 2, startAngle: startAngle, endAngle: startAngle+arcAngle, clockwise: true)
progressColour.setStroke()
progressPath.lineWidth = self.progressLineWith
progressPath.lineCapStyle = CGLineCap.round
progressPath.stroke()
// Text Drawing
let textRect = CGRect(x: rect.minX, y: rect.minY, width: rect.size.width, height: rect.size.height)
let textContent = NSString(string: timeLabelString)
let textStyle = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
textStyle.alignment = .center
let textFontAttributes = [
NSAttributedStringKey.font: UIFont(name: "Helvetica", size: rect.width / 12)!, // 3, HelveticaNeue-Light
NSAttributedStringKey.foregroundColor: UIColor.black, // NSAttributedStringKey.foregroundColor: titleColor,
NSAttributedStringKey.paragraphStyle: textStyle]
let textHeight = textContent.boundingRect(with: CGSize(width: textRect.width, height: textRect.height), options: .usesLineFragmentOrigin, attributes: textFontAttributes, context: nil).height
context.saveGState()
context.clip(to: textRect)
textContent.draw(in: CGRect(x: textRect.minX, y: textRect.minY + (textRect.height - textHeight) / 2, width: textRect.width, height: textHeight), withAttributes: textFontAttributes)
context.restoreGState();
}
override func prepareForInterfaceBuilder() {
self.currentTime = Date()
self.startTime = self.currentTime?.addingTimeInterval(-self.duration/3)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment