Created
February 7, 2017 02:42
-
-
Save SAllen0400/303c3a49142eeace8e57d269943e048d to your computer and use it in GitHub Desktop.
Custom Slider
This file contains 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
import Foundation | |
class BMSliderControl: UIControl { | |
var minimumValue: Double = 0.0 { | |
didSet { | |
updateLayerFrames() | |
} | |
} | |
var maximumValue: Double = 1.0 { | |
didSet { | |
updateLayerFrames() | |
} | |
} | |
var upperValue: Double = 0.5 { | |
didSet { | |
updateLayerFrames() | |
} | |
} | |
var lowerValue: Double = 0.0 { | |
didSet { | |
updateLayerFrames() | |
} | |
} | |
var trackTintColor: UIColor = Colors.darkGrey { | |
didSet { | |
trackLayer.setNeedsDisplay() | |
} | |
} | |
var trackHighlightTintColor: UIColor = Colors.brightOrange { | |
didSet { | |
trackLayer.setNeedsDisplay() | |
} | |
} | |
var thumbTintColor: UIColor = UIColor.whiteColor() { | |
didSet { | |
upperThumbLayer.setNeedsDisplay() | |
} | |
} | |
var curvaceousness: CGFloat = 1.0 { | |
didSet { | |
trackLayer.setNeedsDisplay() | |
upperThumbLayer.setNeedsDisplay() | |
} | |
} | |
var thumbWidth: CGFloat { | |
return CGFloat(bounds.height) | |
} | |
var previousLocation = CGPoint() | |
let trackLayer = BMSliderTrackLayer() | |
let upperThumbLayer = BMSliderThumbLayer() | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
trackLayer.slider = self | |
trackLayer.contentsScale = UIScreen.mainScreen().scale | |
layer.addSublayer(trackLayer) | |
upperThumbLayer.slider = self | |
upperThumbLayer.contentsScale = UIScreen.mainScreen().scale | |
layer.addSublayer(upperThumbLayer) | |
} | |
required init?(coder: NSCoder) { | |
super.init(coder: coder) | |
} | |
func updateLayerFrames() { | |
CATransaction.begin() | |
CATransaction.setDisableActions(true) | |
trackLayer.frame = bounds | |
trackLayer.setNeedsDisplay() | |
let upperThumbCenter = CGFloat(positionForValue(upperValue)) | |
upperThumbLayer.frame = CGRect(x: upperThumbCenter - thumbWidth / 2.0, y: 0.0, width: thumbWidth, height: thumbWidth) | |
upperThumbLayer.setNeedsDisplay() | |
CATransaction.commit() | |
} | |
func positionForValue(value: Double) -> Double { | |
return Double(bounds.width - thumbWidth) * (value - minimumValue) / | |
(maximumValue - minimumValue) + Double(thumbWidth / 2.0) | |
} | |
override var frame: CGRect { | |
didSet { | |
updateLayerFrames() | |
} | |
} | |
override func beginTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool { | |
previousLocation = touch.locationInView(self) | |
if upperThumbLayer.frame.contains(previousLocation) { | |
upperThumbLayer.highlighted = true | |
} | |
return upperThumbLayer.highlighted | |
} | |
func boundValue(value: Double, toLowerValue lowerValue: Double, upperValue: Double) -> Double { | |
return min(max(value, lowerValue), upperValue) | |
} | |
override func continueTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool { | |
let location = touch.locationInView(self) | |
let deltaLocation = Double(location.x - previousLocation.x) | |
let deltaValue = (maximumValue - minimumValue) * deltaLocation / Double(bounds.width - thumbWidth) | |
previousLocation = location | |
if upperThumbLayer.highlighted { | |
upperValue += deltaValue | |
upperValue = boundValue(upperValue, toLowerValue: lowerValue, upperValue: maximumValue) | |
} | |
sendActionsForControlEvents(.ValueChanged) | |
return true | |
} | |
override func endTrackingWithTouch(touch: UITouch?, withEvent event: UIEvent?) { | |
upperThumbLayer.highlighted = false | |
} | |
} | |
class BMSliderThumbLayer: CALayer { | |
var highlighted: Bool = false { | |
didSet { | |
setNeedsDisplay() | |
} | |
} | |
weak var slider: BMSliderControl? | |
override func drawInContext(ctx: CGContext) { | |
if let slider = slider { | |
let thumbFrame = bounds.insetBy(dx: 3.0, dy: 3.0) | |
let cornerRadius = thumbFrame.height * slider.curvaceousness / 2.0 | |
let thumbPath = UIBezierPath(roundedRect: thumbFrame, cornerRadius: cornerRadius) | |
// Fill - with a subtle shadow | |
let shadowColor = Colors.veryDarkGrey | |
CGContextSetShadowWithColor(ctx, CGSize(width: 0.0, height: 2), 3, shadowColor.CGColor) | |
CGContextSetFillColorWithColor(ctx, slider.thumbTintColor.CGColor) | |
CGContextAddPath(ctx, thumbPath.CGPath) | |
CGContextFillPath(ctx) | |
// Outline | |
CGContextSetStrokeColorWithColor(ctx, shadowColor.CGColor) | |
CGContextSetLineWidth(ctx, 0.5) | |
CGContextAddPath(ctx, thumbPath.CGPath) | |
CGContextStrokePath(ctx) | |
if highlighted { | |
CGContextSetFillColorWithColor(ctx, UIColor(white: 0.0, alpha: 0.1).CGColor) | |
CGContextAddPath(ctx, thumbPath.CGPath) | |
CGContextFillPath(ctx) | |
} | |
} | |
} | |
} | |
class BMSliderTrackLayer: CALayer { | |
weak var slider: BMSliderControl? | |
override func drawInContext(ctx: CGContext) { | |
if let slider = slider { | |
let cornerRadius = bounds.height * slider.curvaceousness / 2.0 | |
let path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius) | |
CGContextAddPath(ctx, path.CGPath) | |
// Fill the track | |
CGContextSetFillColorWithColor(ctx, slider.trackTintColor.CGColor) | |
CGContextAddPath(ctx, path.CGPath) | |
CGContextFillPath(ctx) | |
// Fill the highlighted range | |
CGContextSetFillColorWithColor(ctx, slider.trackHighlightTintColor.CGColor) | |
let upperValuePosition = CGFloat(slider.positionForValue(slider.upperValue)) | |
let rect = CGRect(x: 0, y: 0.0, width: upperValuePosition + 20, height: bounds.height) | |
let highlightedRect = UIBezierPath(roundedRect: rect, cornerRadius: rect.size.height/2) | |
CGContextAddPath(ctx, highlightedRect.CGPath) | |
CGContextFillPath(ctx) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment