diff --git a/CircleSlider.podspec b/CircleSlider.podspec index 959c2e9..548a957 100644 --- a/CircleSlider.podspec +++ b/CircleSlider.podspec @@ -1,15 +1,13 @@ Pod::Spec.new do |s| s.name = "CircleSlider" -s.version = "0.2.0" +s.version = "0.2.3" s.summary = "CircleSlider is a Circular slider library. written in pure swift." -s.homepage = "https://github.com/shushutochako/CircleSlider" +s.homepage = "https://github.com/lampi87/CircleSlider.git" s.license = 'MIT' +s.source = { :git => "https://github.com/lampi87/CircleSlider.git", :tag => s.version.to_s } s.author = { "shushutochako" => "shushutochako22@gmail.com" } -s.source = { :git => "https://github.com/shushutochako/CircleSlider.git", :tag => s.version.to_s } -s.social_media_url = 'https://twitter.com/shushutochako' - s.platform = :ios, '8.0' s.requires_arc = true s.source_files = 'Pod/Classes/**/*' -end \ No newline at end of file +end diff --git a/Pod/Classes/CircleSlider.swift b/Pod/Classes/CircleSlider.swift index c24f542..ea2f2f1 100644 --- a/Pod/Classes/CircleSlider.swift +++ b/Pod/Classes/CircleSlider.swift @@ -7,175 +7,200 @@ // public enum CircleSliderOption { - case StartAngle(Double) - case BarColor(UIColor) - case TrackingColor(UIColor) - case ThumbColor(UIColor) - case BarWidth(CGFloat) - case ThumbWidth(CGFloat) - case MaxValue(Float) - case MinValue(Float) - case SliderEnabled(Bool) + case StartAngle(Double) + case BarColor(UIColor) + case TrackingColor(UIColor) + case ThumbColor(UIColor) + case BarWidth(CGFloat) + case ThumbWidth(CGFloat) + case ThumbImage(UIImage?) + case MaxValue(Float) + case MinValue(Float) + case SliderEnabled(Bool) + case ThumbOffset(Bool) } public class CircleSlider: UIControl { - private var latestDegree: Double = 0 - private var _value: Float = 0 - public var value: Float { - get { - return self._value - } - set { - self._value = newValue - self.sendActionsForControlEvents(.ValueChanged) - let degree = Math.degreeFromValue(self.startAngle, value: self.value, maxValue: self.maxValue, minValue: self.minValue) - self.layout(degree) - } - } - private var trackLayer: TrackLayer! { - didSet { - self.layer.addSublayer(self.trackLayer) - } - } - private var thumbView: UIView! { - didSet { - if self.sliderEnabled { - self.thumbView.backgroundColor = self.thumbColor - self.thumbView.center = self.thumbCenter(self.startAngle) - self.thumbView.layer.cornerRadius = self.thumbView!.bounds.size.width * 0.5 - self.addSubview(self.thumbView) - } else { - self.thumbView.hidden = true - } - } - } - // Options - private var startAngle: Double = -90 - private var barColor = UIColor.lightGrayColor() - private var trackingColor = UIColor.blueColor() - private var thumbColor = UIColor.blackColor() - private var barWidth: CGFloat = 20 - private var maxValue: Float = 100 - private var minValue: Float = 0 - private var sliderEnabled = true - private var _thumbWidth: CGFloat? - private var thumbWidth: CGFloat { - get { - if let retValue = self._thumbWidth { - return retValue - } - return self.barWidth * 1.5 - } - set { - self._thumbWidth = newValue - } - } - - override public func awakeFromNib() { - super.awakeFromNib() - self.backgroundColor = UIColor.clearColor() - } - - public init(frame: CGRect, options: [CircleSliderOption]?) { - super.init(frame: frame) - if let options = options { - self.build(options) - } - } - - required public init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override public func layoutSublayersOfLayer(layer: CALayer) { - if self.trackLayer == nil { - self.trackLayer = TrackLayer(bounds: self.bounds, setting: self.createLayerSetting()) - } - if self.thumbView == nil { - self.thumbView = UIView(frame: CGRect(x: 0, y: 0, width: self.thumbWidth, height: self.thumbWidth)) - } - } - - override public func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? { - let rect = self.trackLayer.hollowRect - let hollowPath = UIBezierPath(roundedRect: rect, cornerRadius: self.trackLayer.hollowRadius) - if !(CGRectContainsPoint(self.bounds, point) || hollowPath.containsPoint(point)) || - !(CGRectContainsPoint(self.thumbView.frame, point)) { - return nil - } - return self - } - - override public func continueTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool { - let degree = Math.pointPairToBearingDegrees(self.center, endPoint: touch.locationInView(self)) - self.latestDegree = degree - self.layout(degree) - let value = Float(Math.adjustValue(self.startAngle, degree: degree, maxValue: self.maxValue, minValue: self.minValue)) - self.value = value - return true - } - - public func changeOptions(options: [CircleSliderOption]) { - self.build(options) - self.redraw() - } - - private func redraw() { - self.trackLayer.removeFromSuperlayer() - self.trackLayer = TrackLayer(bounds: self.bounds, setting: self.createLayerSetting()) - self.thumbView.removeFromSuperview() - self.thumbView = UIView(frame: CGRect(x: 0, y: 0, width: self.thumbWidth, height: self.thumbWidth)) - self.layout(self.latestDegree) - } + private var latestDegree: Double = 0 + private var _value: Float = 0 + public var value: Float { + get { + return self._value + } + set { + self._value = newValue + self.sendActionsForControlEvents(.ValueChanged) + let degree = Math.degreeFromValue(self.startAngle, value: self.value, maxValue: self.maxValue, minValue: self.minValue) + self.layout(degree) + } + } + private var trackLayer: TrackLayer! { + didSet { + self.layer.addSublayer(self.trackLayer) + } + } + private var thumbImageView: UIImageView! { + didSet { + if self.sliderEnabled && self.thumbImage != nil { + self.thumbImageView.image = self.thumbImage + self.addSubview(self.thumbImageView) + } + } + } + private var thumbView: UIView! { + didSet { + if self.sliderEnabled { + self.thumbView.backgroundColor = self.thumbColor + self.thumbView.center = self.thumbCenter(self.startAngle) + self.thumbView.layer.cornerRadius = self.thumbView!.bounds.size.width * 0.5 + self.addSubview(self.thumbView) + } else { + self.thumbView.hidden = true + } + } + } + // Options + private var startAngle: Double = -90 + private var barColor = UIColor.lightGrayColor() + private var trackingColor = UIColor.blueColor() + private var thumbColor = UIColor.blackColor() + private var thumbImage: UIImage? + private var barWidth: CGFloat = 20 + private var maxValue: Float = 100 + private var minValue: Float = 0 + private var sliderEnabled = true + private var thumbOffset = true + private var _thumbWidth: CGFloat? + private var thumbWidth: CGFloat { + get { + if let retValue = self._thumbWidth { + return retValue + } + return self.barWidth * 1.5 + } + set { + self._thumbWidth = newValue + } + } + + override public func awakeFromNib() { + super.awakeFromNib() + self.backgroundColor = UIColor.clearColor() + } + + public init(frame: CGRect, options: [CircleSliderOption]?) { + super.init(frame: frame) + if let options = options { + self.build(options) + } + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func layoutSublayersOfLayer(layer: CALayer) { + if self.trackLayer == nil { + self.trackLayer = TrackLayer(bounds: self.bounds, setting: self.createLayerSetting()) + } + if self.thumbView == nil { + self.thumbView = UIView(frame: CGRect(x: 0, y: 0, width: self.thumbWidth, height: self.thumbWidth)) + } + if self.thumbImageView == nil { + self.thumbImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: self.thumbWidth, height: self.thumbWidth)) + } + } - private func build(options: [CircleSliderOption]) { - for option in options { - switch option { - case let .StartAngle(value): - self.startAngle = value - self.latestDegree = self.startAngle - case let .BarColor(value): - self.barColor = value - case let .TrackingColor(value): - self.trackingColor = value - case let .ThumbColor(value): - self.thumbColor = value - case let .BarWidth(value): - self.barWidth = value - case let .ThumbWidth(value): - self.thumbWidth = value - case let .MaxValue(value): - self.maxValue = value - case let .MinValue(value): - self.minValue = value - self._value = self.minValue - case let .SliderEnabled(value): - self.sliderEnabled = value - } - } - // Adjust because value not rise up to the maxValue - self.maxValue++ - } - - private func layout(degree: Double) { - if let trackLayer = self.trackLayer, thumbView = self.thumbView { - trackLayer.degree = degree - thumbView.center = self.thumbCenter(degree) - trackLayer.setNeedsDisplay() - } - } - - private func createLayerSetting() -> TrackLayer.Setting { - var setting = TrackLayer.Setting() - setting.startAngle = self.startAngle - setting.barColor = self.barColor - setting.trackingColor = self.trackingColor - setting.barWidth = self.barWidth - return setting - } - - private func thumbCenter(degree: Double) -> CGPoint { - let radius = (self.bounds.width * 0.5) - (self.barWidth * 0.5) - return Math.pointFromAngle(self.frame, angle: degree, radius: Double(radius)) - } + override public func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? { + let rect = self.trackLayer.hollowRect + let hollowPath = UIBezierPath(roundedRect: rect, cornerRadius: self.trackLayer.hollowRadius) + if !(CGRectContainsPoint(self.bounds, point) || hollowPath.containsPoint(point)) || + !(CGRectContainsPoint(self.thumbView.frame, point)) { + return nil + } + return self + } + + override public func continueTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool { + let degree = Math.pointPairToBearingDegrees(self.center, endPoint: touch.locationInView(self)) + self.latestDegree = degree + self.layout(degree) + let value = Float(Math.adjustValue(self.startAngle, degree: degree, maxValue: self.maxValue, minValue: self.minValue)) + self.value = value + return true + } + + public func changeOptions(options: [CircleSliderOption]) { + self.build(options) + self.redraw() + } + + private func redraw() { + self.trackLayer.removeFromSuperlayer() + self.trackLayer = TrackLayer(bounds: self.bounds, setting: self.createLayerSetting()) + self.thumbView.removeFromSuperview() + self.thumbView = UIView(frame: CGRect(x: 0, y: 0, width: self.thumbWidth, height: self.thumbWidth)) + self.thumbImageView.removeFromSuperview() + self.thumbImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: self.thumbWidth, height: self.thumbWidth)) + self.layout(self.latestDegree) + } + + private func build(options: [CircleSliderOption]) { + for option in options { + switch option { + case let .StartAngle(value): + self.startAngle = value + self.latestDegree = self.startAngle + case let .BarColor(value): + self.barColor = value + case let .TrackingColor(value): + self.trackingColor = value + case let .ThumbColor(value): + self.thumbColor = value + case let .BarWidth(value): + self.barWidth = value + case let .ThumbWidth(value): + self.thumbWidth = value + case let .MaxValue(value): + self.maxValue = value + case let .MinValue(value): + self.minValue = value + self._value = self.minValue + case let .SliderEnabled(value): + self.sliderEnabled = value + case let .ThumbImage(value): + self.thumbImage = value + case let .ThumbOffset(value): + self.thumbOffset = value + } + } + // Adjust because value not rise up to the maxValue + self.maxValue += 1 + } + + private func layout(degree: Double) { + if let trackLayer = self.trackLayer, thumbView = self.thumbView { + trackLayer.degree = degree + thumbView.center = self.thumbCenter(degree) + thumbImageView.center = self.thumbCenter(degree) + trackLayer.setNeedsDisplay() + } + } + + private func createLayerSetting() -> TrackLayer.Setting { + var setting = TrackLayer.Setting() + setting.startAngle = self.startAngle + setting.barColor = self.barColor + setting.trackingColor = self.trackingColor + setting.barWidth = self.barWidth + return setting + } + + private func thumbCenter(degree: Double) -> CGPoint { + var radius = (self.bounds.width * 0.5) + if thumbOffset { + radius = radius - (self.barWidth * 0.5) + } + return Math.pointFromAngle(self.frame, angle: degree, radius: Double(radius)) + } } diff --git a/Pod/Classes/Math.swift b/Pod/Classes/Math.swift index 1db3a01..c3af09b 100644 --- a/Pod/Classes/Math.swift +++ b/Pod/Classes/Math.swift @@ -9,46 +9,46 @@ import UIKit internal class Math { - - internal class func degreesToRadians(angle: Double) -> Double { - return angle / 180 * M_PI - } - - internal class func pointFromAngle(frame: CGRect, angle: Double, radius: Double) -> CGPoint { - let radian = self.degreesToRadians(angle) - let x = Double(CGRectGetMidX(frame)) + cos(radian) * radius - let y = Double(CGRectGetMidY(frame)) + sin(radian) * radius - return CGPoint(x: x, y: y) - } - - internal class func pointPairToBearingDegrees(startPoint: CGPoint, endPoint: CGPoint) -> Double { - let originPoint = CGPointMake(endPoint.x - startPoint.x, endPoint.y - startPoint.y) - let bearingRadians = atan2(Double(originPoint.y), Double(originPoint.x)) - var bearingDegrees = bearingRadians * (180.0 / M_PI) - bearingDegrees = (bearingDegrees > 0.0 ? bearingDegrees : (360.0 + bearingDegrees)) - return bearingDegrees - } - - internal class func adjustValue(startAngle: Double, degree: Double, maxValue: Float, minValue: Float) -> Double { - let ratio = Double((maxValue - minValue) / 360) - let ratioStart = ratio * startAngle - let ratioDegree = ratio * degree - let adjustValue: Double - if startAngle < 0 { - adjustValue = (360 + startAngle) > degree ? (ratioDegree - ratioStart) : (ratioDegree - ratioStart) - (360 * ratio) - } else { - adjustValue = (360 - (360 - startAngle)) < degree ? (ratioDegree - ratioStart) : (ratioDegree - ratioStart) + (360 * ratio) + + internal class func degreesToRadians(angle: Double) -> Double { + return angle / 180 * M_PI + } + + internal class func pointFromAngle(frame: CGRect, angle: Double, radius: Double) -> CGPoint { + let radian = self.degreesToRadians(angle) + let x = Double(CGRectGetMidX(frame)) + cos(radian) * radius + let y = Double(CGRectGetMidY(frame)) + sin(radian) * radius + return CGPoint(x: x, y: y) + } + + internal class func pointPairToBearingDegrees(startPoint: CGPoint, endPoint: CGPoint) -> Double { + let originPoint = CGPointMake(endPoint.x - startPoint.x, endPoint.y - startPoint.y) + let bearingRadians = atan2(Double(originPoint.y), Double(originPoint.x)) + var bearingDegrees = bearingRadians * (180.0 / M_PI) + bearingDegrees = (bearingDegrees > 0.0 ? bearingDegrees : (360.0 + bearingDegrees)) + return bearingDegrees + } + + internal class func adjustValue(startAngle: Double, degree: Double, maxValue: Float, minValue: Float) -> Double { + let ratio = Double((maxValue - minValue) / 360) + let ratioStart = ratio * startAngle + let ratioDegree = ratio * degree + let adjustValue: Double + if startAngle < 0 { + adjustValue = (360 + startAngle) > degree ? (ratioDegree - ratioStart) : (ratioDegree - ratioStart) - (360 * ratio) + } else { + adjustValue = (360 - (360 - startAngle)) < degree ? (ratioDegree - ratioStart) : (ratioDegree - ratioStart) + (360 * ratio) + } + return adjustValue + (Double(minValue)) + } + + internal class func adjustDegree(startAngle: Double, degree: Double) -> Double { + return (360 + startAngle) > degree ? degree : -(360 - degree) + } + + internal class func degreeFromValue(startAngle: Double, value: Float, maxValue: Float, minValue: Float) -> Double { + let ratio = Double((maxValue - minValue) / 360) + let angle = Double(value) / ratio + return angle + startAngle - (Double(minValue) / ratio) } - return adjustValue + (Double(minValue)) - } - - internal class func adjustDegree(startAngle: Double, degree: Double) -> Double { - return (360 + startAngle) > degree ? degree : -(360 - degree) - } - - internal class func degreeFromValue(startAngle: Double, value: Float, maxValue: Float, minValue: Float) -> Double { - let ratio = Double((maxValue - minValue) / 360) - let angle = Double(value) / ratio - return angle + startAngle - (Double(minValue) / ratio) - } } diff --git a/Pod/Classes/TrackLayer.swift b/Pod/Classes/TrackLayer.swift index fc4045c..b24f121 100644 --- a/Pod/Classes/TrackLayer.swift +++ b/Pod/Classes/TrackLayer.swift @@ -9,69 +9,69 @@ import UIKit internal class TrackLayer: CAShapeLayer { - struct Setting { - var startAngle = Double() - var barWidth = CGFloat() - var barColor = UIColor() - var trackingColor = UIColor() - } - internal var setting = Setting!() - internal var degree: Double = 0 - internal var hollowRadius: CGFloat { - return (self.bounds.width * 0.5) - self.setting.barWidth - } - internal var currentCenter: CGPoint { - return CGPoint(x: CGRectGetMidX(self.bounds), y: CGRectGetMidY(self.bounds)) - } - internal var hollowRect: CGRect { - return CGRect( - x: self.currentCenter.x - self.hollowRadius, - y: self.currentCenter.y - self.hollowRadius, - width: self.hollowRadius * 2.0, - height: self.hollowRadius * 2.0) - } - internal init(bounds: CGRect, setting: Setting) { - super.init() - self.bounds = bounds - self.setting = setting - self.cornerRadius = self.bounds.size.width * 0.5 - self.position = self.currentCenter - self.backgroundColor = self.setting.barColor.CGColor - self.mask() - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override internal func drawInContext(ctx: CGContext) { - self.drawTrack(ctx) - } - - private func mask() { - let maskLayer = CAShapeLayer() - maskLayer.bounds = self.bounds - let ovalRect = self.hollowRect - let path = UIBezierPath(ovalInRect: ovalRect) - path.appendPath(UIBezierPath(rect: maskLayer.bounds)) - maskLayer.path = path.CGPath - maskLayer.position = self.currentCenter - maskLayer.fillRule = kCAFillRuleEvenOdd - self.mask = maskLayer - } - - private func drawTrack(ctx: CGContext) { - let adjustDegree = Math.adjustDegree(self.setting.startAngle, degree: self.degree) - let centerX = self.currentCenter.x - let centerY = self.currentCenter.y - let radius = min(centerX, centerY) - CGContextSetFillColorWithColor(ctx, self.setting.trackingColor.CGColor) - CGContextBeginPath(ctx) - CGContextMoveToPoint(ctx, centerX, centerY) - CGContextAddArc(ctx, centerX, centerY, radius, - CGFloat(Math.degreesToRadians(self.setting.startAngle - M_PI * 0.5)), - CGFloat(Math.degreesToRadians(adjustDegree - M_PI * 0.5)), 0) - CGContextClosePath(ctx); - CGContextFillPath(ctx); - } + struct Setting { + var startAngle = Double() + var barWidth = CGFloat() + var barColor = UIColor() + var trackingColor = UIColor() + } + internal var setting: Setting! + internal var degree: Double = 0 + internal var hollowRadius: CGFloat { + return (self.bounds.width * 0.5) - self.setting.barWidth + } + internal var currentCenter: CGPoint { + return CGPoint(x: CGRectGetMidX(self.bounds), y: CGRectGetMidY(self.bounds)) + } + internal var hollowRect: CGRect { + return CGRect( + x: self.currentCenter.x - self.hollowRadius, + y: self.currentCenter.y - self.hollowRadius, + width: self.hollowRadius * 2.0, + height: self.hollowRadius * 2.0) + } + internal init(bounds: CGRect, setting: Setting) { + super.init() + self.bounds = bounds + self.setting = setting + self.cornerRadius = self.bounds.size.width * 0.5 + self.position = self.currentCenter + self.backgroundColor = self.setting.barColor.CGColor + self.mask() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override internal func drawInContext(ctx: CGContext) { + self.drawTrack(ctx) + } + + private func mask() { + let maskLayer = CAShapeLayer() + maskLayer.bounds = self.bounds + let ovalRect = self.hollowRect + let path = UIBezierPath(ovalInRect: ovalRect) + path.appendPath(UIBezierPath(rect: maskLayer.bounds)) + maskLayer.path = path.CGPath + maskLayer.position = self.currentCenter + maskLayer.fillRule = kCAFillRuleEvenOdd + self.mask = maskLayer + } + + private func drawTrack(ctx: CGContext) { + let adjustDegree = Math.adjustDegree(self.setting.startAngle, degree: self.degree) + let centerX = self.currentCenter.x + let centerY = self.currentCenter.y + let radius = min(centerX, centerY) + CGContextSetFillColorWithColor(ctx, self.setting.trackingColor.CGColor) + CGContextBeginPath(ctx) + CGContextMoveToPoint(ctx, centerX, centerY) + CGContextAddArc(ctx, centerX, centerY, radius, + CGFloat(Math.degreesToRadians(self.setting.startAngle - M_PI * 0.5)), + CGFloat(Math.degreesToRadians(adjustDegree - M_PI * 0.5)), 0) + CGContextClosePath(ctx); + CGContextFillPath(ctx); + } } diff --git a/README.md b/README.md index b556e9f..ca4004e 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ self.circleSlider.changeOptions([.BarWidth(45)]) - ``case ThumbColor(UIColor)`` - ``case BarWidth(CGFloat)`` - ``case ThumbWidth(CGFloat)`` +- ``case ThumbImage(UIImage?)`` - ``case MaxValue(Float)`` - ``case MinValue(Float)`` - ``case SliderEnabled(Bool) ``