├── .swift-version ├── demo.gif ├── DoubleSlider ├── .swiftlint.yml ├── DoubleSlider.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── DoubleSlider.xcscheme │ └── project.pbxproj ├── DoubleSlider │ ├── CALayers │ │ ├── DoubleSliderLayerProtocol.swift │ │ ├── DoubleSliderTrackLayer.swift │ │ └── DoubleSliderThumbLayer.swift │ ├── DoubleSlider │ │ ├── DoubleSliderColors.swift │ │ ├── DoubleSlider.h │ │ ├── DoubleSliderDelegates.swift │ │ ├── DoubleSliderTouchTracking.swift │ │ ├── DoubleSliderUpdateFunctions.swift │ │ └── DoubleSliderMain.swift │ ├── Info.plist │ └── Extensions │ │ └── Extensions.swift └── DoubleSliderTests │ ├── Info.plist │ └── DoubleSliderTests.swift ├── DoubleSliderDemo ├── .swiftlint.yml ├── DoubleSliderDemo.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── project.pbxproj └── DoubleSliderDemo │ ├── Extensions.swift │ ├── Info.plist │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── ViewController.swift │ └── Main.storyboard ├── Makefile ├── .github └── workflows │ ├── ci.yml │ └── build.yml ├── DoubleSlider.xcworkspace ├── xcshareddata │ └── IDEWorkspaceChecks.plist └── contents.xcworkspacedata ├── Package.swift ├── DoubleSlider.podspec ├── LICENSE └── README.md /.swift-version: -------------------------------------------------------------------------------- 1 | 5.5.2 2 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhkaplan/DoubleSlider/HEAD/demo.gif -------------------------------------------------------------------------------- /DoubleSlider/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - leading_whitespace 3 | - trailing_whitespace 4 | -------------------------------------------------------------------------------- /DoubleSliderDemo/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - leading_whitespace 3 | - trailing_whitespace 4 | - line_length 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | swift build -Xswiftc "-sdk" -Xswiftc "`xcrun --sdk iphonesimulator --show-sdk-path`" -Xswiftc "-target" -Xswiftc "x86_64-apple-ios13.0-simulator" -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: macos-latest 8 | steps: 9 | - uses: action/checkout@v2 10 | - run: swift --version; make build 11 | -------------------------------------------------------------------------------- /DoubleSlider/DoubleSlider.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DoubleSliderDemo/DoubleSliderDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DoubleSlider.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DoubleSlider/DoubleSlider/CALayers/DoubleSliderLayerProtocol.swift: -------------------------------------------------------------------------------- 1 | 2 | // This is the protocol that all CALayer components 3 | // used by DoubleSlider conform to. It ensures they 4 | // contain a weak delegate to DoubleSlider 5 | protocol DoubleSliderLayer: AnyObject { 6 | var doubleSlider: DoubleSlider? { get set } 7 | } 8 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "DoubleSlider", 7 | platforms: [.iOS(.v11)], 8 | products: [.library(name: "DoubleSlider", targets: ["DoubleSlider"])], 9 | targets: [.target(name: "DoubleSlider", path: "DoubleSlider/DoubleSlider")] 10 | ) 11 | -------------------------------------------------------------------------------- /DoubleSlider.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: macos-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Version 17 | run: swift --version 18 | - name: Build 19 | run: make build 20 | -------------------------------------------------------------------------------- /DoubleSliderDemo/DoubleSliderDemo/Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions.swift 3 | // DoubleSlider 4 | // 5 | // Created by josh on 2018/03/30. 6 | // Copyright © 2018年 yhkaplan. All rights reserved. 7 | // 8 | 9 | extension Array { 10 | func item(at index: Int) -> Element? { 11 | return (index < self.count && index >= 0) ? self[index] : nil 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /DoubleSlider/DoubleSlider/DoubleSlider/DoubleSliderColors.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | // The Color enum is populated with the default color values that Apple uses in 4 | // standard UIKit controls 5 | enum Colors { 6 | static let defaultBlue = UIColor(red: 0.0, green: 0.51, blue: 0.98, alpha: 1.0) 7 | static let defaultGray = UIColor(red: 0.71, green: 0.71, blue: 0.71, alpha: 1.0) 8 | static let textGray = UIColor(red: 90.0 / 255.0, green: 90.0 / 255.0, blue: 95.0 / 255.0, alpha: 1.0) 9 | static let defaultWhite = UIColor.white 10 | } 11 | -------------------------------------------------------------------------------- /DoubleSlider/DoubleSlider/DoubleSlider/DoubleSlider.h: -------------------------------------------------------------------------------- 1 | // 2 | // DoubleSlider.h 3 | // DoubleSlider 4 | // 5 | // Created by josh on 2018/04/02. 6 | // Copyright © 2018年 yhkaplan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for DoubleSlider. 12 | FOUNDATION_EXPORT double DoubleSliderVersionNumber; 13 | 14 | //! Project version string for DoubleSlider. 15 | FOUNDATION_EXPORT const unsigned char DoubleSliderVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /DoubleSlider/DoubleSliderTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /DoubleSlider/DoubleSlider/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.02 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /DoubleSlider/DoubleSlider/Extensions/Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions.swift 3 | // DoubleSlider 4 | // 5 | // Created by josh on 2018/04/02. 6 | // Copyright © 2018年 yhkaplan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Double { 12 | 13 | // Rounds double to two decimal places 14 | var roundedToTwoPlaces: Double { 15 | let divisor = pow(10.0, 2.0) 16 | return (self * divisor).rounded() / divisor 17 | } 18 | 19 | // This is used to provide a formatted version of the label 20 | // string for the current upper/lower value 21 | var asRoundedAttributedString: NSAttributedString { 22 | return "\(self.roundedToTwoPlaces)".asAttributedString 23 | } 24 | 25 | } 26 | 27 | extension String { 28 | 29 | var asAttributedString: NSAttributedString { 30 | return NSAttributedString(string: self, attributes: DoubleSlider.labelAttributes) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /DoubleSlider.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "DoubleSlider" 3 | s.version = "1.0.0" 4 | s.summary = "DoubleSlider is a version of UISlider that has two draggable points —useful for choosing two points in a range." 5 | #s.description = "DoubleSlider is a version of UISlider that has two draggable points —useful for choosing two points in a range." 6 | s.homepage = "https://github.com/yhkaplan/doubleslider" 7 | # s.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif" 8 | s.license = { :type => "MIT", :file => "LICENSE" } 9 | s.author = { "yhkaplan" => "yhkaplan@gmail.com" } 10 | s.platform = :ios, "11.0" 11 | s.source = { :git => "https://github.com/yhkaplan/DoubleSlider.git", :tag => "#{s.version}" } 12 | s.source_files = "DoubleSlider/DoubleSlider/**/*.{h,swift}" 13 | s.exclude_files = "Classes/Exclude" 14 | # s.public_header_files = "Classes/**/*.h" 15 | s.framework = "UIKit", "CoreGraphics" 16 | end 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Joshua Kaplan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /DoubleSlider/DoubleSlider/DoubleSlider/DoubleSliderDelegates.swift: -------------------------------------------------------------------------------- 1 | 2 | // This protocol allows you to display a custom label 3 | // for each step index selected. Labels must be set to on 4 | // and the number of steps must be set. 5 | public protocol DoubleSliderLabelDelegate: AnyObject { 6 | func labelForStep(at index: Int) -> String? 7 | } 8 | 9 | // I choose not to combine the two delegates below in 10 | // order to make it clearer which protocol is being conformed to 11 | // and for what reason, as well as avoid an unneeded @objc annotation 12 | 13 | // This protocol allows you to use a Swiftier delegate pattern 14 | // rather than rely on notifications for updating values 15 | // Equivalent to `valueChanged` notification 16 | public protocol DoubleSliderValueChangedDelegate: AnyObject { 17 | func valueChanged(for doubleSlider: DoubleSlider) 18 | } 19 | 20 | // This protocol allows you to use a Swiftier delegate pattern 21 | // rather than rely on notifications for updating values 22 | // Equivalent to `editingDidEnd` notification 23 | public protocol DoubleSliderEditingDidEndDelegate: AnyObject { 24 | func editingDidEnd(for doubleSlider: DoubleSlider) 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 2 | [![Swift 5.5](https://img.shields.io/badge/Swift-5.5-orange.svg?style=flat)](swift.org) 3 | [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/DoubleSlider.svg)](https://img.shields.io/cocoapods/v/DoubleSlider.svg) 4 | [![Platform](https://img.shields.io/cocoapods/p/DoubleSlider.svg?style=flat)](http://cocoapods.org/pods/DoubleSlider) 5 | # DoubleSlider 6 | DoubleSlider is a version of UISlider that has two draggable points —useful for choosing two points in a range. 7 | 8 | ## Requirements 9 | - iOS 12.0+ 10 | - Xcode 12.0+ 11 | 12 | ## Screenshots 13 | ![demo](demo.gif) 14 | 15 | ## Installation 16 | 17 | #### Swift Package Manager 18 | 19 | Add via Xcode in the [usual way](https://developer.apple.com/documentation/xcode/adding_package_dependencies_to_your_app) 20 | 21 | #### CocoaPods 22 | You can use [CocoaPods](http://cocoapods.org/) to install `DoubleSlider` by adding it to your `Podfile`: 23 | 24 | ```ruby 25 | platform :ios, '11.0' 26 | use_frameworks! 27 | pod 'DoubleSlider' 28 | ``` 29 | ``` swift 30 | import DoubleSlider 31 | ``` 32 | #### Carthage 33 | Create a `Cartfile` that lists the framework and run `carthage update`. Follow the [instructions](https://github.com/Carthage/Carthage#if-youre-building-for-ios) to add `$(SRCROOT)/Carthage/Build/iOS/DoubleSlider.framework` to an iOS project. 34 | 35 | ``` 36 | github "yhkaplan/DoubleSlider" 37 | ``` 38 | -------------------------------------------------------------------------------- /DoubleSliderDemo/DoubleSliderDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /DoubleSlider/DoubleSlider/CALayers/DoubleSliderTrackLayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DoubleSliderTrackLayer.swift 3 | // DoubleSlider 4 | // 5 | // Created by josh on 2018/03/30. 6 | // Copyright © 2018年 yhkaplan. All rights reserved. 7 | // 8 | 9 | import QuartzCore 10 | import UIKit 11 | 12 | // DoubleSliderTrackLayer represents the colorized space between 13 | // the two thumb layers 14 | // I decided not to make this class open because it is very small, 15 | // meaning that developers could make their own custom CALayer 16 | // subclass in the same amount of time as it would take to subclass 17 | // this one 18 | public class DoubleSliderTrackLayer: CALayer, DoubleSliderLayer { 19 | weak var doubleSlider: DoubleSlider? 20 | 21 | override public func draw(in ctx: CGContext) { 22 | guard let slider = doubleSlider else { return } 23 | 24 | // Clip 25 | let cornerRadius = bounds.height * slider.roundedness / 2.0 26 | let path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius) 27 | ctx.addPath(path.cgPath) 28 | ctx.fillPath() 29 | 30 | // Fill track 31 | ctx.setFillColor(slider.trackTintColor.cgColor) 32 | ctx.addPath(path.cgPath) 33 | ctx.fillPath() 34 | 35 | // Fill highlighted range 36 | ctx.setFillColor(slider.trackHighlightTintColor.cgColor) 37 | let lowerValuePosition = slider.positionForValue(value: slider.lowerValue) 38 | let upperValuePosition = slider.positionForValue(value: slider.upperValue) 39 | let rect = CGRect(x: lowerValuePosition, 40 | y: 0.0, 41 | width: upperValuePosition - lowerValuePosition, 42 | height: bounds.height) 43 | ctx.fill(rect) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /DoubleSliderDemo/DoubleSliderDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /DoubleSliderDemo/DoubleSliderDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /DoubleSlider/DoubleSlider/CALayers/DoubleSliderThumbLayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DoubleSliderThumbLayer.swift 3 | // DoubleSlider 4 | // 5 | // Created by josh on 2018/03/30. 6 | // Copyright © 2018年 yhkaplan. All rights reserved. 7 | // 8 | 9 | import QuartzCore 10 | import UIKit 11 | import Foundation 12 | 13 | // DoubleSliderThumbLayer represents the each of the draggable 14 | // thumb layers that indicate the minimum and maximum values 15 | // in the range 16 | // I decided not to make this class open because it is very small, 17 | // meaning that developers could make their own custom CALayer 18 | // subclass in the same amount of time as it would take to subclass 19 | // this one 20 | public class DoubleSliderThumbLayer: CALayer, DoubleSliderLayer { 21 | weak var doubleSlider: DoubleSlider? 22 | 23 | var isHighlighted: Bool = false { 24 | didSet { 25 | setNeedsDisplay() 26 | } 27 | } 28 | 29 | override public func draw(in ctx: CGContext) { 30 | guard let slider = doubleSlider else { return } 31 | 32 | let inset = slider.layerInset 33 | let thumbFrame = bounds.insetBy(dx: inset, dy: inset) 34 | let cornerRadius = thumbFrame.height * slider.roundedness / 2.0 35 | let thumbPath = UIBezierPath(roundedRect: thumbFrame, cornerRadius: cornerRadius) 36 | 37 | // Fill with shadow 38 | let shadowColor = Colors.defaultGray.cgColor 39 | ctx.setShadow(offset: CGSize(width: 0.0, height: 2.0), blur: 4.0, color: shadowColor) 40 | ctx.setFillColor(slider.thumbTintColor.cgColor) 41 | ctx.addPath(thumbPath.cgPath) 42 | ctx.fillPath() 43 | 44 | // Outline 45 | ctx.setStrokeColor(shadowColor) 46 | ctx.setLineWidth(0.25) 47 | ctx.addPath(thumbPath.cgPath) 48 | ctx.strokePath() 49 | 50 | // Set color for highlighted thumb picker (move somewhere else)?? 51 | if isHighlighted { 52 | ctx.setFillColor(UIColor(white: 0.0, alpha: 0.1).cgColor) 53 | ctx.addPath(thumbPath.cgPath) 54 | ctx.fillPath() 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /DoubleSliderDemo/DoubleSliderDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // DoubleSliderDemo 4 | // 5 | // Created by josh on 2018/04/02. 6 | // Copyright © 2018年 yhkaplan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | func applicationWillResignActive(_ application: UIApplication) { 22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 23 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 24 | } 25 | 26 | func applicationDidEnterBackground(_ application: UIApplication) { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | func applicationWillEnterForeground(_ application: UIApplication) { 32 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 33 | } 34 | 35 | func applicationDidBecomeActive(_ application: UIApplication) { 36 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 37 | } 38 | 39 | func applicationWillTerminate(_ application: UIApplication) { 40 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /DoubleSlider/DoubleSliderTests/DoubleSliderTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DoubleSliderTests.swift 3 | // DoubleSliderTests 4 | // 5 | // Created by josh on 2018/04/16. 6 | // Copyright © 2018年 yhkaplan. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class DoubleSliderTests: XCTestCase { 12 | 13 | var labels: [String] = [] 14 | var doubleSlider: DoubleSlider! 15 | 16 | override func setUp() { 17 | super.setUp() 18 | // Put setup code here. This method is called before the invocation of each test method in the class. 19 | } 20 | 21 | override func tearDown() { 22 | doubleSlider.removeTarget(self, action: #selector(printVal(_:)), for: .valueChanged) 23 | super.tearDown() 24 | } 25 | 26 | func testExample() { 27 | // This is an example of a functional test case. 28 | // Use XCTAssert and related functions to verify your tests produce the correct results. 29 | } 30 | 31 | @objc func printVal(_ control: DoubleSlider) { 32 | print("Lower: \(control.lowerValue) Upper: \(control.upperValue)") 33 | } 34 | 35 | @objc func finishChanging(_ control: DoubleSlider) { 36 | print("Final Lower: \(control.stepIndexForLowerValue!) Final Upper: \(control.stepIndexForUpperValue!)") 37 | } 38 | 39 | private func makeLabels() { 40 | for num in stride(from: 0, to: 300, by: 10) { 41 | labels.append("$\(i)") 42 | } 43 | labels.append("No limit") 44 | } 45 | 46 | private func setupDoubleSlider() { 47 | let height: CGFloat = 38.0 //TODO: make this the default height 48 | let width = view.bounds.width - 40.0 49 | let frame = CGRect(x: backgroundView.bounds.minX, 50 | y: backgroundView.bounds.midY - (height / 2.0), 51 | width: width, 52 | height: height) 53 | 54 | doubleSlider = DoubleSlider(frame: frame) 55 | doubleSlider.translatesAutoresizingMaskIntoConstraints = false 56 | 57 | doubleSlider.labelDelegate = self 58 | doubleSlider.numberOfSteps = labels.count 59 | doubleSlider.smoothStepping = true 60 | doubleSlider.lowerLabelMargin = -20 61 | doubleSlider.upperLabelMargin = view.bounds.maxX 62 | 63 | doubleSlider.addTarget(self, action: #selector(printVal(_:)), for: .valueChanged) 64 | doubleSlider.addTarget(self, action: #selector(finishChanging(_:)), for: .editingDidEnd) 65 | 66 | backgroundView.addSubview(doubleSlider) 67 | } 68 | } 69 | 70 | class ViewController: DoubleSliderLabelDelegate { 71 | func labelForStep(at index: Int) -> String? { 72 | return labels.item(at: index) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /DoubleSlider/DoubleSlider.xcodeproj/xcshareddata/xcschemes/DoubleSlider.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /DoubleSliderDemo/DoubleSliderDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // DoubleSliderDemo 4 | // 5 | // Created by josh on 2018/03/30. 6 | // Copyright © 2018年 yhkaplan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import DoubleSlider 11 | 12 | class ViewController: UIViewController { 13 | 14 | @IBOutlet weak var backgroundView: UIView! 15 | @IBOutlet weak var normalSlider: UISlider! 16 | @IBOutlet weak var redDoubleSlider: DoubleSlider! 17 | 18 | var labels: [String] = [] 19 | var doubleSlider: DoubleSlider! 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | makeLabels() 25 | setupDoubleSlider() 26 | 27 | redDoubleSlider.labelDelegate = self 28 | redDoubleSlider.numberOfSteps = labels.count 29 | redDoubleSlider.labelsAreHidden = false 30 | redDoubleSlider.smoothStepping = true 31 | } 32 | 33 | private func makeLabels() { 34 | for num in stride(from: 0, to: 200, by: 10) { 35 | labels.append("$\(num)") 36 | } 37 | labels.append("No limit") 38 | } 39 | 40 | private func setupDoubleSlider() { 41 | let height: CGFloat = 38.0 //TODO: make this the default height 42 | let width = view.bounds.width - 38.0 43 | 44 | let frame = CGRect( 45 | x: backgroundView.bounds.minX - 2.0, 46 | y: backgroundView.bounds.midY - (height / 2.0), 47 | width: width, 48 | height: height 49 | ) 50 | 51 | doubleSlider = DoubleSlider(frame: frame) 52 | doubleSlider.translatesAutoresizingMaskIntoConstraints = false 53 | 54 | doubleSlider.labelDelegate = self 55 | doubleSlider.numberOfSteps = labels.count 56 | doubleSlider.smoothStepping = true 57 | let labelOffset: CGFloat = 8.0 58 | doubleSlider.lowerLabelMarginOffset = labelOffset 59 | doubleSlider.upperLabelMarginOffset = labelOffset 60 | 61 | doubleSlider.lowerValueStepIndex = 0 62 | doubleSlider.upperValueStepIndex = labels.count - 1 63 | 64 | // You can use traditional notifications 65 | doubleSlider.addTarget(self, action: #selector(printVal(_:)), for: .valueChanged) 66 | // Or Swifty delegates 67 | doubleSlider.editingDidEndDelegate = self 68 | 69 | backgroundView.addSubview(doubleSlider) 70 | } 71 | 72 | override func viewWillDisappear(_ animated: Bool) { 73 | super.viewWillDisappear(animated) 74 | 75 | doubleSlider.removeTarget(self, action: #selector(printVal(_:)), for: .valueChanged) 76 | } 77 | 78 | @objc func printVal(_ doubleSlider: DoubleSlider) { 79 | print("Lower: \(doubleSlider.lowerValue) Upper: \(doubleSlider.upperValue)") 80 | } 81 | } 82 | 83 | extension ViewController: DoubleSliderEditingDidEndDelegate { 84 | func editingDidEnd(for doubleSlider: DoubleSlider) { 85 | print("Lower Step Index: \(doubleSlider.lowerValueStepIndex) Upper Step Index: \(doubleSlider.upperValueStepIndex)") 86 | } 87 | } 88 | 89 | extension ViewController: DoubleSliderLabelDelegate { 90 | func labelForStep(at index: Int) -> String? { 91 | return labels.item(at: index) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /DoubleSlider/DoubleSlider/DoubleSlider/DoubleSliderTouchTracking.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DoubleSliderTouchTracking.swift 3 | // DoubleSlider 4 | // 5 | // Created by josh on 2018/03/30. 6 | // Copyright © 2018年 yhkaplan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension DoubleSlider { 12 | 13 | // MARK: - Private var 14 | 15 | private var minimumSpaceBetweenThumbOrigins: Double { 16 | return stepDistance ?? 0.05 17 | } 18 | 19 | // MARK: - Touch tracking 20 | 21 | override open func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { 22 | previousLocation = touch.location(in: self) 23 | 24 | if lowerThumbLayer.frame.contains(previousLocation) { 25 | lowerThumbLayer.isHighlighted = true 26 | 27 | } else if upperThumbLayer.frame.contains(previousLocation) { 28 | upperThumbLayer.isHighlighted = true 29 | } 30 | 31 | return lowerThumbLayer.isHighlighted || upperThumbLayer.isHighlighted 32 | } 33 | 34 | override open func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { 35 | let location = touch.location(in: self) 36 | 37 | // Check drag distance 38 | let dragDistance = Double(location.x - previousLocation.x) 39 | let dragValue = (maxValue - minValue) * dragDistance / Double(bounds.width - thumbWidth) 40 | 41 | previousLocation = location 42 | 43 | // Update values 44 | if lowerThumbLayer.isHighlighted { 45 | lowerValue = lowerValueAdjusted(for: dragValue) 46 | 47 | } else if upperThumbLayer.isHighlighted { 48 | upperValue = upperValueAdjusted(for: dragValue) 49 | } 50 | 51 | // Declare that a value was updated, called continuously 52 | sendActions(for: .valueChanged) 53 | valueChangedDelegate?.valueChanged(for: self) 54 | 55 | return true 56 | } 57 | 58 | override open func endTracking(_ touch: UITouch?, with event: UIEvent?) { 59 | lowerThumbLayer.isHighlighted = false 60 | upperThumbLayer.isHighlighted = false 61 | 62 | // Declare that the change finished, called at the end 63 | // of a drag session 64 | sendActions(for: .editingDidEnd) 65 | editingDidEndDelegate?.editingDidEnd(for: self) 66 | } 67 | 68 | // MARK: - Private funcs 69 | 70 | private func lowerValueAdjusted(for dragValue: Double) -> Double { 71 | var lowerTempValue = lowerValue + dragValue 72 | lowerTempValue = boundValue(value: lowerTempValue, lowerValue: minValue, upperValue: upperValue) 73 | 74 | // For step-like behavior 75 | if !smoothStepping, let steppedValue = steppedValue(for: lowerTempValue) { 76 | lowerTempValue = steppedValue 77 | } 78 | 79 | let highestPositionAllowed = upperValue - minimumSpaceBetweenThumbOrigins 80 | 81 | // To prevent excessive overlap 82 | if lowerTempValue > highestPositionAllowed { 83 | lowerTempValue = highestPositionAllowed 84 | } 85 | 86 | // To prevent a rare case in which 2 thumbs are barely near 87 | // enough to register as the same stepIndex/label 88 | if let lowerIndex = stepIndex(for: lowerTempValue), 89 | let upperIndex = stepIndex(for: upperValue), 90 | lowerIndex >= upperIndex { 91 | 92 | lowerTempValue = lowerValue 93 | } 94 | 95 | return lowerTempValue 96 | } 97 | 98 | private func upperValueAdjusted(for dragValue: Double) -> Double { 99 | var upperTempValue = upperValue + dragValue 100 | upperTempValue = boundValue(value: upperTempValue, lowerValue: lowerValue, upperValue: maxValue) 101 | 102 | // For step-like behavior 103 | if !smoothStepping, let steppedValue = steppedValue(for: upperTempValue) { 104 | upperTempValue = steppedValue 105 | } 106 | 107 | let lowestPositionAllowed = lowerValue + minimumSpaceBetweenThumbOrigins 108 | 109 | // To prevent excessive overlap 110 | if upperTempValue < lowestPositionAllowed { 111 | upperTempValue = lowestPositionAllowed 112 | } 113 | 114 | // To prevent a rare case in which 2 thumbs are barely near 115 | // enough to register as the same stepIndex/label 116 | if let upperIndex = stepIndex(for: upperTempValue), 117 | let lowerIndex = stepIndex(for: lowerValue), 118 | upperIndex <= lowerIndex { 119 | 120 | upperTempValue = upperValue 121 | } 122 | 123 | return upperTempValue 124 | } 125 | 126 | private func steppedValue(for value: Double) -> Double? { 127 | guard let stepDistance = stepDistance else { return nil } 128 | 129 | return round(value / stepDistance) * stepDistance 130 | } 131 | 132 | private func boundValue(value: Double, lowerValue: Double, upperValue: Double) -> Double { 133 | return min(max(value, lowerValue), upperValue) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /DoubleSlider/DoubleSlider/DoubleSlider/DoubleSliderUpdateFunctions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DoubleSliderUpdateFunctions.swift 3 | // DoubleSlider 4 | // 5 | // Created by josh on 2018/03/30. 6 | // Copyright © 2018年 yhkaplan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // MARK: General update functions 12 | 13 | extension DoubleSlider { 14 | 15 | open func updateLayerFrames() { 16 | CATransaction.begin() 17 | // Prevents interaction while updating 18 | CATransaction.setDisableActions(true) 19 | 20 | // Get lower values 21 | let lowerThumbCenter = positionForValue(value: lowerValue) 22 | let lowerThumbMinX = lowerThumbCenter - thumbWidth / 2.0 23 | 24 | // Get upper values 25 | let upperThumbCenter = positionForValue(value: upperValue) 26 | let upperThumbMinX = upperThumbCenter - thumbWidth / 2.0 27 | 28 | lowerThumbLayer.frame = CGRect(x: lowerThumbMinX, y: 0.0, width: thumbWidth, height: thumbWidth) 29 | lowerThumbLayer.setNeedsDisplay() 30 | 31 | upperThumbLayer.frame = CGRect(x: upperThumbMinX, y: 0.0, width: thumbWidth, height: thumbWidth) 32 | upperThumbLayer.setNeedsDisplay() 33 | 34 | trackLayer.frame = bounds.insetBy(dx: layerInset, dy: 18.0) 35 | trackLayer.setNeedsDisplay() 36 | 37 | updateLabelValues() 38 | updateLabelSizes() 39 | updateLabelPositions() 40 | 41 | minLabel.alignmentMode = .center 42 | maxLabel.alignmentMode = .center 43 | 44 | CATransaction.commit() 45 | } 46 | 47 | func positionForValue(value: Double) -> CGFloat { 48 | let widthDifference = bounds.width - thumbWidth 49 | return widthDifference * CGFloat(value - minValue) / CGFloat(maxValue - minValue) + (thumbWidth / 2.0) 50 | } 51 | } 52 | 53 | // MARK: Label related funcs 54 | 55 | extension DoubleSlider { 56 | 57 | // If a custom label is set for the lower or upper value, 58 | // then that label is used instead of the raw value 59 | private func updateLabelValues() { 60 | if let stepIndex = stepIndex(for: lowerValue), 61 | let label = labelDelegate?.labelForStep(at: stepIndex) { 62 | minLabel.string = label.asAttributedString 63 | 64 | } else { 65 | minLabel.string = lowerValue.asRoundedAttributedString 66 | } 67 | 68 | if let stepIndex = stepIndex(for: upperValue), 69 | let label = labelDelegate?.labelForStep(at: stepIndex) { 70 | maxLabel.string = label.asAttributedString 71 | 72 | } else { 73 | maxLabel.string = upperValue.asRoundedAttributedString 74 | } 75 | setNeedsLayout() 76 | } 77 | 78 | private func labelCenter(for label: DoubleSliderThumbLayer) -> CGPoint { 79 | let labelY = trackLayer.frame.midY - (minLabel.frame.height / 2.0) - spaceBetweenThumbAndLabel 80 | return CGPoint(x: label.frame.midX, y: labelY) 81 | } 82 | 83 | private func updateLabelSizes() { 84 | [minLabel, maxLabel].forEach { label in 85 | let width = (label.string as? NSAttributedString)?.size().width ?? 55.0 86 | label.frame.size = CGSize(width: width, height: 20.0) 87 | } 88 | } 89 | 90 | private func updateLabelPositions() { 91 | let newMinLabelCenter = labelCenter(for: lowerThumbLayer) 92 | let newMaxLabelCenter = labelCenter(for: upperThumbLayer) 93 | 94 | let newRightmostXInMinLabel = newMinLabelCenter.x + minLabel.frame.width / 2.0 95 | let newLeftmostXInMaxLabel = newMaxLabelCenter.x - maxLabel.frame.width / 2.0 96 | let newSpaceBetweenLabels = newLeftmostXInMaxLabel - newRightmostXInMinLabel 97 | 98 | if newSpaceBetweenLabels > minimumSpaceBetweenLabels { // No position conflict between labels 99 | minLabel.position = newMinLabelCenter 100 | maxLabel.position = newMaxLabelCenter 101 | 102 | // Position conflict between lowerLabel and lowerLabelMargin 103 | if minLabel.frame.minX < lowerLabelMargin { 104 | minLabel.frame.origin.x = minLabel.frame.minX + 4.0 105 | } 106 | 107 | // Position conflict between upperLabel and upperLabelMargin 108 | if maxLabel.frame.maxX > upperLabelMargin { 109 | maxLabel.frame.origin.x = frame.width - maxLabel.frame.width - 4.0 110 | } 111 | 112 | } else { // Positions conflict between labels 113 | let increaseAmount: CGFloat = minimumSpaceBetweenLabels - newSpaceBetweenLabels 114 | minLabel.position = CGPoint(x: newMinLabelCenter.x - increaseAmount / 2.0, y: newMinLabelCenter.y) 115 | maxLabel.position = CGPoint(x: newMaxLabelCenter.x + increaseAmount / 2.0, y: newMaxLabelCenter.y) 116 | 117 | // Adjust labels positions when they collide at the left lowerLabelMargin 118 | if minLabel.frame.minX < lowerLabelMargin { 119 | minLabel.frame.origin.x = lowerLabelMargin 120 | maxLabel.frame.origin.x = minimumSpaceBetweenLabels + minLabel.frame.width 121 | } 122 | 123 | // Adjust labels positions when they collide at the right upperLabelMargin 124 | if maxLabel.frame.maxX > upperLabelMargin { 125 | maxLabel.frame.origin.x = upperLabelMargin - maxLabel.frame.width 126 | minLabel.frame.origin.x = maxLabel.frame.origin.x - minimumSpaceBetweenLabels - minLabel.frame.width 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /DoubleSlider/DoubleSlider/DoubleSlider/DoubleSliderMain.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DoubleSlider.swift 3 | // DoubleSlider 4 | // 5 | // Created by josh on 2018/03/30. 6 | // Copyright © 2018年 yhkaplan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // The DoubleSlider class is the main class that implements the DoubleSlider 12 | // control. Its main methods are marked open, so they can be subclassed and 13 | // overridden to add or change behaviors 14 | @IBDesignable open class DoubleSlider: UIControl { 15 | 16 | // Public vars 17 | 18 | // Track values 19 | public private(set) var minValue: Double = 0.0 20 | 21 | public private(set) var maxValue: Double = 1.0 22 | 23 | @IBInspectable public var lowerValue: Double = 0.2 { 24 | didSet { 25 | updateLayerFrames() 26 | } 27 | } 28 | 29 | @IBInspectable public var upperValue: Double = 0.8 { 30 | didSet { 31 | updateLayerFrames() 32 | } 33 | } 34 | 35 | @IBInspectable public var numberOfSteps: Int = 0 { 36 | didSet { 37 | updateLayerFrames() 38 | } 39 | } 40 | 41 | // lowerValue as a step index (int) 42 | public var lowerValueStepIndex: Int { 43 | // Return 0 if steps don't exist 44 | get { 45 | return stepIndex(for: lowerValue) ?? 0 46 | } 47 | // Don't change lowerValue if steps aren't set 48 | set(newIndex) { 49 | lowerValue = value(for: newIndex) ?? lowerValue 50 | updateLayerFrames() 51 | } 52 | } 53 | 54 | // upperValue as a step index (int) 55 | public var upperValueStepIndex: Int { 56 | // Return 0 if steps don't exist 57 | get { 58 | return stepIndex(for: upperValue) ?? 0 59 | } 60 | // Don't change upperValue if steps aren't set 61 | set(newIndex) { 62 | upperValue = value(for: newIndex) ?? upperValue 63 | updateLayerFrames() 64 | } 65 | } 66 | 67 | // This bool turns off traditional stepping behavior, 68 | // allowing for custom labels set at given intervals 69 | // that don't "jump" from step to step, but instead 70 | // transition smoothly 71 | @IBInspectable public var smoothStepping: Bool = false { 72 | didSet { 73 | updateLayerFrames() 74 | } 75 | } 76 | 77 | // This inset is shared between the thumb layers and 78 | // the track layer so the track layer doesn't extend 79 | // beyond the thumbs. Also, this value makes sure 80 | // that the shadows found on the thumb layer aren't 81 | // cut off. 82 | @IBInspectable public var layerInset: CGFloat = 5.0 { 83 | didSet { 84 | updateLayerFrames() 85 | } 86 | } 87 | 88 | // Override this to show only one label, etc. 89 | @IBInspectable open var labelsAreHidden: Bool = false { 90 | didSet { 91 | minLabel.isHidden = labelsAreHidden 92 | maxLabel.isHidden = labelsAreHidden 93 | } 94 | } 95 | 96 | // MARK: - Colors 97 | 98 | @IBInspectable public var trackTintColor: UIColor = Colors.defaultGray { 99 | didSet { 100 | trackLayer.setNeedsDisplay() 101 | } 102 | } 103 | 104 | @IBInspectable public var trackHighlightTintColor: UIColor = Colors.defaultBlue { 105 | didSet { 106 | trackLayer.setNeedsDisplay() 107 | } 108 | } 109 | 110 | @IBInspectable public var thumbTintColor: UIColor = Colors.defaultWhite { 111 | didSet { 112 | lowerThumbLayer.setNeedsDisplay() 113 | upperThumbLayer.setNeedsDisplay() 114 | } 115 | } 116 | 117 | // MARK: - General appearance 118 | 119 | @IBInspectable public var roundedness: CGFloat = 1.0 { 120 | didSet { 121 | trackLayer.setNeedsDisplay() 122 | lowerThumbLayer.setNeedsDisplay() 123 | upperThumbLayer.setNeedsDisplay() 124 | } 125 | } 126 | 127 | // This is a class var so it can be overridden at will 128 | public class var labelAttributes: [NSAttributedString.Key: Any] { 129 | return [ 130 | .font: UIFont.systemFont(ofSize: 14.0), 131 | .foregroundColor: Colors.textGray 132 | ] 133 | } 134 | 135 | @IBInspectable public var minimumSpaceBetweenLabels: CGFloat = 5.0 { 136 | didSet { 137 | updateLayerFrames() 138 | } 139 | } 140 | 141 | @IBInspectable public var spaceBetweenThumbAndLabel: CGFloat = 18.0 { 142 | didSet { 143 | updateLayerFrames() 144 | } 145 | } 146 | 147 | // The minimum distance from minX of the bounds that the lowerLabel can move to 148 | // Make this a positive value to allow the label to go beyond the bounds 149 | // Make this negative value to make the label stay away from the edge of the bounds 150 | @IBInspectable public var lowerLabelMarginOffset: CGFloat = 0.0 151 | 152 | // The maximum distance from maxX of the bounds that the upperLabel can move to 153 | // Make this a positive value to allow the label to go beyond the bounds 154 | // Make this negative value to make the label stay away from the edge of the bounds 155 | @IBInspectable public var upperLabelMarginOffset: CGFloat = 0.0 156 | 157 | // The furthest left that a label can move 158 | var lowerLabelMargin: CGFloat { 159 | return bounds.minX - lowerLabelMarginOffset 160 | } 161 | 162 | // The furthest right that a label can move 163 | var upperLabelMargin: CGFloat { 164 | return bounds.maxX + upperLabelMarginOffset 165 | } 166 | 167 | public weak var labelDelegate: DoubleSliderLabelDelegate? { 168 | didSet { 169 | updateLayerFrames() 170 | } 171 | } 172 | 173 | public weak var valueChangedDelegate: DoubleSliderValueChangedDelegate? 174 | 175 | public weak var editingDidEndDelegate: DoubleSliderEditingDidEndDelegate? 176 | 177 | // Render components 178 | public let trackLayer = DoubleSliderTrackLayer() 179 | public let lowerThumbLayer = DoubleSliderThumbLayer() 180 | public let upperThumbLayer = DoubleSliderThumbLayer() 181 | public let minLabel = CATextLayer() 182 | public let maxLabel = CATextLayer() 183 | 184 | var previousLocation = CGPoint() 185 | 186 | open var thumbWidth: CGFloat { 187 | return CGFloat(bounds.height) 188 | } 189 | 190 | override open var frame: CGRect { 191 | didSet { 192 | updateLayerFrames() 193 | } 194 | } 195 | 196 | override open var bounds: CGRect { 197 | // Without this, the sizing in AutoLayout would be off 198 | didSet { 199 | updateLayerFrames() 200 | } 201 | } 202 | 203 | // MARK: - Internal funcs/vars 204 | 205 | var stepDistance: Double? { 206 | guard numberOfSteps > 0 else { return nil } 207 | 208 | return maxValue / Double(numberOfSteps) 209 | } 210 | 211 | func value(for stepIndex: Int) -> Double? { 212 | guard let stepDistance = stepDistance else { 213 | return nil 214 | } 215 | 216 | // This prevents the thumb from looking slightly off at minValue 217 | if stepIndex == 0 { return minValue } 218 | // This prevents the thumb from looking slightly off at maxValue 219 | if stepIndex == numberOfSteps - 1 { return maxValue } 220 | 221 | return Double(stepIndex + 1) * stepDistance - (stepDistance / 2.0) 222 | } 223 | 224 | func stepIndex(for value: Double) -> Int? { 225 | guard numberOfSteps > 0 else { return nil } 226 | 227 | return Int(round((value - minValue) * Double(numberOfSteps - 1))) 228 | } 229 | 230 | // MARK: - Initializers 231 | 232 | public required init?(coder aDecoder: NSCoder) { 233 | super.init(coder: aDecoder) 234 | initialSetup() 235 | } 236 | 237 | public override init(frame: CGRect) { 238 | super.init(frame: frame) 239 | initialSetup() 240 | } 241 | 242 | // MARK: - Private func 243 | 244 | private func initialSetup() { 245 | // The reason that I put the upperThumbLayer before the lowerThumbLayer is to make the 246 | // lowerThumbLayer be above the upperThumbLayer in the z axis, which reflects the priority 247 | // logic when selecting one thumbLayer when they both overlap. Without this, the lowerLayer is 248 | // prioritized even though it is below the upperThumbLayer in the z axis 249 | [trackLayer, upperThumbLayer, lowerThumbLayer, minLabel, maxLabel].forEach { caLayer in 250 | // Set the doubleSlider delegate for CALayers that 251 | // conform to DoubleSliderLayer protocol 252 | if let caLayer = caLayer as? DoubleSliderLayer { 253 | caLayer.doubleSlider = self 254 | } 255 | 256 | caLayer.contentsScale = UIScreen.main.scale 257 | layer.addSublayer(caLayer) 258 | } 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /DoubleSliderDemo/DoubleSliderDemo/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /DoubleSliderDemo/DoubleSliderDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 036F406020760DF9002AC7C0 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036F405F20760DF8002AC7C0 /* ViewController.swift */; }; 11 | 03763D762072443D000AFB80 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03763D752072443D000AFB80 /* AppDelegate.swift */; }; 12 | 03763D7D2072443D000AFB80 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 03763D7C2072443D000AFB80 /* Assets.xcassets */; }; 13 | 03763D802072443D000AFB80 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 03763D7E2072443D000AFB80 /* LaunchScreen.storyboard */; }; 14 | 03763D8820724463000AFB80 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03763D8720724463000AFB80 /* Extensions.swift */; }; 15 | 03763D8C2072448A000AFB80 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 03763D8B2072448A000AFB80 /* Main.storyboard */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXCopyFilesBuildPhase section */ 19 | 03125729207244E700AA8ACB /* Embed Frameworks */ = { 20 | isa = PBXCopyFilesBuildPhase; 21 | buildActionMask = 2147483647; 22 | dstPath = ""; 23 | dstSubfolderSpec = 10; 24 | files = ( 25 | ); 26 | name = "Embed Frameworks"; 27 | runOnlyForDeploymentPostprocessing = 0; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 036F405F20760DF8002AC7C0 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 33 | 03763D722072443D000AFB80 /* DoubleSliderDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DoubleSliderDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | 03763D752072443D000AFB80 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 35 | 03763D7C2072443D000AFB80 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 36 | 03763D7F2072443D000AFB80 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 37 | 03763D812072443D000AFB80 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 38 | 03763D8720724463000AFB80 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 39 | 03763D8B2072448A000AFB80 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 40 | /* End PBXFileReference section */ 41 | 42 | /* Begin PBXFrameworksBuildPhase section */ 43 | 03763D6F2072443D000AFB80 /* Frameworks */ = { 44 | isa = PBXFrameworksBuildPhase; 45 | buildActionMask = 2147483647; 46 | files = ( 47 | ); 48 | runOnlyForDeploymentPostprocessing = 0; 49 | }; 50 | /* End PBXFrameworksBuildPhase section */ 51 | 52 | /* Begin PBXGroup section */ 53 | 03763D692072443D000AFB80 = { 54 | isa = PBXGroup; 55 | children = ( 56 | 03763D742072443D000AFB80 /* DoubleSliderDemo */, 57 | 03763D732072443D000AFB80 /* Products */, 58 | ); 59 | sourceTree = ""; 60 | }; 61 | 03763D732072443D000AFB80 /* Products */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | 03763D722072443D000AFB80 /* DoubleSliderDemo.app */, 65 | ); 66 | name = Products; 67 | sourceTree = ""; 68 | }; 69 | 03763D742072443D000AFB80 /* DoubleSliderDemo */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | 03763D8720724463000AFB80 /* Extensions.swift */, 73 | 03763D752072443D000AFB80 /* AppDelegate.swift */, 74 | 036F405F20760DF8002AC7C0 /* ViewController.swift */, 75 | 03763D8B2072448A000AFB80 /* Main.storyboard */, 76 | 03763D7C2072443D000AFB80 /* Assets.xcassets */, 77 | 03763D7E2072443D000AFB80 /* LaunchScreen.storyboard */, 78 | 03763D812072443D000AFB80 /* Info.plist */, 79 | ); 80 | path = DoubleSliderDemo; 81 | sourceTree = ""; 82 | }; 83 | /* End PBXGroup section */ 84 | 85 | /* Begin PBXNativeTarget section */ 86 | 03763D712072443D000AFB80 /* DoubleSliderDemo */ = { 87 | isa = PBXNativeTarget; 88 | buildConfigurationList = 03763D842072443D000AFB80 /* Build configuration list for PBXNativeTarget "DoubleSliderDemo" */; 89 | buildPhases = ( 90 | 03763D6E2072443D000AFB80 /* Sources */, 91 | 03763D6F2072443D000AFB80 /* Frameworks */, 92 | 03763D702072443D000AFB80 /* Resources */, 93 | 03125729207244E700AA8ACB /* Embed Frameworks */, 94 | 0392EF0A2090412E00DEE1E3 /* ShellScript */, 95 | ); 96 | buildRules = ( 97 | ); 98 | dependencies = ( 99 | ); 100 | name = DoubleSliderDemo; 101 | productName = DoubleSliderDemo; 102 | productReference = 03763D722072443D000AFB80 /* DoubleSliderDemo.app */; 103 | productType = "com.apple.product-type.application"; 104 | }; 105 | /* End PBXNativeTarget section */ 106 | 107 | /* Begin PBXProject section */ 108 | 03763D6A2072443D000AFB80 /* Project object */ = { 109 | isa = PBXProject; 110 | attributes = { 111 | LastSwiftUpdateCheck = 0920; 112 | LastUpgradeCheck = 1250; 113 | ORGANIZATIONNAME = yhkaplan; 114 | TargetAttributes = { 115 | 03763D712072443D000AFB80 = { 116 | CreatedOnToolsVersion = 9.2; 117 | ProvisioningStyle = Automatic; 118 | }; 119 | }; 120 | }; 121 | buildConfigurationList = 03763D6D2072443D000AFB80 /* Build configuration list for PBXProject "DoubleSliderDemo" */; 122 | compatibilityVersion = "Xcode 8.0"; 123 | developmentRegion = en; 124 | hasScannedForEncodings = 0; 125 | knownRegions = ( 126 | en, 127 | Base, 128 | ); 129 | mainGroup = 03763D692072443D000AFB80; 130 | productRefGroup = 03763D732072443D000AFB80 /* Products */; 131 | projectDirPath = ""; 132 | projectRoot = ""; 133 | targets = ( 134 | 03763D712072443D000AFB80 /* DoubleSliderDemo */, 135 | ); 136 | }; 137 | /* End PBXProject section */ 138 | 139 | /* Begin PBXResourcesBuildPhase section */ 140 | 03763D702072443D000AFB80 /* Resources */ = { 141 | isa = PBXResourcesBuildPhase; 142 | buildActionMask = 2147483647; 143 | files = ( 144 | 03763D8C2072448A000AFB80 /* Main.storyboard in Resources */, 145 | 03763D802072443D000AFB80 /* LaunchScreen.storyboard in Resources */, 146 | 03763D7D2072443D000AFB80 /* Assets.xcassets in Resources */, 147 | ); 148 | runOnlyForDeploymentPostprocessing = 0; 149 | }; 150 | /* End PBXResourcesBuildPhase section */ 151 | 152 | /* Begin PBXShellScriptBuildPhase section */ 153 | 0392EF0A2090412E00DEE1E3 /* ShellScript */ = { 154 | isa = PBXShellScriptBuildPhase; 155 | buildActionMask = 2147483647; 156 | files = ( 157 | ); 158 | inputPaths = ( 159 | ); 160 | outputPaths = ( 161 | ); 162 | runOnlyForDeploymentPostprocessing = 0; 163 | shellPath = /bin/sh; 164 | shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi"; 165 | }; 166 | /* End PBXShellScriptBuildPhase section */ 167 | 168 | /* Begin PBXSourcesBuildPhase section */ 169 | 03763D6E2072443D000AFB80 /* Sources */ = { 170 | isa = PBXSourcesBuildPhase; 171 | buildActionMask = 2147483647; 172 | files = ( 173 | 036F406020760DF9002AC7C0 /* ViewController.swift in Sources */, 174 | 03763D8820724463000AFB80 /* Extensions.swift in Sources */, 175 | 03763D762072443D000AFB80 /* AppDelegate.swift in Sources */, 176 | ); 177 | runOnlyForDeploymentPostprocessing = 0; 178 | }; 179 | /* End PBXSourcesBuildPhase section */ 180 | 181 | /* Begin PBXVariantGroup section */ 182 | 03763D7E2072443D000AFB80 /* LaunchScreen.storyboard */ = { 183 | isa = PBXVariantGroup; 184 | children = ( 185 | 03763D7F2072443D000AFB80 /* Base */, 186 | ); 187 | name = LaunchScreen.storyboard; 188 | sourceTree = ""; 189 | }; 190 | /* End PBXVariantGroup section */ 191 | 192 | /* Begin XCBuildConfiguration section */ 193 | 03763D822072443D000AFB80 /* Debug */ = { 194 | isa = XCBuildConfiguration; 195 | buildSettings = { 196 | ALWAYS_SEARCH_USER_PATHS = NO; 197 | CLANG_ANALYZER_NONNULL = YES; 198 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 199 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 200 | CLANG_CXX_LIBRARY = "libc++"; 201 | CLANG_ENABLE_MODULES = YES; 202 | CLANG_ENABLE_OBJC_ARC = YES; 203 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 204 | CLANG_WARN_BOOL_CONVERSION = YES; 205 | CLANG_WARN_COMMA = YES; 206 | CLANG_WARN_CONSTANT_CONVERSION = YES; 207 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 208 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 209 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 210 | CLANG_WARN_EMPTY_BODY = YES; 211 | CLANG_WARN_ENUM_CONVERSION = YES; 212 | CLANG_WARN_INFINITE_RECURSION = YES; 213 | CLANG_WARN_INT_CONVERSION = YES; 214 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 215 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 216 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 217 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 218 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 219 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 220 | CLANG_WARN_STRICT_PROTOTYPES = YES; 221 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 222 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 223 | CLANG_WARN_UNREACHABLE_CODE = YES; 224 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 225 | CODE_SIGN_IDENTITY = "iPhone Developer"; 226 | COPY_PHASE_STRIP = NO; 227 | DEBUG_INFORMATION_FORMAT = dwarf; 228 | ENABLE_STRICT_OBJC_MSGSEND = YES; 229 | ENABLE_TESTABILITY = YES; 230 | GCC_C_LANGUAGE_STANDARD = gnu11; 231 | GCC_DYNAMIC_NO_PIC = NO; 232 | GCC_NO_COMMON_BLOCKS = YES; 233 | GCC_OPTIMIZATION_LEVEL = 0; 234 | GCC_PREPROCESSOR_DEFINITIONS = ( 235 | "DEBUG=1", 236 | "$(inherited)", 237 | ); 238 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 239 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 240 | GCC_WARN_UNDECLARED_SELECTOR = YES; 241 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 242 | GCC_WARN_UNUSED_FUNCTION = YES; 243 | GCC_WARN_UNUSED_VARIABLE = YES; 244 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 245 | MTL_ENABLE_DEBUG_INFO = YES; 246 | ONLY_ACTIVE_ARCH = YES; 247 | SDKROOT = iphoneos; 248 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 249 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 250 | }; 251 | name = Debug; 252 | }; 253 | 03763D832072443D000AFB80 /* Release */ = { 254 | isa = XCBuildConfiguration; 255 | buildSettings = { 256 | ALWAYS_SEARCH_USER_PATHS = NO; 257 | CLANG_ANALYZER_NONNULL = YES; 258 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 259 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 260 | CLANG_CXX_LIBRARY = "libc++"; 261 | CLANG_ENABLE_MODULES = YES; 262 | CLANG_ENABLE_OBJC_ARC = YES; 263 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 264 | CLANG_WARN_BOOL_CONVERSION = YES; 265 | CLANG_WARN_COMMA = YES; 266 | CLANG_WARN_CONSTANT_CONVERSION = YES; 267 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 268 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 269 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 270 | CLANG_WARN_EMPTY_BODY = YES; 271 | CLANG_WARN_ENUM_CONVERSION = YES; 272 | CLANG_WARN_INFINITE_RECURSION = YES; 273 | CLANG_WARN_INT_CONVERSION = YES; 274 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 275 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 276 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 277 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 278 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 279 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 280 | CLANG_WARN_STRICT_PROTOTYPES = YES; 281 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 282 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 283 | CLANG_WARN_UNREACHABLE_CODE = YES; 284 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 285 | CODE_SIGN_IDENTITY = "iPhone Developer"; 286 | COPY_PHASE_STRIP = NO; 287 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 288 | ENABLE_NS_ASSERTIONS = NO; 289 | ENABLE_STRICT_OBJC_MSGSEND = YES; 290 | GCC_C_LANGUAGE_STANDARD = gnu11; 291 | GCC_NO_COMMON_BLOCKS = YES; 292 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 293 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 294 | GCC_WARN_UNDECLARED_SELECTOR = YES; 295 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 296 | GCC_WARN_UNUSED_FUNCTION = YES; 297 | GCC_WARN_UNUSED_VARIABLE = YES; 298 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 299 | MTL_ENABLE_DEBUG_INFO = NO; 300 | SDKROOT = iphoneos; 301 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 302 | VALIDATE_PRODUCT = YES; 303 | }; 304 | name = Release; 305 | }; 306 | 03763D852072443D000AFB80 /* Debug */ = { 307 | isa = XCBuildConfiguration; 308 | buildSettings = { 309 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 310 | CODE_SIGN_STYLE = Automatic; 311 | DEVELOPMENT_TEAM = ""; 312 | INFOPLIST_FILE = DoubleSliderDemo/Info.plist; 313 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 314 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 315 | PRODUCT_BUNDLE_IDENTIFIER = yhkaplan.DoubleSliderDemo; 316 | PRODUCT_NAME = "$(TARGET_NAME)"; 317 | SWIFT_VERSION = 4.0; 318 | TARGETED_DEVICE_FAMILY = "1,2"; 319 | }; 320 | name = Debug; 321 | }; 322 | 03763D862072443D000AFB80 /* Release */ = { 323 | isa = XCBuildConfiguration; 324 | buildSettings = { 325 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 326 | CODE_SIGN_STYLE = Automatic; 327 | DEVELOPMENT_TEAM = ""; 328 | INFOPLIST_FILE = DoubleSliderDemo/Info.plist; 329 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 330 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 331 | PRODUCT_BUNDLE_IDENTIFIER = yhkaplan.DoubleSliderDemo; 332 | PRODUCT_NAME = "$(TARGET_NAME)"; 333 | SWIFT_VERSION = 4.0; 334 | TARGETED_DEVICE_FAMILY = "1,2"; 335 | }; 336 | name = Release; 337 | }; 338 | /* End XCBuildConfiguration section */ 339 | 340 | /* Begin XCConfigurationList section */ 341 | 03763D6D2072443D000AFB80 /* Build configuration list for PBXProject "DoubleSliderDemo" */ = { 342 | isa = XCConfigurationList; 343 | buildConfigurations = ( 344 | 03763D822072443D000AFB80 /* Debug */, 345 | 03763D832072443D000AFB80 /* Release */, 346 | ); 347 | defaultConfigurationIsVisible = 0; 348 | defaultConfigurationName = Release; 349 | }; 350 | 03763D842072443D000AFB80 /* Build configuration list for PBXNativeTarget "DoubleSliderDemo" */ = { 351 | isa = XCConfigurationList; 352 | buildConfigurations = ( 353 | 03763D852072443D000AFB80 /* Debug */, 354 | 03763D862072443D000AFB80 /* Release */, 355 | ); 356 | defaultConfigurationIsVisible = 0; 357 | defaultConfigurationName = Release; 358 | }; 359 | /* End XCConfigurationList section */ 360 | }; 361 | rootObject = 03763D6A2072443D000AFB80 /* Project object */; 362 | } 363 | -------------------------------------------------------------------------------- /DoubleSlider/DoubleSlider.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 030B81182085F67E00169A88 /* DoubleSliderLayerProtocol.swift in Headers */ = {isa = PBXBuildFile; fileRef = 03763D5620724317000AFB80 /* DoubleSliderLayerProtocol.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | 030B81192085F67E00169A88 /* DoubleSliderThumbLayer.swift in Headers */ = {isa = PBXBuildFile; fileRef = 03763D5420724317000AFB80 /* DoubleSliderThumbLayer.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 12 | 030B811A2085F67E00169A88 /* DoubleSliderTrackLayer.swift in Headers */ = {isa = PBXBuildFile; fileRef = 03763D5720724317000AFB80 /* DoubleSliderTrackLayer.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | 030B811B2085F67E00169A88 /* DoubleSliderColors.swift in Headers */ = {isa = PBXBuildFile; fileRef = 0333886C207F22FE003F5981 /* DoubleSliderColors.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 14 | 030B811C2085F67E00169A88 /* DoubleSliderDelegates.swift in Headers */ = {isa = PBXBuildFile; fileRef = 03763D5E20724326000AFB80 /* DoubleSliderDelegates.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 15 | 030B811D2085F67E00169A88 /* DoubleSliderMain.swift in Headers */ = {isa = PBXBuildFile; fileRef = 03763D5D20724326000AFB80 /* DoubleSliderMain.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 16 | 030B811E2085F67E00169A88 /* DoubleSliderUpdateFunctions.swift in Headers */ = {isa = PBXBuildFile; fileRef = 03763D5C20724325000AFB80 /* DoubleSliderUpdateFunctions.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 17 | 030B811F2085F67E00169A88 /* DoubleSliderTouchTracking.swift in Headers */ = {isa = PBXBuildFile; fileRef = 03763D5F20724326000AFB80 /* DoubleSliderTouchTracking.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 18 | 030B81202085F67E00169A88 /* Extensions.swift in Headers */ = {isa = PBXBuildFile; fileRef = 03763D6420724330000AFB80 /* Extensions.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 19 | 0333886D207F22FE003F5981 /* DoubleSliderColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0333886C207F22FE003F5981 /* DoubleSliderColors.swift */; }; 20 | 03763D3C20724273000AFB80 /* DoubleSlider.h in Headers */ = {isa = PBXBuildFile; fileRef = 03763D3A20724273000AFB80 /* DoubleSlider.h */; settings = {ATTRIBUTES = (Public, ); }; }; 21 | 03763D5820724317000AFB80 /* DoubleSliderThumbLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03763D5420724317000AFB80 /* DoubleSliderThumbLayer.swift */; }; 22 | 03763D5A20724317000AFB80 /* DoubleSliderLayerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03763D5620724317000AFB80 /* DoubleSliderLayerProtocol.swift */; }; 23 | 03763D5B20724317000AFB80 /* DoubleSliderTrackLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03763D5720724317000AFB80 /* DoubleSliderTrackLayer.swift */; }; 24 | 03763D6020724326000AFB80 /* DoubleSliderUpdateFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03763D5C20724325000AFB80 /* DoubleSliderUpdateFunctions.swift */; }; 25 | 03763D6120724326000AFB80 /* DoubleSliderMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03763D5D20724326000AFB80 /* DoubleSliderMain.swift */; }; 26 | 03763D6220724326000AFB80 /* DoubleSliderDelegates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03763D5E20724326000AFB80 /* DoubleSliderDelegates.swift */; }; 27 | 03763D6320724326000AFB80 /* DoubleSliderTouchTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03763D5F20724326000AFB80 /* DoubleSliderTouchTracking.swift */; }; 28 | 03763D6520724331000AFB80 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03763D6420724330000AFB80 /* Extensions.swift */; }; 29 | 039F2E712085A1FA00A2DB79 /* DoubleSlider.h in Headers */ = {isa = PBXBuildFile; fileRef = 03763D3A20724273000AFB80 /* DoubleSlider.h */; settings = {ATTRIBUTES = (Public, ); }; }; 30 | 03DDABD52084AA7F00E2BB4A /* DoubleSliderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03DDABD42084AA7F00E2BB4A /* DoubleSliderTests.swift */; }; 31 | 03DDABD72084AA8000E2BB4A /* DoubleSlider.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03763D3720724273000AFB80 /* DoubleSlider.framework */; }; 32 | /* End PBXBuildFile section */ 33 | 34 | /* Begin PBXContainerItemProxy section */ 35 | 03DDABD82084AA8000E2BB4A /* PBXContainerItemProxy */ = { 36 | isa = PBXContainerItemProxy; 37 | containerPortal = 03763D2E20724273000AFB80 /* Project object */; 38 | proxyType = 1; 39 | remoteGlobalIDString = 03763D3620724273000AFB80; 40 | remoteInfo = DoubleSlider; 41 | }; 42 | /* End PBXContainerItemProxy section */ 43 | 44 | /* Begin PBXFileReference section */ 45 | 0333886C207F22FE003F5981 /* DoubleSliderColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoubleSliderColors.swift; sourceTree = ""; }; 46 | 03763D3720724273000AFB80 /* DoubleSlider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DoubleSlider.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | 03763D3A20724273000AFB80 /* DoubleSlider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DoubleSlider.h; sourceTree = ""; }; 48 | 03763D3B20724273000AFB80 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49 | 03763D5420724317000AFB80 /* DoubleSliderThumbLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoubleSliderThumbLayer.swift; sourceTree = ""; }; 50 | 03763D5620724317000AFB80 /* DoubleSliderLayerProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoubleSliderLayerProtocol.swift; sourceTree = ""; }; 51 | 03763D5720724317000AFB80 /* DoubleSliderTrackLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoubleSliderTrackLayer.swift; sourceTree = ""; }; 52 | 03763D5C20724325000AFB80 /* DoubleSliderUpdateFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoubleSliderUpdateFunctions.swift; sourceTree = ""; }; 53 | 03763D5D20724326000AFB80 /* DoubleSliderMain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoubleSliderMain.swift; sourceTree = ""; }; 54 | 03763D5E20724326000AFB80 /* DoubleSliderDelegates.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoubleSliderDelegates.swift; sourceTree = ""; }; 55 | 03763D5F20724326000AFB80 /* DoubleSliderTouchTracking.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoubleSliderTouchTracking.swift; sourceTree = ""; }; 56 | 03763D6420724330000AFB80 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 57 | 03DDABD22084AA7F00E2BB4A /* DoubleSliderTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DoubleSliderTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | 03DDABD42084AA7F00E2BB4A /* DoubleSliderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoubleSliderTests.swift; sourceTree = ""; }; 59 | 03DDABD62084AA8000E2BB4A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 60 | /* End PBXFileReference section */ 61 | 62 | /* Begin PBXFrameworksBuildPhase section */ 63 | 03763D3320724273000AFB80 /* Frameworks */ = { 64 | isa = PBXFrameworksBuildPhase; 65 | buildActionMask = 2147483647; 66 | files = ( 67 | ); 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | 03DDABCF2084AA7F00E2BB4A /* Frameworks */ = { 71 | isa = PBXFrameworksBuildPhase; 72 | buildActionMask = 2147483647; 73 | files = ( 74 | 03DDABD72084AA8000E2BB4A /* DoubleSlider.framework in Frameworks */, 75 | ); 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | /* End PBXFrameworksBuildPhase section */ 79 | 80 | /* Begin PBXGroup section */ 81 | 03763D2D20724273000AFB80 = { 82 | isa = PBXGroup; 83 | children = ( 84 | 03763D3920724273000AFB80 /* DoubleSlider */, 85 | 03DDABD32084AA7F00E2BB4A /* DoubleSliderTests */, 86 | 03763D3820724273000AFB80 /* Products */, 87 | ); 88 | sourceTree = ""; 89 | }; 90 | 03763D3820724273000AFB80 /* Products */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | 03763D3720724273000AFB80 /* DoubleSlider.framework */, 94 | 03DDABD22084AA7F00E2BB4A /* DoubleSliderTests.xctest */, 95 | ); 96 | name = Products; 97 | sourceTree = ""; 98 | }; 99 | 03763D3920724273000AFB80 /* DoubleSlider */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 03763D68207243D2000AFB80 /* CALayers */, 103 | 03763D67207243BF000AFB80 /* DoubleSlider */, 104 | 03763D66207243B7000AFB80 /* Extensions */, 105 | 03763D3B20724273000AFB80 /* Info.plist */, 106 | ); 107 | path = DoubleSlider; 108 | sourceTree = ""; 109 | }; 110 | 03763D66207243B7000AFB80 /* Extensions */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 03763D6420724330000AFB80 /* Extensions.swift */, 114 | ); 115 | path = Extensions; 116 | sourceTree = ""; 117 | }; 118 | 03763D67207243BF000AFB80 /* DoubleSlider */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 03763D3A20724273000AFB80 /* DoubleSlider.h */, 122 | 0333886C207F22FE003F5981 /* DoubleSliderColors.swift */, 123 | 03763D5E20724326000AFB80 /* DoubleSliderDelegates.swift */, 124 | 03763D5D20724326000AFB80 /* DoubleSliderMain.swift */, 125 | 03763D5C20724325000AFB80 /* DoubleSliderUpdateFunctions.swift */, 126 | 03763D5F20724326000AFB80 /* DoubleSliderTouchTracking.swift */, 127 | ); 128 | path = DoubleSlider; 129 | sourceTree = ""; 130 | }; 131 | 03763D68207243D2000AFB80 /* CALayers */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | 03763D5620724317000AFB80 /* DoubleSliderLayerProtocol.swift */, 135 | 03763D5420724317000AFB80 /* DoubleSliderThumbLayer.swift */, 136 | 03763D5720724317000AFB80 /* DoubleSliderTrackLayer.swift */, 137 | ); 138 | path = CALayers; 139 | sourceTree = ""; 140 | }; 141 | 03DDABD32084AA7F00E2BB4A /* DoubleSliderTests */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | 03DDABD42084AA7F00E2BB4A /* DoubleSliderTests.swift */, 145 | 03DDABD62084AA8000E2BB4A /* Info.plist */, 146 | ); 147 | path = DoubleSliderTests; 148 | sourceTree = ""; 149 | }; 150 | /* End PBXGroup section */ 151 | 152 | /* Begin PBXHeadersBuildPhase section */ 153 | 03763D3420724273000AFB80 /* Headers */ = { 154 | isa = PBXHeadersBuildPhase; 155 | buildActionMask = 2147483647; 156 | files = ( 157 | 03763D3C20724273000AFB80 /* DoubleSlider.h in Headers */, 158 | 030B81182085F67E00169A88 /* DoubleSliderLayerProtocol.swift in Headers */, 159 | 030B81192085F67E00169A88 /* DoubleSliderThumbLayer.swift in Headers */, 160 | 030B811A2085F67E00169A88 /* DoubleSliderTrackLayer.swift in Headers */, 161 | 030B811B2085F67E00169A88 /* DoubleSliderColors.swift in Headers */, 162 | 030B811C2085F67E00169A88 /* DoubleSliderDelegates.swift in Headers */, 163 | 030B811D2085F67E00169A88 /* DoubleSliderMain.swift in Headers */, 164 | 030B811E2085F67E00169A88 /* DoubleSliderUpdateFunctions.swift in Headers */, 165 | 030B811F2085F67E00169A88 /* DoubleSliderTouchTracking.swift in Headers */, 166 | 030B81202085F67E00169A88 /* Extensions.swift in Headers */, 167 | ); 168 | runOnlyForDeploymentPostprocessing = 0; 169 | }; 170 | 039F2E7020859FFC00A2DB79 /* Headers */ = { 171 | isa = PBXHeadersBuildPhase; 172 | buildActionMask = 2147483647; 173 | files = ( 174 | 039F2E712085A1FA00A2DB79 /* DoubleSlider.h in Headers */, 175 | ); 176 | runOnlyForDeploymentPostprocessing = 0; 177 | }; 178 | /* End PBXHeadersBuildPhase section */ 179 | 180 | /* Begin PBXNativeTarget section */ 181 | 03763D3620724273000AFB80 /* DoubleSlider */ = { 182 | isa = PBXNativeTarget; 183 | buildConfigurationList = 03763D3F20724273000AFB80 /* Build configuration list for PBXNativeTarget "DoubleSlider" */; 184 | buildPhases = ( 185 | 03763D3220724273000AFB80 /* Sources */, 186 | 03763D3320724273000AFB80 /* Frameworks */, 187 | 03763D3420724273000AFB80 /* Headers */, 188 | 03763D3520724273000AFB80 /* Resources */, 189 | 0392EF092090410600DEE1E3 /* ShellScript */, 190 | ); 191 | buildRules = ( 192 | ); 193 | dependencies = ( 194 | ); 195 | name = DoubleSlider; 196 | productName = DoubleSlider; 197 | productReference = 03763D3720724273000AFB80 /* DoubleSlider.framework */; 198 | productType = "com.apple.product-type.framework"; 199 | }; 200 | 03DDABD12084AA7F00E2BB4A /* DoubleSliderTests */ = { 201 | isa = PBXNativeTarget; 202 | buildConfigurationList = 03DDABDC2084AA8000E2BB4A /* Build configuration list for PBXNativeTarget "DoubleSliderTests" */; 203 | buildPhases = ( 204 | 03DDABCE2084AA7F00E2BB4A /* Sources */, 205 | 03DDABCF2084AA7F00E2BB4A /* Frameworks */, 206 | 039F2E7020859FFC00A2DB79 /* Headers */, 207 | 03DDABD02084AA7F00E2BB4A /* Resources */, 208 | ); 209 | buildRules = ( 210 | ); 211 | dependencies = ( 212 | 03DDABD92084AA8000E2BB4A /* PBXTargetDependency */, 213 | ); 214 | name = DoubleSliderTests; 215 | productName = DoubleSliderTests; 216 | productReference = 03DDABD22084AA7F00E2BB4A /* DoubleSliderTests.xctest */; 217 | productType = "com.apple.product-type.bundle.unit-test"; 218 | }; 219 | /* End PBXNativeTarget section */ 220 | 221 | /* Begin PBXProject section */ 222 | 03763D2E20724273000AFB80 /* Project object */ = { 223 | isa = PBXProject; 224 | attributes = { 225 | LastSwiftUpdateCheck = 0920; 226 | LastUpgradeCheck = 1250; 227 | ORGANIZATIONNAME = yhkaplan; 228 | TargetAttributes = { 229 | 03763D3620724273000AFB80 = { 230 | CreatedOnToolsVersion = 9.2; 231 | LastSwiftMigration = 1150; 232 | ProvisioningStyle = Automatic; 233 | }; 234 | 03DDABD12084AA7F00E2BB4A = { 235 | CreatedOnToolsVersion = 9.2; 236 | ProvisioningStyle = Automatic; 237 | }; 238 | }; 239 | }; 240 | buildConfigurationList = 03763D3120724273000AFB80 /* Build configuration list for PBXProject "DoubleSlider" */; 241 | compatibilityVersion = "Xcode 8.0"; 242 | developmentRegion = en; 243 | hasScannedForEncodings = 0; 244 | knownRegions = ( 245 | en, 246 | Base, 247 | ); 248 | mainGroup = 03763D2D20724273000AFB80; 249 | productRefGroup = 03763D3820724273000AFB80 /* Products */; 250 | projectDirPath = ""; 251 | projectRoot = ""; 252 | targets = ( 253 | 03763D3620724273000AFB80 /* DoubleSlider */, 254 | 03DDABD12084AA7F00E2BB4A /* DoubleSliderTests */, 255 | ); 256 | }; 257 | /* End PBXProject section */ 258 | 259 | /* Begin PBXResourcesBuildPhase section */ 260 | 03763D3520724273000AFB80 /* Resources */ = { 261 | isa = PBXResourcesBuildPhase; 262 | buildActionMask = 2147483647; 263 | files = ( 264 | ); 265 | runOnlyForDeploymentPostprocessing = 0; 266 | }; 267 | 03DDABD02084AA7F00E2BB4A /* Resources */ = { 268 | isa = PBXResourcesBuildPhase; 269 | buildActionMask = 2147483647; 270 | files = ( 271 | ); 272 | runOnlyForDeploymentPostprocessing = 0; 273 | }; 274 | /* End PBXResourcesBuildPhase section */ 275 | 276 | /* Begin PBXShellScriptBuildPhase section */ 277 | 0392EF092090410600DEE1E3 /* ShellScript */ = { 278 | isa = PBXShellScriptBuildPhase; 279 | buildActionMask = 2147483647; 280 | files = ( 281 | ); 282 | inputPaths = ( 283 | ); 284 | outputPaths = ( 285 | ); 286 | runOnlyForDeploymentPostprocessing = 0; 287 | shellPath = /bin/sh; 288 | shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi"; 289 | }; 290 | /* End PBXShellScriptBuildPhase section */ 291 | 292 | /* Begin PBXSourcesBuildPhase section */ 293 | 03763D3220724273000AFB80 /* Sources */ = { 294 | isa = PBXSourcesBuildPhase; 295 | buildActionMask = 2147483647; 296 | files = ( 297 | 03763D5820724317000AFB80 /* DoubleSliderThumbLayer.swift in Sources */, 298 | 0333886D207F22FE003F5981 /* DoubleSliderColors.swift in Sources */, 299 | 03763D6220724326000AFB80 /* DoubleSliderDelegates.swift in Sources */, 300 | 03763D5A20724317000AFB80 /* DoubleSliderLayerProtocol.swift in Sources */, 301 | 03763D6020724326000AFB80 /* DoubleSliderUpdateFunctions.swift in Sources */, 302 | 03763D6520724331000AFB80 /* Extensions.swift in Sources */, 303 | 03763D5B20724317000AFB80 /* DoubleSliderTrackLayer.swift in Sources */, 304 | 03763D6120724326000AFB80 /* DoubleSliderMain.swift in Sources */, 305 | 03763D6320724326000AFB80 /* DoubleSliderTouchTracking.swift in Sources */, 306 | ); 307 | runOnlyForDeploymentPostprocessing = 0; 308 | }; 309 | 03DDABCE2084AA7F00E2BB4A /* Sources */ = { 310 | isa = PBXSourcesBuildPhase; 311 | buildActionMask = 2147483647; 312 | files = ( 313 | 03DDABD52084AA7F00E2BB4A /* DoubleSliderTests.swift in Sources */, 314 | ); 315 | runOnlyForDeploymentPostprocessing = 0; 316 | }; 317 | /* End PBXSourcesBuildPhase section */ 318 | 319 | /* Begin PBXTargetDependency section */ 320 | 03DDABD92084AA8000E2BB4A /* PBXTargetDependency */ = { 321 | isa = PBXTargetDependency; 322 | target = 03763D3620724273000AFB80 /* DoubleSlider */; 323 | targetProxy = 03DDABD82084AA8000E2BB4A /* PBXContainerItemProxy */; 324 | }; 325 | /* End PBXTargetDependency section */ 326 | 327 | /* Begin XCBuildConfiguration section */ 328 | 03763D3D20724273000AFB80 /* Debug */ = { 329 | isa = XCBuildConfiguration; 330 | buildSettings = { 331 | ALWAYS_SEARCH_USER_PATHS = NO; 332 | CLANG_ANALYZER_NONNULL = YES; 333 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 334 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 335 | CLANG_CXX_LIBRARY = "libc++"; 336 | CLANG_ENABLE_MODULES = YES; 337 | CLANG_ENABLE_OBJC_ARC = YES; 338 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 339 | CLANG_WARN_BOOL_CONVERSION = YES; 340 | CLANG_WARN_COMMA = YES; 341 | CLANG_WARN_CONSTANT_CONVERSION = YES; 342 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 343 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 344 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 345 | CLANG_WARN_EMPTY_BODY = YES; 346 | CLANG_WARN_ENUM_CONVERSION = YES; 347 | CLANG_WARN_INFINITE_RECURSION = YES; 348 | CLANG_WARN_INT_CONVERSION = YES; 349 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 350 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 351 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 352 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 353 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 354 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 355 | CLANG_WARN_STRICT_PROTOTYPES = YES; 356 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 357 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 358 | CLANG_WARN_UNREACHABLE_CODE = YES; 359 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 360 | CODE_SIGN_IDENTITY = "iPhone Developer"; 361 | COPY_PHASE_STRIP = NO; 362 | CURRENT_PROJECT_VERSION = 1; 363 | DEBUG_INFORMATION_FORMAT = dwarf; 364 | ENABLE_STRICT_OBJC_MSGSEND = YES; 365 | ENABLE_TESTABILITY = YES; 366 | GCC_C_LANGUAGE_STANDARD = gnu11; 367 | GCC_DYNAMIC_NO_PIC = NO; 368 | GCC_NO_COMMON_BLOCKS = YES; 369 | GCC_OPTIMIZATION_LEVEL = 0; 370 | GCC_PREPROCESSOR_DEFINITIONS = ( 371 | "DEBUG=1", 372 | "$(inherited)", 373 | ); 374 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 375 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 376 | GCC_WARN_UNDECLARED_SELECTOR = YES; 377 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 378 | GCC_WARN_UNUSED_FUNCTION = YES; 379 | GCC_WARN_UNUSED_VARIABLE = YES; 380 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 381 | MTL_ENABLE_DEBUG_INFO = YES; 382 | ONLY_ACTIVE_ARCH = YES; 383 | SDKROOT = iphoneos; 384 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 385 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 386 | VERSIONING_SYSTEM = "apple-generic"; 387 | VERSION_INFO_PREFIX = ""; 388 | }; 389 | name = Debug; 390 | }; 391 | 03763D3E20724273000AFB80 /* Release */ = { 392 | isa = XCBuildConfiguration; 393 | buildSettings = { 394 | ALWAYS_SEARCH_USER_PATHS = NO; 395 | CLANG_ANALYZER_NONNULL = YES; 396 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 397 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 398 | CLANG_CXX_LIBRARY = "libc++"; 399 | CLANG_ENABLE_MODULES = YES; 400 | CLANG_ENABLE_OBJC_ARC = YES; 401 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 402 | CLANG_WARN_BOOL_CONVERSION = YES; 403 | CLANG_WARN_COMMA = YES; 404 | CLANG_WARN_CONSTANT_CONVERSION = YES; 405 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 406 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 407 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 408 | CLANG_WARN_EMPTY_BODY = YES; 409 | CLANG_WARN_ENUM_CONVERSION = YES; 410 | CLANG_WARN_INFINITE_RECURSION = YES; 411 | CLANG_WARN_INT_CONVERSION = YES; 412 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 413 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 414 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 415 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 416 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 417 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 418 | CLANG_WARN_STRICT_PROTOTYPES = YES; 419 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 420 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 421 | CLANG_WARN_UNREACHABLE_CODE = YES; 422 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 423 | CODE_SIGN_IDENTITY = "iPhone Developer"; 424 | COPY_PHASE_STRIP = NO; 425 | CURRENT_PROJECT_VERSION = 1; 426 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 427 | ENABLE_NS_ASSERTIONS = NO; 428 | ENABLE_STRICT_OBJC_MSGSEND = YES; 429 | GCC_C_LANGUAGE_STANDARD = gnu11; 430 | GCC_NO_COMMON_BLOCKS = YES; 431 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 432 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 433 | GCC_WARN_UNDECLARED_SELECTOR = YES; 434 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 435 | GCC_WARN_UNUSED_FUNCTION = YES; 436 | GCC_WARN_UNUSED_VARIABLE = YES; 437 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 438 | MTL_ENABLE_DEBUG_INFO = NO; 439 | SDKROOT = iphoneos; 440 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 441 | VALIDATE_PRODUCT = YES; 442 | VERSIONING_SYSTEM = "apple-generic"; 443 | VERSION_INFO_PREFIX = ""; 444 | }; 445 | name = Release; 446 | }; 447 | 03763D4020724273000AFB80 /* Debug */ = { 448 | isa = XCBuildConfiguration; 449 | buildSettings = { 450 | CLANG_ENABLE_MODULES = YES; 451 | CODE_SIGN_IDENTITY = ""; 452 | CODE_SIGN_STYLE = Automatic; 453 | DEFINES_MODULE = YES; 454 | DYLIB_COMPATIBILITY_VERSION = 1; 455 | DYLIB_CURRENT_VERSION = 1; 456 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 457 | INFOPLIST_FILE = DoubleSlider/Info.plist; 458 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 459 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 460 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 461 | PRODUCT_BUNDLE_IDENTIFIER = yhkaplan.DoubleSlider; 462 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 463 | SKIP_INSTALL = YES; 464 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 465 | SWIFT_VERSION = 5.0; 466 | TARGETED_DEVICE_FAMILY = "1,2"; 467 | }; 468 | name = Debug; 469 | }; 470 | 03763D4120724273000AFB80 /* Release */ = { 471 | isa = XCBuildConfiguration; 472 | buildSettings = { 473 | CLANG_ENABLE_MODULES = YES; 474 | CODE_SIGN_IDENTITY = ""; 475 | CODE_SIGN_STYLE = Automatic; 476 | DEFINES_MODULE = YES; 477 | DYLIB_COMPATIBILITY_VERSION = 1; 478 | DYLIB_CURRENT_VERSION = 1; 479 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 480 | INFOPLIST_FILE = DoubleSlider/Info.plist; 481 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 482 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 483 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 484 | PRODUCT_BUNDLE_IDENTIFIER = yhkaplan.DoubleSlider; 485 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 486 | SKIP_INSTALL = YES; 487 | SWIFT_VERSION = 5.0; 488 | TARGETED_DEVICE_FAMILY = "1,2"; 489 | }; 490 | name = Release; 491 | }; 492 | 03DDABDA2084AA8000E2BB4A /* Debug */ = { 493 | isa = XCBuildConfiguration; 494 | buildSettings = { 495 | CODE_SIGN_STYLE = Automatic; 496 | INFOPLIST_FILE = DoubleSliderTests/Info.plist; 497 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 498 | PRODUCT_BUNDLE_IDENTIFIER = yhkaplan.DoubleSliderTests; 499 | PRODUCT_NAME = "$(TARGET_NAME)"; 500 | SWIFT_VERSION = 4.0; 501 | TARGETED_DEVICE_FAMILY = "1,2"; 502 | }; 503 | name = Debug; 504 | }; 505 | 03DDABDB2084AA8000E2BB4A /* Release */ = { 506 | isa = XCBuildConfiguration; 507 | buildSettings = { 508 | CODE_SIGN_STYLE = Automatic; 509 | INFOPLIST_FILE = DoubleSliderTests/Info.plist; 510 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 511 | PRODUCT_BUNDLE_IDENTIFIER = yhkaplan.DoubleSliderTests; 512 | PRODUCT_NAME = "$(TARGET_NAME)"; 513 | SWIFT_VERSION = 4.0; 514 | TARGETED_DEVICE_FAMILY = "1,2"; 515 | }; 516 | name = Release; 517 | }; 518 | /* End XCBuildConfiguration section */ 519 | 520 | /* Begin XCConfigurationList section */ 521 | 03763D3120724273000AFB80 /* Build configuration list for PBXProject "DoubleSlider" */ = { 522 | isa = XCConfigurationList; 523 | buildConfigurations = ( 524 | 03763D3D20724273000AFB80 /* Debug */, 525 | 03763D3E20724273000AFB80 /* Release */, 526 | ); 527 | defaultConfigurationIsVisible = 0; 528 | defaultConfigurationName = Release; 529 | }; 530 | 03763D3F20724273000AFB80 /* Build configuration list for PBXNativeTarget "DoubleSlider" */ = { 531 | isa = XCConfigurationList; 532 | buildConfigurations = ( 533 | 03763D4020724273000AFB80 /* Debug */, 534 | 03763D4120724273000AFB80 /* Release */, 535 | ); 536 | defaultConfigurationIsVisible = 0; 537 | defaultConfigurationName = Release; 538 | }; 539 | 03DDABDC2084AA8000E2BB4A /* Build configuration list for PBXNativeTarget "DoubleSliderTests" */ = { 540 | isa = XCConfigurationList; 541 | buildConfigurations = ( 542 | 03DDABDA2084AA8000E2BB4A /* Debug */, 543 | 03DDABDB2084AA8000E2BB4A /* Release */, 544 | ); 545 | defaultConfigurationIsVisible = 0; 546 | defaultConfigurationName = Release; 547 | }; 548 | /* End XCConfigurationList section */ 549 | }; 550 | rootObject = 03763D2E20724273000AFB80 /* Project object */; 551 | } 552 | --------------------------------------------------------------------------------