14 Commits

Author SHA1 Message Date
Jalal Ouraigua 7fdd18f890 Merge remote-tracking branch 'refs/remotes/origin/master' 2018-12-22 05:51:50 +00:00
Jalal Ouraigua ea3ce73f51 Remove isNegative. No longer needed 2018-12-22 05:51:09 +00:00
Jalal Ouraigua 556f8a858c Update README.md 2018-12-22 05:08:48 +00:00
Jalal Ouraigua ef28529778 - Change the circularSlider's value to function as the generic UIKit slider's value
- Enhance min/max values to support any range positive, negative or both.
- Enhance the label's text display by specifying decimal places or completely override it and set your own text.
2018-12-22 04:59:38 +00:00
Jalal Ouraigua b3c83b0daf Update version 2018-12-10 05:08:52 +00:00
Jalal Ouraigua cb8fc1f889 Add value change Target-Action 2018-12-10 04:52:18 +00:00
Jalal Ouraigua eb18fac98b Update codebeat config 2018-12-09 05:10:14 +00:00
Jalal Ouraigua ae1febd06c Update gitignore 2018-12-09 00:10:38 +00:00
Jalal Ouraigua 04181154f3 Update README 2018-12-09 00:05:40 +00:00
Jalal Ouraigua d44c2519b4 New Tag 2018-12-08 23:54:20 +00:00
Jalal Ouraigua 001dbd7871 Update banner gif 2018-12-08 23:46:31 +00:00
Jalal Ouraigua bf53a2decb Update podspec 2018-12-08 22:49:02 +00:00
Jalal Ouraigua 5bac4820e0 Add screenshots 2018-12-08 21:13:05 +00:00
Jalal Ouraigua 83176a8c16 Update README 2018-12-08 21:12:01 +00:00
17 changed files with 310 additions and 100 deletions
+10
View File
@@ -0,0 +1,10 @@
{
"SWIFT": {
"TOO_MANY_IVARS": [8, 12, 16, 20],
"TOO_MANY_FUNCTIONS": [46, 55, 65, 85],
"ARITY" : [5, 6, 7, 8],
"ABC": [15, 25, 50, 70],
"TOTAL_COMPLEXITY": [100, 180, 280, 400],
"TOTAL_LOC": [250, 300, 350, 450]
}
}
+40
View File
@@ -0,0 +1,40 @@
# OS X
.DS_Store
# Xcode
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
*.xccheckout
profile
*.moved-aside
DerivedData
*.hmap
*.ipa
# Bundler
.bundle
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
#
# Note: if you ignore the Pods directory, make sure to uncomment
# `pod install` in .travis.yml
#
# Pods/
*.podspec
Podfile*
+1
View File
@@ -0,0 +1 @@
4.2
-39
View File
@@ -1,39 +0,0 @@
#
# Be sure to run `pod lib lint KnobFramework.podspec' to ensure this is a
# valid spec before submitting.
#
# Any lines starting with a # are optional, but their use is encouraged
# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
#
Pod::Spec.new do |s|
s.name = 'KnobFramework'
s.version = '1.0.0'
s.summary = 'A highly customisable and reusable iOS circular slider.'
# This description is used to generate tags and improve search results.
# * Think: What does it do? Why did you write it? What is the focus?
# * Try to keep it short, snappy and to the point.
# * Write the description between the DESC delimiters below.
# * Finally, don't worry about the indent, CocoaPods strips it!
s.description = <<-DESC
KnobFramework is a highly customisable and reusable iOS circular slider that mimics the behaviour of a knob control. It uses no preset images and every one of its components is drawn completely in code making it extremely adaptable to every design and theme.
It's written in Swift 4.2 and it's 100% IBDesignable and all parameters are IBInspectable.
You can control almost every aspect of the slider's design: Size, colors, direction (clockwise/anti-clockwise), etc...
DESC
s.homepage = 'https://github.com/ouraigua/KnobFramework'
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'Jalal Ouraigua' => 'ouraigua@icloud.com' }
s.source = { :git => 'https://github.com/ouraigua/KnobFramework.git', :tag => s.version.to_s }
s.social_media_url = 'https://twitter.com/ouraigua'
s.ios.deployment_target = '10.0'
s.source_files = 'KnobFramework/**/*.{swift}'
s.documentation_url = 'http://ouraigua.com/github/jocircularslider/docs/index.html'
end
+31 -1
View File
@@ -15,6 +15,11 @@
AF4F27DE21BC3DD000D39C60 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF4F27D721BC3DD000D39C60 /* Extensions.swift */; };
AF4F27DF21BC3DD000D39C60 /* PointerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF4F27D821BC3DD000D39C60 /* PointerView.swift */; };
AF4F27E021BC3DD000D39C60 /* DotView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF4F27D921BC3DD000D39C60 /* DotView.swift */; };
AF4F27E921BC6A8200D39C60 /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = AF4F27E521BC6A8200D39C60 /* .gitignore */; };
AF4F27EA21BC6A8200D39C60 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = AF4F27E621BC6A8200D39C60 /* LICENSE */; };
AF4F27EB21BC6A8200D39C60 /* .codebeatsettings in Resources */ = {isa = PBXBuildFile; fileRef = AF4F27E721BC6A8200D39C60 /* .codebeatsettings */; };
AF4F27EC21BC6A8200D39C60 /* .swift-version in Resources */ = {isa = PBXBuildFile; fileRef = AF4F27E821BC6A8200D39C60 /* .swift-version */; };
AF4F280E21BC94A100D39C60 /* KnobFramework.podspec in Resources */ = {isa = PBXBuildFile; fileRef = AF4F280D21BC94A100D39C60 /* KnobFramework.podspec */; };
FDE1C785BDA3FFA82992A815 /* libPods-KnobFramework.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F7FDC350CB218A84F96BF04E /* libPods-KnobFramework.a */; };
/* End PBXBuildFile section */
@@ -29,6 +34,12 @@
AF4F27D721BC3DD000D39C60 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
AF4F27D821BC3DD000D39C60 /* PointerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PointerView.swift; sourceTree = "<group>"; };
AF4F27D921BC3DD000D39C60 /* DotView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DotView.swift; sourceTree = "<group>"; };
AF4F27E221BC693000D39C60 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
AF4F27E521BC6A8200D39C60 /* .gitignore */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = .gitignore; sourceTree = "<group>"; };
AF4F27E621BC6A8200D39C60 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
AF4F27E721BC6A8200D39C60 /* .codebeatsettings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = .codebeatsettings; sourceTree = "<group>"; };
AF4F27E821BC6A8200D39C60 /* .swift-version */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ".swift-version"; sourceTree = "<group>"; };
AF4F280D21BC94A100D39C60 /* KnobFramework.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = KnobFramework.podspec; sourceTree = "<group>"; };
CD06525455A889F9C77DF83D /* Pods-KnobFramework.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KnobFramework.release.xcconfig"; path = "Pods/Target Support Files/Pods-KnobFramework/Pods-KnobFramework.release.xcconfig"; sourceTree = "<group>"; };
E79A965E70FE5C13B3DAFCB6 /* Pods-KnobFramework.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KnobFramework.debug.xcconfig"; path = "Pods/Target Support Files/Pods-KnobFramework/Pods-KnobFramework.debug.xcconfig"; sourceTree = "<group>"; };
F7FDC350CB218A84F96BF04E /* libPods-KnobFramework.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-KnobFramework.a"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -58,6 +69,7 @@
AF4F27BE21BC3B3200D39C60 = {
isa = PBXGroup;
children = (
AF4F27E121BC691A00D39C60 /* Podspec Metadata */,
AF4F27CA21BC3B3200D39C60 /* KnobFramework */,
AF4F27C921BC3B3200D39C60 /* Products */,
39B98C47AA8B4792DF7C299D /* Pods */,
@@ -77,8 +89,8 @@
isa = PBXGroup;
children = (
AF4F27CB21BC3B3200D39C60 /* KnobFramework.h */,
AF4F27D421BC3DD000D39C60 /* CircularLayer.swift */,
AF4F27D321BC3DD000D39C60 /* CircularSlider.swift */,
AF4F27D421BC3DD000D39C60 /* CircularLayer.swift */,
AF4F27D521BC3DD000D39C60 /* DotLayer.swift */,
AF4F27D921BC3DD000D39C60 /* DotView.swift */,
AF4F27D721BC3DD000D39C60 /* Extensions.swift */,
@@ -89,6 +101,19 @@
path = KnobFramework;
sourceTree = "<group>";
};
AF4F27E121BC691A00D39C60 /* Podspec Metadata */ = {
isa = PBXGroup;
children = (
AF4F280D21BC94A100D39C60 /* KnobFramework.podspec */,
AF4F27E221BC693000D39C60 /* README.md */,
AF4F27E621BC6A8200D39C60 /* LICENSE */,
AF4F27E721BC6A8200D39C60 /* .codebeatsettings */,
AF4F27E521BC6A8200D39C60 /* .gitignore */,
AF4F27E821BC6A8200D39C60 /* .swift-version */,
);
name = "Podspec Metadata";
sourceTree = "<group>";
};
B381CC5598F6BFBA4C33DBA7 /* Frameworks */ = {
isa = PBXGroup;
children = (
@@ -167,6 +192,11 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
AF4F27EA21BC6A8200D39C60 /* LICENSE in Resources */,
AF4F27E921BC6A8200D39C60 /* .gitignore in Resources */,
AF4F27EB21BC6A8200D39C60 /* .codebeatsettings in Resources */,
AF4F27EC21BC6A8200D39C60 /* .swift-version in Resources */,
AF4F280E21BC94A100D39C60 /* KnobFramework.podspec in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
+72 -58
View File
@@ -227,21 +227,24 @@ open class CircularSlider: UIControl {
set { renderer.miniDotView.highlightColor = newValue ? highlightColor : nil }
}
@IBInspectable open var minimumValue: CGFloat {
get { return CGFloat(renderer.minimumValue) }
/**
This value will be pinned to minimumValue/maximumValue
The default value of this property is 0.0.
*/
@IBInspectable open var value: CGFloat { // always min <= value <= max
get { return CGFloat(renderer.value) * (maximumValue - minimumValue) + minimumValue }
set {
renderer.minimumValue = Float(newValue)
setValue(value, isPercentage: true)
guard maximumValue >= minimumValue else {
fatalError("`maximumValue` should be greater then `minimumValue`.")
}
let rangedValue = min(maximumValue, max(minimumValue, newValue))
renderer.value = Float((rangedValue - minimumValue) / (maximumValue - minimumValue))
}
}
@IBInspectable open var maximumValue: CGFloat {
get { return CGFloat(renderer.maximumValue) }
set {
renderer.maximumValue = Float(newValue)
setValue(value, isPercentage: true)
}
}
@IBInspectable open var minimumValue: CGFloat = 0
@IBInspectable open var maximumValue: CGFloat = 100
@IBInspectable open var startAngle: CGFloat {
get { return renderer.startAngle.toDegree }
@@ -258,29 +261,29 @@ open class CircularSlider: UIControl {
set { renderer.isClockwise = newValue }
}
@IBInspectable open var labelDecimalPlaces: Int = 0
// MARK: - Private Properties
lazy private var renderer = Renderer(with: self)
private var lastTouchAngle: CGFloat = 0
open private (set) var value: Float = 0 // 0 <= value <= 1
private var centerPoint: CGPoint { return CGPoint(x: bounds.midX, y: bounds.midY) }
// MARK: - Functions
override public init(frame: CGRect) {
super.init(frame: frame)
setValue(renderer.minimumValue)
value = minimumValue
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setValue(renderer.minimumValue)
value = minimumValue
}
override open func layoutSubviews() {
super.layoutSubviews()
renderer.update(bounds, value: value)
renderer.updateUI(in: bounds)
}
// MARK: - Touches
@@ -291,6 +294,7 @@ open class CircularSlider: UIControl {
let distanceToOrigin = location - centerPoint
lastTouchAngle = atan2(distanceToOrigin.y, distanceToOrigin.x).toDegree
sendActions(for: .editingDidBegin)
}
override open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
@@ -307,40 +311,32 @@ open class CircularSlider: UIControl {
let angelDeltaAsPercentage = Float(angelDelta / angleRange)
guard abs(angelDeltaAsPercentage) < 1 else { return }
let newValue = isClockwise ? value + angelDeltaAsPercentage : value - angelDeltaAsPercentage
setValue(newValue, isPercentage: true)
let newValue = renderer.value + (isClockwise ? angelDeltaAsPercentage : -angelDeltaAsPercentage)
renderer.value = max(0, min(newValue, 1))
}
open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
sendActions(for: .editingDidEnd)
}
// MARK: - Public
open func setValue(_ newValue: Float, isPercentage: Bool = false) {
guard maximumValue > minimumValue else {
fatalError("`maximumValue` should be greater then `minimumValue`.")
}
if isPercentage {
value = min(1, max(0, newValue))
} else {
// convert to percentage
let rangedValue = min(renderer.maximumValue, max(renderer.minimumValue, newValue))
value = (rangedValue - renderer.minimumValue) / (renderer.maximumValue - renderer.minimumValue)
}
// updates
renderer.updateText(with: value)
renderer.updatePointerView(in: bounds, value: value)
renderer.maxiDotView.updateColors(using: value)
}
open func setTextFont(named: String, textColor: UIColor, multiplier: CGFloat) {
open func setLabelFont(named: String, textColor: UIColor, multiplier: CGFloat) {
textFontSizeMultiplier = multiplier
let textSize = bounds.height * multiplier
renderer.setTextFont(named: named, textColor: textColor, textSize: textSize)
}
open func setTextShadow(color: UIColor, opacity: Float = 1, offset: CGSize = CGSize(width: 1, height: 1), radius: CGFloat = 0) {
open func setLabelShadow(color: UIColor, opacity: Float = 1, offset: CGSize = CGSize(width: 1, height: 1), radius: CGFloat = 0) {
renderer.setTextShadow(color: color, opacity: opacity, offset: offset, radius: radius)
}
open func setLabelText(_ text: String?) {
renderer.textField.text = text
}
}
class Renderer {
@@ -381,9 +377,18 @@ class Renderer {
}
}
fileprivate var minimumValue: Float = 0.0
fileprivate var maximumValue: Float = 100.0
fileprivate var value: Float = 0.0 { // 0 <= renderer.value <= 1
didSet {
// updates
updateText()
if let bounds = circularSlider?.bounds {
updatePointerView(in: bounds)
}
maxiDotView.updateColors(using: value)
circularSlider?.sendActions(for: .valueChanged)
}
}
// MARK: - Init
@@ -392,8 +397,8 @@ class Renderer {
commonInit()
}
func update(_ bounds: CGRect, value: Float) {
updatePointerView(in: bounds, value: value)
func updateUI(in bounds: CGRect) {
updatePointerView(in: bounds)
updateTextField(in: bounds)
updateDotViews(in: bounds)
}
@@ -472,13 +477,14 @@ private extension Renderer {
@objc func keyboardDoneButtonTapped() {
textField.endEditing(true)
guard let circularSlider = circularSlider else { return }
guard let text = textField.text, let newValue = Int(text) else {
updateText(with: circularSlider.value, isPercentage: true)
updateText()
return
}
circularSlider.setValue(Float(newValue))
maxiDotView.layoutSubviews()
circularSlider?.value = CGFloat(newValue)
circularSlider?.sendActions(for: .editingDidEnd)
//maxiDotView.setNeedsLayout()
}
// MARK: - Updates
@@ -508,20 +514,28 @@ private extension Renderer {
textField.isHidden = textFieldIsHidden
}
func updateText(with newValue: Float, isPercentage: Bool = true) {
var value = Int(newValue)
if isPercentage {
// convert value (0...1) to a value within proposed range
value = Int(newValue * (maximumValue - minimumValue) + minimumValue)
func updateText() {
guard let circularSlider = circularSlider else { return }
let minimum = Float(circularSlider.minimumValue)
let maximum = Float(circularSlider.maximumValue)
let _value = Float(circularSlider.value)
var decimalPlaces = circularSlider.labelDecimalPlaces
if value != 0 && _value.rounded(.towardZero) == 0 && decimalPlaces == 0 {
decimalPlaces = 2
}
switch value {
case Int(minimumValue): textField.text = "MIN"
case Int(maximumValue): textField.text = "MAX"
default: textField.text = "\(value)"
guard let newValue = Float(String(format: "%.\(decimalPlaces)f", _value)) else { return }
switch newValue {
case minimum: textField.text = "MIN"
case maximum: textField.text = "MAX"
default: textField.text = String(format: "%.\(decimalPlaces)f", newValue)
}
}
func updatePointerView(in bounds: CGRect, value: Float) {
func updatePointerView(in bounds: CGRect) {
let angleRange = angleDifferenceInDegree(from: startAngle.toDegree, to: endAngle.toDegree, isClockwise: isClockwise)
let angle = isClockwise ? startAngle.toDegree + (angleRange * CGFloat(value)) : startAngle.toDegree - (angleRange * CGFloat(value))
+1
View File
@@ -162,3 +162,4 @@ private extension DotView {
}
}
}
+8
View File
@@ -24,6 +24,13 @@ extension FloatingPoint {
let degree = self * 180 / .pi
return degree >= 0 ? degree : 360 + degree
}
public func roundedDown(toPlaces places: Int) -> Self {
let rounded = self.rounded(.down)
let power = Self(Int(powf(10, Float(places))))
let decimal = ((self - rounded) * power).rounded(.down)
return rounded + (decimal / power)
}
}
extension CGPoint {
@@ -53,3 +60,4 @@ extension CALayer {
return UIBezierPath(ovalIn: bounds.insetBy(dx: bounds.width * multiplier, dy: bounds.height * multiplier))
}
}
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<string>2.0.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2018 Jalal Ouraigua <ouraigua@icloud.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+1 -1
View File
@@ -3,7 +3,7 @@ platform :ios, '12.0'
target 'KnobFramework' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
#use_frameworks!
# use_frameworks!
# Pods for KnobFramework
+126
View File
@@ -0,0 +1,126 @@
![](https://raw.githubusercontent.com/ouraigua/KnobFramework/master/Screenshots/banner.gif)
[![Version](https://img.shields.io/cocoapods/v/KnobFramework.svg?style=flat)](https://cocoapods.org/pods/KnobFramework)
[![Platform](https://img.shields.io/cocoapods/p/KnobFramework.svg?style=flat)](https://cocoapods.org/pods/KnobFramework)
[![Language](https://img.shields.io/badge/language-Swift-orange.svg?style=flat)]()
[![License](https://img.shields.io/cocoapods/l/KnobFramework.svg?style=flat)](https://cocoapods.org/pods/KnobFramework)
[![Twitter: @ouraigua](https://img.shields.io/badge/twitter-@ouraigua-blue.svg?style=flat)](https://twitter.com/ouraigua)
[![Readme Score](http://readme-score-api.herokuapp.com/score.svg?url=https://github.com/ouraigua/knobframework)](http://clayallsopp.github.io/readme-score?url=https://github.com/ouraigua/knobframework)
[![codebeat badge](https://codebeat.co/badges/c4db03f5-903a-4b0e-84bb-98362fc5bd7a)](https://codebeat.co/projects/github-com-ouraigua-knobframework-master)
# KnobFramework
KnobFramework is a highly customisable and reusable iOS circular slider that mimics the behaviour of a knob control.
It uses no preset images and every one of its components is drawn completely in code making it extremely adaptable to every design and theme.
## Example
To run the example project, clone the repo, and run `pod install` from the Example directory first.
![](https://raw.githubusercontent.com/ouraigua/KnobFramework/master/Screenshots/shot1.gif)
![](https://raw.githubusercontent.com/ouraigua/KnobFramework/master/Screenshots/shot2.gif)
![](https://raw.githubusercontent.com/ouraigua/KnobFramework/master/Screenshots/shot3.gif)
## Requirements
- iOS 10.0+
- Xcode 10.0
## Installation
KnobFramework is available through [CocoaPods](https://cocoapods.org). To install
it, simply add the following line to your Podfile:
```ruby
pod 'KnobFramework'
```
## Usage
1. Visually:
Drag a UIView to your storyboard, change its class of to CircularSlider and start visually customising the design to your liking.
All the parameters are IBInspectable, so you can configure the slider straight from the attribute inspector tab, without having to write a single line of code.
![](https://raw.githubusercontent.com/ouraigua/KnobFramework/master/Screenshots/shot4.gif)
2. Programatically:
```swift
import KnobFramework
let circularSlider = CircularSlider(frame: aFrame)
circularSlider.startAngle = 230
circularSlider.endAngle = 310
circularSlider.minimumValue = 0
circularSlider.maximumValue = 60
circularSlider.isClockwise = false
```
These are just few of the many params you can configure for the slider.
The slider's min/max values are IBInspectable and default to 0.0 and 100.0 respectively.
They also support negative and positive values or a mixture of them.
```swift
@IBInspectable open var minimumValue: CGFloat = 0
@IBInspectable open var maximumValue: CGFloat = 100
```
The slider's `value` property is both { get set } and it is designed to work similarly to the familiar value property of UIKit's generic UISlider.
```swift
/**
This value will be pinned to minimumValue/maximumValue
The default value of this property is 0.0.
*/
@IBInspectable open var value: CGFloat // { get set }
```
In order to get value change notifications, use the `Target-Action` pattern which is an inherent part of UIControl, like so:
``` swift
/**
Add target/action for particular event.
- parameter target: The object whose action method is called
- parameter action: A selector identifying the action method to be called
- parameter Event: The control-specific events for which the action method is called
So far I've only implemented the following:
UIControl.Event.valueChanged
UIControl.Event.editingDidBegin
UIControl.Event.editingDidEnd
*/
circularSlider.addTarget(target: Any?, action: Selector, for: UIControl.Event)
```
To Control the appearance of the label displaying the value, use these:
```swift
open func setLabelFont(named: String, textColor: UIColor, multiplier: CGFloat)
open func setLabelShadow(color: UIColor, opacity: Float = 1, offset: CGSize = CGSize(width: 1, height: 1), radius: CGFloat = 0)
```
You can also specify the number of decimal places this label should show.
The default is 0.0 unless for example the value is within [0.0, 1.0]
```swift
@IBInspectable open var labelDecimalPlaces: Int = 0
```
If you need to override the text in the label, then just implement the following function inside the selector of the `Target-Action`
method for the event `UIControl.Event.valueChanged`
```swift
open func setLabelText(_ text: String?)
```
## References
The project is Inspired by:
- [How To Make a Custom Control Tutorial: A Reusable Knob](https://www.raywenderlich.com/5294-how-to-make-a-custom-control-tutorial-a-reusable-knob)
- [HGCircularSlider](https://github.com/HamzaGhazouani/HGCircularSlider)
## Author
Jalal Ouraigua, ouraigua@icloud.com
## License
KnobFramework is available under the MIT license. See the LICENSE file for more info.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 598 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB