├── .gitignore ├── .swift-version ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTORS.md ├── Example ├── Podfile ├── SnappingStepper │ ├── Info.plist │ └── SnappingStepper.h ├── SnappingStepperExample.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ ├── SnappingStepper.xcscheme │ │ ├── SnappingStepperExample.xcscheme │ │ └── Tests.xcscheme ├── SnappingStepperExample │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ └── ViewController.swift └── Tests │ └── Info.plist ├── LICENSE ├── Package.swift ├── README.md ├── SnappingStepper.podspec ├── Sources ├── AutoRepeatHelper.swift ├── CustomShapeLayer.swift ├── ShapeStyle.swift ├── SnappingStepper+Internal.swift ├── SnappingStepper.swift ├── SnappingStepperBehavior.swift ├── StyledControlDirection.swift ├── StyledLabel.swift ├── UIBuilder.swift └── UITouchGestureRecognizer.swift ├── Tests └── SnappingStepperTests.swift └── codecov.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | */build/* 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | *.xccheckout 19 | *.xcworkspace 20 | *.lock 21 | **/Pods/* 22 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 4.2 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode10 3 | before_install: 4 | - gem install xcpretty 5 | - gem install cocoapods 6 | branches: 7 | only: 8 | - master 9 | script: 10 | - cd Example 11 | - pod install 12 | - xcodebuild -version 13 | - xcodebuild -workspace SnappingStepperExample.xcworkspace -scheme Tests -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone 8" -configuration Release GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES GCC_GENERATE_TEST_COVERAGE_FILES=YES ONLY_ACTIVE_ARCH=YES test | xcpretty -c 14 | after_success: 15 | - bash <(curl -s https://codecov.io/bash) 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ## [Version 3.0.0](https://github.com/yannickl/SnappingStepper/releases/tag/3.0.0) 4 | *Released on 2018-10-16.* 5 | 6 | - Swift 4.2 supports 7 | 8 | ## [Version 2.4.0](https://github.com/yannickl/SnappingStepper/releases/tag/2.4.0) 9 | *Released on 2016-09-09.* 10 | 11 | - [ADD] Vertical Layout 12 | 13 | ## [Version 2.3.0](https://github.com/yannickl/SnappingStepper/releases/tag/2.3.0) 14 | *Released on 2016-06-09.* 15 | 16 | - Swift Package Manager 17 | - [ADD] hint property 18 | - [ADD] Custom shape customization 19 | 20 | ## [Version 2.2.0](https://github.com/yannickl/SnappingStepper/releases/tag/2.2.0) 21 | *Released on 2016-03-22.* 22 | 23 | - Swift 2.2 supports 24 | 25 | ## [Version 2.1.1](https://github.com/yannickl/SnappingStepper/releases/tag/2.1.1) 26 | *Released on 2016-03-12.* 27 | 28 | - [REFACTORIGNG] minus symbol with the unicode one. 29 | 30 | ## [Version 2.1.0](https://github.com/yannickl/SnappingStepper/releases/tag/2.1.0) 31 | *Released on 2015-10-21.* 32 | 33 | - [REFACTORIGNG] `fontColor` to `symbolFontColor` 34 | - [REFACTORIGNG] `font` to `symbolFont` 35 | - [REFACTORING] `thumbColor` to `thumbBackgroundColor` 36 | - [ADD] `thumbWidthRatio` property 37 | - [ADD] `thumbText` property 38 | - [ADD] `thumbTextColor` property 39 | - [ADD] `thumbFont` property 40 | 41 | ## [Version 2.0.0](https://github.com/yannickl/SnappingStepper/releases/tag/2.0.0) 42 | *Released on 2015-09-17.* 43 | 44 | - [UPDATE] Swift 2 compatibility 45 | - [ADD] Carthage support 46 | - [ADD] Changelog 47 | 48 | ## [Version 1.2.1](https://github.com/yannickl/SnappingStepper/releases/tag/1.3.1) 49 | *Released on 2015-05-31.* 50 | 51 | - [FIX] `value` should always be into the bounds [`minimumValue`:`maximumValue`] 52 | 53 | ## [Version 1.2.0](https://github.com/yannickl/SnappingStepper/releases/tag/1.2.0) 54 | *Released on 2015-05-30.* 55 | 56 | - [ADD] `valueChangedBlock` property 57 | 58 | ## [Version 1.1.0](https://github.com/yannickl/SnappingStepper/releases/tag/1.1.0) 59 | *Released on 2015-05-29.* 60 | 61 | - [ADD] `fontColor` property 62 | - [ADD] New logo 63 | - [ADD] Test cases 64 | - [UPDATE] Improve the slider behavior 65 | - [FIX] Minimum <> Maximum range values 66 | 67 | ## [Version 1.0.3](https://github.com/yannickl/SnappingStepper/releases/tag/1.0.3) 68 | *Released on 2015-05-28.* 69 | 70 | - [FIX] Non autorepeat slider behavior 71 | 72 | ## [Version 1.0.2](https://github.com/yannickl/SnappingStepper/releases/tag/1.0.2) 73 | *Released on 2015-05-18.* 74 | 75 | - [ADD] `thumbColor` property 76 | - [FIX] Background color of the stepper 77 | 78 | ## [Version 1.0.1](https://github.com/yannickl/SnappingStepper/releases/tag/1.0.1) 79 | *Released on 2015-05-03.* 80 | 81 | - [FIX] Public variable accessors 82 | 83 | ## Initial Version [1.0.0](https://github.com/yannickl/SnappingStepper/releases/tag/1.0.0) 84 | *Released on 2015-05-02.* 85 | 86 | - `continuous` property 87 | - `autorepeat` property 88 | - `wraps` property 89 | - `minimumValue` property 90 | - `maximumValue` property 91 | - `stepValue` property 92 | - `value` property 93 | - `font` property 94 | - `fontColor` property 95 | - Cocoapods support 96 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | ## Contributors 2 | This is the official list of people who can contribute (and typically have contributed) code to the SnappingStepper repository. 3 | 4 | Names should be added to this file like so: 5 | ``` * [Firstname Lastname|Nickname](github_page_url)``` 6 | 7 | Please keep the list sorted. 8 | 9 | ### Lead developer 10 | 11 | * [Yannick Loriot](https://github.com/yannickl) 12 | 13 | ### People and companies, who have contributed 14 | 15 | * [Ergün KOÇAK](https://github.com/ergunkocak) 16 | * [Martin J. Rehder](https://github.com/mjrehder) 17 | * [Vincent Tourraine](https://github.com/vtourraine) 18 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '9.0' 2 | 3 | source 'https://github.com/CocoaPods/Specs.git' 4 | use_frameworks! 5 | 6 | def shared_pods 7 | pod 'DynamicColor', '~> 4.0' 8 | end 9 | 10 | target 'SnappingStepper' do 11 | shared_pods 12 | end 13 | 14 | target 'SnappingStepperExample' do 15 | shared_pods 16 | end 17 | 18 | target 'Tests' do 19 | shared_pods 20 | end 21 | -------------------------------------------------------------------------------- /Example/SnappingStepper/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | 3.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/SnappingStepper/SnappingStepper.h: -------------------------------------------------------------------------------- 1 | // 2 | // SnappingStepper.h 3 | // SnappingStepper 4 | // 5 | // Created by Yannick LORIOT on 17/09/15. 6 | // Copyright © 2015 Yannick Loriot. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for SnappingStepper. 12 | FOUNDATION_EXPORT double SnappingStepperVersionNumber; 13 | 14 | //! Project version string for SnappingStepper. 15 | FOUNDATION_EXPORT const unsigned char SnappingStepperVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Example/SnappingStepperExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 01AA9210AD07C825DAE1F607 /* Pods_SnappingStepperExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 449D06ECFFEC0DD8C4DA6EF2 /* Pods_SnappingStepperExample.framework */; }; 11 | 0D1DA54DFA51DA8D2A48E14A /* Pods_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09E3430E9EA28A5D0C4ED262 /* Pods_Tests.framework */; }; 12 | 5B1EFC7E1D71932E0040AD50 /* StyledControlDirection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B1EFC7D1D71932E0040AD50 /* StyledControlDirection.swift */; }; 13 | 5B1EFC7F1D71932E0040AD50 /* StyledControlDirection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B1EFC7D1D71932E0040AD50 /* StyledControlDirection.swift */; }; 14 | 5B1EFC801D71932E0040AD50 /* StyledControlDirection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B1EFC7D1D71932E0040AD50 /* StyledControlDirection.swift */; }; 15 | 5E1F7E9CB92461FE26B43DF2 /* Pods_SnappingStepper.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 921B58F6F24292F873D6FE4A /* Pods_SnappingStepper.framework */; }; 16 | CE132D681D0880EB00CA6DB5 /* UIBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE132D671D0880EB00CA6DB5 /* UIBuilder.swift */; }; 17 | CE132D691D0880EB00CA6DB5 /* UIBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE132D671D0880EB00CA6DB5 /* UIBuilder.swift */; }; 18 | CE132D6A1D08810C00CA6DB5 /* UIBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE132D671D0880EB00CA6DB5 /* UIBuilder.swift */; }; 19 | CE6F723E1AF513810019CB55 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6F723D1AF513810019CB55 /* AppDelegate.swift */; }; 20 | CE6F72401AF513810019CB55 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6F723F1AF513810019CB55 /* ViewController.swift */; }; 21 | CE6F72431AF513810019CB55 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CE6F72411AF513810019CB55 /* Main.storyboard */; }; 22 | CE6F72451AF513810019CB55 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CE6F72441AF513810019CB55 /* Images.xcassets */; }; 23 | CE6F72481AF513810019CB55 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE6F72461AF513810019CB55 /* LaunchScreen.xib */; }; 24 | CE8FFEB21BAB22C100D43F38 /* SnappingStepper.h in Headers */ = {isa = PBXBuildFile; fileRef = CE8FFEB11BAB22C100D43F38 /* SnappingStepper.h */; settings = {ATTRIBUTES = (Public, ); }; }; 25 | CE8FFEC41BAB22C100D43F38 /* SnappingStepper.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE8FFEAF1BAB22C100D43F38 /* SnappingStepper.framework */; }; 26 | CE8FFEC51BAB22C100D43F38 /* SnappingStepper.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CE8FFEAF1BAB22C100D43F38 /* SnappingStepper.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 27 | CE972EEE1D044B7E00EDE4A7 /* CustomShapeLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE972EE71D044B7E00EDE4A7 /* CustomShapeLayer.swift */; }; 28 | CE972EEF1D044B7E00EDE4A7 /* CustomShapeLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE972EE71D044B7E00EDE4A7 /* CustomShapeLayer.swift */; }; 29 | CE972EF01D044B7E00EDE4A7 /* ShapeStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE972EE81D044B7E00EDE4A7 /* ShapeStyle.swift */; }; 30 | CE972EF11D044B7E00EDE4A7 /* ShapeStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE972EE81D044B7E00EDE4A7 /* ShapeStyle.swift */; }; 31 | CE972EF21D044B7E00EDE4A7 /* SnappingStepper+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE972EE91D044B7E00EDE4A7 /* SnappingStepper+Internal.swift */; }; 32 | CE972EF31D044B7E00EDE4A7 /* SnappingStepper+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE972EE91D044B7E00EDE4A7 /* SnappingStepper+Internal.swift */; }; 33 | CE972EF41D044B7E00EDE4A7 /* SnappingStepper.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE972EEA1D044B7E00EDE4A7 /* SnappingStepper.swift */; }; 34 | CE972EF51D044B7E00EDE4A7 /* SnappingStepper.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE972EEA1D044B7E00EDE4A7 /* SnappingStepper.swift */; }; 35 | CE972EF61D044B7E00EDE4A7 /* SnappingStepperBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE972EEB1D044B7E00EDE4A7 /* SnappingStepperBehavior.swift */; }; 36 | CE972EF71D044B7E00EDE4A7 /* SnappingStepperBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE972EEB1D044B7E00EDE4A7 /* SnappingStepperBehavior.swift */; }; 37 | CE972EF81D044B7E00EDE4A7 /* StyledLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE972EEC1D044B7E00EDE4A7 /* StyledLabel.swift */; }; 38 | CE972EF91D044B7E00EDE4A7 /* StyledLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE972EEC1D044B7E00EDE4A7 /* StyledLabel.swift */; }; 39 | CE972EFA1D044B7E00EDE4A7 /* UITouchGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE972EED1D044B7E00EDE4A7 /* UITouchGestureRecognizer.swift */; }; 40 | CE972EFB1D044B7E00EDE4A7 /* UITouchGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE972EED1D044B7E00EDE4A7 /* UITouchGestureRecognizer.swift */; }; 41 | CE972EFC1D044B8200EDE4A7 /* CustomShapeLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE972EE71D044B7E00EDE4A7 /* CustomShapeLayer.swift */; }; 42 | CE972EFD1D044B8200EDE4A7 /* ShapeStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE972EE81D044B7E00EDE4A7 /* ShapeStyle.swift */; }; 43 | CE972EFE1D044B8200EDE4A7 /* SnappingStepper+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE972EE91D044B7E00EDE4A7 /* SnappingStepper+Internal.swift */; }; 44 | CE972EFF1D044B8200EDE4A7 /* SnappingStepper.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE972EEA1D044B7E00EDE4A7 /* SnappingStepper.swift */; }; 45 | CE972F001D044B8200EDE4A7 /* SnappingStepperBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE972EEB1D044B7E00EDE4A7 /* SnappingStepperBehavior.swift */; }; 46 | CE972F011D044B8200EDE4A7 /* StyledLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE972EEC1D044B7E00EDE4A7 /* StyledLabel.swift */; }; 47 | CE972F021D044B8200EDE4A7 /* UITouchGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE972EED1D044B7E00EDE4A7 /* UITouchGestureRecognizer.swift */; }; 48 | CE972F041D0453D800EDE4A7 /* AutoRepeatHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE972F031D0453D800EDE4A7 /* AutoRepeatHelper.swift */; }; 49 | CE972F051D0453D800EDE4A7 /* AutoRepeatHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE972F031D0453D800EDE4A7 /* AutoRepeatHelper.swift */; }; 50 | CE972F061D0453D800EDE4A7 /* AutoRepeatHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE972F031D0453D800EDE4A7 /* AutoRepeatHelper.swift */; }; 51 | CEB21D691B18C18A003E9EEC /* SnappingStepperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEB21D681B18C18A003E9EEC /* SnappingStepperTests.swift */; }; 52 | /* End PBXBuildFile section */ 53 | 54 | /* Begin PBXContainerItemProxy section */ 55 | CE8FFEC21BAB22C100D43F38 /* PBXContainerItemProxy */ = { 56 | isa = PBXContainerItemProxy; 57 | containerPortal = CE6F72301AF513810019CB55 /* Project object */; 58 | proxyType = 1; 59 | remoteGlobalIDString = CE8FFEAE1BAB22C100D43F38; 60 | remoteInfo = SnappingStepper; 61 | }; 62 | CEB21D6E1B18E480003E9EEC /* PBXContainerItemProxy */ = { 63 | isa = PBXContainerItemProxy; 64 | containerPortal = CE6F72301AF513810019CB55 /* Project object */; 65 | proxyType = 1; 66 | remoteGlobalIDString = CE6F72371AF513810019CB55; 67 | remoteInfo = SnappingStepperExample; 68 | }; 69 | /* End PBXContainerItemProxy section */ 70 | 71 | /* Begin PBXCopyFilesBuildPhase section */ 72 | CE8FFEC91BAB22C100D43F38 /* Embed Frameworks */ = { 73 | isa = PBXCopyFilesBuildPhase; 74 | buildActionMask = 2147483647; 75 | dstPath = ""; 76 | dstSubfolderSpec = 10; 77 | files = ( 78 | CE8FFEC51BAB22C100D43F38 /* SnappingStepper.framework in Embed Frameworks */, 79 | ); 80 | name = "Embed Frameworks"; 81 | runOnlyForDeploymentPostprocessing = 0; 82 | }; 83 | /* End PBXCopyFilesBuildPhase section */ 84 | 85 | /* Begin PBXFileReference section */ 86 | 09E3430E9EA28A5D0C4ED262 /* Pods_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 87 | 3B63928ED40BD6C1AAE23668 /* Pods_UITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_UITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 88 | 449D06ECFFEC0DD8C4DA6EF2 /* Pods_SnappingStepperExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SnappingStepperExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 89 | 5B1EFC7D1D71932E0040AD50 /* StyledControlDirection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StyledControlDirection.swift; sourceTree = ""; }; 90 | 7C4C30B228B621354D490313 /* Pods-Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-Tests/Pods-Tests.release.xcconfig"; sourceTree = ""; }; 91 | 8B6D05BEEE35E75A880B7639 /* Pods-SnappingStepperExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SnappingStepperExample.release.xcconfig"; path = "Pods/Target Support Files/Pods-SnappingStepperExample/Pods-SnappingStepperExample.release.xcconfig"; sourceTree = ""; }; 92 | 921B58F6F24292F873D6FE4A /* Pods_SnappingStepper.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SnappingStepper.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 93 | AA5888D5090F31444D20DBDA /* Pods-SnappingStepper.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SnappingStepper.release.xcconfig"; path = "Pods/Target Support Files/Pods-SnappingStepper/Pods-SnappingStepper.release.xcconfig"; sourceTree = ""; }; 94 | B096075174A29C2DEC848639 /* Pods-SnappingStepper.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SnappingStepper.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SnappingStepper/Pods-SnappingStepper.debug.xcconfig"; sourceTree = ""; }; 95 | B2E3DBDEDC6BE1779FC7B5FA /* Pods-SnappingStepperExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SnappingStepperExample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SnappingStepperExample/Pods-SnappingStepperExample.debug.xcconfig"; sourceTree = ""; }; 96 | CE132D671D0880EB00CA6DB5 /* UIBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIBuilder.swift; sourceTree = ""; }; 97 | CE6F72381AF513810019CB55 /* SnappingStepperExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SnappingStepperExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 98 | CE6F723C1AF513810019CB55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 99 | CE6F723D1AF513810019CB55 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 100 | CE6F723F1AF513810019CB55 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 101 | CE6F72421AF513810019CB55 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 102 | CE6F72441AF513810019CB55 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 103 | CE6F72471AF513810019CB55 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 104 | CE8FFEAF1BAB22C100D43F38 /* SnappingStepper.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SnappingStepper.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 105 | CE8FFEB11BAB22C100D43F38 /* SnappingStepper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SnappingStepper.h; sourceTree = ""; }; 106 | CE8FFEB31BAB22C100D43F38 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 107 | CE972EE71D044B7E00EDE4A7 /* CustomShapeLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomShapeLayer.swift; sourceTree = ""; }; 108 | CE972EE81D044B7E00EDE4A7 /* ShapeStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShapeStyle.swift; sourceTree = ""; }; 109 | CE972EE91D044B7E00EDE4A7 /* SnappingStepper+Internal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SnappingStepper+Internal.swift"; sourceTree = ""; }; 110 | CE972EEA1D044B7E00EDE4A7 /* SnappingStepper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnappingStepper.swift; sourceTree = ""; }; 111 | CE972EEB1D044B7E00EDE4A7 /* SnappingStepperBehavior.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnappingStepperBehavior.swift; sourceTree = ""; }; 112 | CE972EEC1D044B7E00EDE4A7 /* StyledLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StyledLabel.swift; sourceTree = ""; }; 113 | CE972EED1D044B7E00EDE4A7 /* UITouchGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITouchGestureRecognizer.swift; sourceTree = ""; }; 114 | CE972F031D0453D800EDE4A7 /* AutoRepeatHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoRepeatHelper.swift; sourceTree = ""; }; 115 | CEB21D5F1B18C169003E9EEC /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 116 | CEB21D621B18C169003E9EEC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 117 | CEB21D681B18C18A003E9EEC /* SnappingStepperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SnappingStepperTests.swift; path = ../../Tests/SnappingStepperTests.swift; sourceTree = ""; }; 118 | FA07CA8AF812FD143261A66A /* Pods-Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Tests/Pods-Tests.debug.xcconfig"; sourceTree = ""; }; 119 | /* End PBXFileReference section */ 120 | 121 | /* Begin PBXFrameworksBuildPhase section */ 122 | CE6F72351AF513810019CB55 /* Frameworks */ = { 123 | isa = PBXFrameworksBuildPhase; 124 | buildActionMask = 2147483647; 125 | files = ( 126 | CE8FFEC41BAB22C100D43F38 /* SnappingStepper.framework in Frameworks */, 127 | 01AA9210AD07C825DAE1F607 /* Pods_SnappingStepperExample.framework in Frameworks */, 128 | ); 129 | runOnlyForDeploymentPostprocessing = 0; 130 | }; 131 | CE8FFEAB1BAB22C100D43F38 /* Frameworks */ = { 132 | isa = PBXFrameworksBuildPhase; 133 | buildActionMask = 2147483647; 134 | files = ( 135 | 5E1F7E9CB92461FE26B43DF2 /* Pods_SnappingStepper.framework in Frameworks */, 136 | ); 137 | runOnlyForDeploymentPostprocessing = 0; 138 | }; 139 | CEB21D5C1B18C169003E9EEC /* Frameworks */ = { 140 | isa = PBXFrameworksBuildPhase; 141 | buildActionMask = 2147483647; 142 | files = ( 143 | 0D1DA54DFA51DA8D2A48E14A /* Pods_Tests.framework in Frameworks */, 144 | ); 145 | runOnlyForDeploymentPostprocessing = 0; 146 | }; 147 | /* End PBXFrameworksBuildPhase section */ 148 | 149 | /* Begin PBXGroup section */ 150 | 5B1EFC7C1D7192FE0040AD50 /* ThirdParty */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | 5B1EFC7D1D71932E0040AD50 /* StyledControlDirection.swift */, 154 | ); 155 | name = ThirdParty; 156 | sourceTree = ""; 157 | }; 158 | B7F47D86834F3F549B39F8F8 /* Pods */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | B2E3DBDEDC6BE1779FC7B5FA /* Pods-SnappingStepperExample.debug.xcconfig */, 162 | 8B6D05BEEE35E75A880B7639 /* Pods-SnappingStepperExample.release.xcconfig */, 163 | FA07CA8AF812FD143261A66A /* Pods-Tests.debug.xcconfig */, 164 | 7C4C30B228B621354D490313 /* Pods-Tests.release.xcconfig */, 165 | B096075174A29C2DEC848639 /* Pods-SnappingStepper.debug.xcconfig */, 166 | AA5888D5090F31444D20DBDA /* Pods-SnappingStepper.release.xcconfig */, 167 | ); 168 | name = Pods; 169 | sourceTree = ""; 170 | }; 171 | CE6F722F1AF513810019CB55 = { 172 | isa = PBXGroup; 173 | children = ( 174 | CE972EE61D044B7E00EDE4A7 /* Sources */, 175 | CE6F723A1AF513810019CB55 /* SnappingStepperExample */, 176 | CEB21D601B18C169003E9EEC /* Tests */, 177 | CE8FFEB01BAB22C100D43F38 /* SnappingStepper */, 178 | CE6F72391AF513810019CB55 /* Products */, 179 | B7F47D86834F3F549B39F8F8 /* Pods */, 180 | FE410316A4FAC960A0982048 /* Frameworks */, 181 | ); 182 | sourceTree = ""; 183 | }; 184 | CE6F72391AF513810019CB55 /* Products */ = { 185 | isa = PBXGroup; 186 | children = ( 187 | CE6F72381AF513810019CB55 /* SnappingStepperExample.app */, 188 | CEB21D5F1B18C169003E9EEC /* Tests.xctest */, 189 | CE8FFEAF1BAB22C100D43F38 /* SnappingStepper.framework */, 190 | ); 191 | name = Products; 192 | sourceTree = ""; 193 | }; 194 | CE6F723A1AF513810019CB55 /* SnappingStepperExample */ = { 195 | isa = PBXGroup; 196 | children = ( 197 | CE6F723D1AF513810019CB55 /* AppDelegate.swift */, 198 | CE6F723F1AF513810019CB55 /* ViewController.swift */, 199 | CE6F72411AF513810019CB55 /* Main.storyboard */, 200 | CE6F72441AF513810019CB55 /* Images.xcassets */, 201 | CE6F72461AF513810019CB55 /* LaunchScreen.xib */, 202 | CE6F723B1AF513810019CB55 /* Supporting Files */, 203 | ); 204 | path = SnappingStepperExample; 205 | sourceTree = ""; 206 | }; 207 | CE6F723B1AF513810019CB55 /* Supporting Files */ = { 208 | isa = PBXGroup; 209 | children = ( 210 | CE6F723C1AF513810019CB55 /* Info.plist */, 211 | ); 212 | name = "Supporting Files"; 213 | sourceTree = ""; 214 | }; 215 | CE8FFEB01BAB22C100D43F38 /* SnappingStepper */ = { 216 | isa = PBXGroup; 217 | children = ( 218 | CE8FFEB11BAB22C100D43F38 /* SnappingStepper.h */, 219 | CE8FFEB31BAB22C100D43F38 /* Info.plist */, 220 | ); 221 | path = SnappingStepper; 222 | sourceTree = ""; 223 | }; 224 | CE972EE61D044B7E00EDE4A7 /* Sources */ = { 225 | isa = PBXGroup; 226 | children = ( 227 | 5B1EFC7C1D7192FE0040AD50 /* ThirdParty */, 228 | CE972F031D0453D800EDE4A7 /* AutoRepeatHelper.swift */, 229 | CE972EE71D044B7E00EDE4A7 /* CustomShapeLayer.swift */, 230 | CE972EE81D044B7E00EDE4A7 /* ShapeStyle.swift */, 231 | CE972EE91D044B7E00EDE4A7 /* SnappingStepper+Internal.swift */, 232 | CE972EEA1D044B7E00EDE4A7 /* SnappingStepper.swift */, 233 | CE972EEB1D044B7E00EDE4A7 /* SnappingStepperBehavior.swift */, 234 | CE972EEC1D044B7E00EDE4A7 /* StyledLabel.swift */, 235 | CE132D671D0880EB00CA6DB5 /* UIBuilder.swift */, 236 | CE972EED1D044B7E00EDE4A7 /* UITouchGestureRecognizer.swift */, 237 | ); 238 | name = Sources; 239 | path = ../Sources; 240 | sourceTree = ""; 241 | }; 242 | CEB21D601B18C169003E9EEC /* Tests */ = { 243 | isa = PBXGroup; 244 | children = ( 245 | CEB21D681B18C18A003E9EEC /* SnappingStepperTests.swift */, 246 | CEB21D611B18C169003E9EEC /* Supporting Files */, 247 | ); 248 | path = Tests; 249 | sourceTree = ""; 250 | }; 251 | CEB21D611B18C169003E9EEC /* Supporting Files */ = { 252 | isa = PBXGroup; 253 | children = ( 254 | CEB21D621B18C169003E9EEC /* Info.plist */, 255 | ); 256 | name = "Supporting Files"; 257 | sourceTree = ""; 258 | }; 259 | FE410316A4FAC960A0982048 /* Frameworks */ = { 260 | isa = PBXGroup; 261 | children = ( 262 | 449D06ECFFEC0DD8C4DA6EF2 /* Pods_SnappingStepperExample.framework */, 263 | 09E3430E9EA28A5D0C4ED262 /* Pods_Tests.framework */, 264 | 3B63928ED40BD6C1AAE23668 /* Pods_UITests.framework */, 265 | 921B58F6F24292F873D6FE4A /* Pods_SnappingStepper.framework */, 266 | ); 267 | name = Frameworks; 268 | sourceTree = ""; 269 | }; 270 | /* End PBXGroup section */ 271 | 272 | /* Begin PBXHeadersBuildPhase section */ 273 | CE8FFEAC1BAB22C100D43F38 /* Headers */ = { 274 | isa = PBXHeadersBuildPhase; 275 | buildActionMask = 2147483647; 276 | files = ( 277 | CE8FFEB21BAB22C100D43F38 /* SnappingStepper.h in Headers */, 278 | ); 279 | runOnlyForDeploymentPostprocessing = 0; 280 | }; 281 | /* End PBXHeadersBuildPhase section */ 282 | 283 | /* Begin PBXNativeTarget section */ 284 | CE6F72371AF513810019CB55 /* SnappingStepperExample */ = { 285 | isa = PBXNativeTarget; 286 | buildConfigurationList = CE6F72571AF513810019CB55 /* Build configuration list for PBXNativeTarget "SnappingStepperExample" */; 287 | buildPhases = ( 288 | 7CA4F1F5B55B4EA73B14C706 /* [CP] Check Pods Manifest.lock */, 289 | CE6F72341AF513810019CB55 /* Sources */, 290 | CE6F72351AF513810019CB55 /* Frameworks */, 291 | CE6F72361AF513810019CB55 /* Resources */, 292 | CE8FFEC91BAB22C100D43F38 /* Embed Frameworks */, 293 | 100D5242A835D0DF21B997AB /* [CP] Embed Pods Frameworks */, 294 | ); 295 | buildRules = ( 296 | ); 297 | dependencies = ( 298 | CE8FFEC31BAB22C100D43F38 /* PBXTargetDependency */, 299 | ); 300 | name = SnappingStepperExample; 301 | productName = SnappingStepperExample; 302 | productReference = CE6F72381AF513810019CB55 /* SnappingStepperExample.app */; 303 | productType = "com.apple.product-type.application"; 304 | }; 305 | CE8FFEAE1BAB22C100D43F38 /* SnappingStepper */ = { 306 | isa = PBXNativeTarget; 307 | buildConfigurationList = CE8FFEC61BAB22C100D43F38 /* Build configuration list for PBXNativeTarget "SnappingStepper" */; 308 | buildPhases = ( 309 | 5A8FDD7C7C23F1CB73E73FAD /* [CP] Check Pods Manifest.lock */, 310 | CE8FFEAA1BAB22C100D43F38 /* Sources */, 311 | CE8FFEAB1BAB22C100D43F38 /* Frameworks */, 312 | CE8FFEAC1BAB22C100D43F38 /* Headers */, 313 | CE8FFEAD1BAB22C100D43F38 /* Resources */, 314 | ); 315 | buildRules = ( 316 | ); 317 | dependencies = ( 318 | ); 319 | name = SnappingStepper; 320 | productName = SnappingStepper; 321 | productReference = CE8FFEAF1BAB22C100D43F38 /* SnappingStepper.framework */; 322 | productType = "com.apple.product-type.framework"; 323 | }; 324 | CEB21D5E1B18C169003E9EEC /* Tests */ = { 325 | isa = PBXNativeTarget; 326 | buildConfigurationList = CEB21D651B18C169003E9EEC /* Build configuration list for PBXNativeTarget "Tests" */; 327 | buildPhases = ( 328 | AEABFEC876ECB92809E6C351 /* [CP] Check Pods Manifest.lock */, 329 | CEB21D5B1B18C169003E9EEC /* Sources */, 330 | CEB21D5C1B18C169003E9EEC /* Frameworks */, 331 | CEB21D5D1B18C169003E9EEC /* Resources */, 332 | 57B8D188DD6B7CA8FF787373 /* [CP] Embed Pods Frameworks */, 333 | ); 334 | buildRules = ( 335 | ); 336 | dependencies = ( 337 | CEB21D6F1B18E480003E9EEC /* PBXTargetDependency */, 338 | ); 339 | name = Tests; 340 | productName = Tests; 341 | productReference = CEB21D5F1B18C169003E9EEC /* Tests.xctest */; 342 | productType = "com.apple.product-type.bundle.unit-test"; 343 | }; 344 | /* End PBXNativeTarget section */ 345 | 346 | /* Begin PBXProject section */ 347 | CE6F72301AF513810019CB55 /* Project object */ = { 348 | isa = PBXProject; 349 | attributes = { 350 | LastSwiftMigration = 0700; 351 | LastSwiftUpdateCheck = 0710; 352 | LastUpgradeCheck = 1000; 353 | ORGANIZATIONNAME = "Yannick Loriot"; 354 | TargetAttributes = { 355 | CE6F72371AF513810019CB55 = { 356 | CreatedOnToolsVersion = 6.3; 357 | LastSwiftMigration = 1000; 358 | }; 359 | CE8FFEAE1BAB22C100D43F38 = { 360 | CreatedOnToolsVersion = 7.0; 361 | LastSwiftMigration = 1000; 362 | }; 363 | CEB21D5E1B18C169003E9EEC = { 364 | CreatedOnToolsVersion = 6.3.2; 365 | TestTargetID = CE6F72371AF513810019CB55; 366 | }; 367 | }; 368 | }; 369 | buildConfigurationList = CE6F72331AF513810019CB55 /* Build configuration list for PBXProject "SnappingStepperExample" */; 370 | compatibilityVersion = "Xcode 3.2"; 371 | developmentRegion = English; 372 | hasScannedForEncodings = 0; 373 | knownRegions = ( 374 | en, 375 | Base, 376 | ); 377 | mainGroup = CE6F722F1AF513810019CB55; 378 | productRefGroup = CE6F72391AF513810019CB55 /* Products */; 379 | projectDirPath = ""; 380 | projectRoot = ""; 381 | targets = ( 382 | CE6F72371AF513810019CB55 /* SnappingStepperExample */, 383 | CE8FFEAE1BAB22C100D43F38 /* SnappingStepper */, 384 | CEB21D5E1B18C169003E9EEC /* Tests */, 385 | ); 386 | }; 387 | /* End PBXProject section */ 388 | 389 | /* Begin PBXResourcesBuildPhase section */ 390 | CE6F72361AF513810019CB55 /* Resources */ = { 391 | isa = PBXResourcesBuildPhase; 392 | buildActionMask = 2147483647; 393 | files = ( 394 | CE6F72431AF513810019CB55 /* Main.storyboard in Resources */, 395 | CE6F72481AF513810019CB55 /* LaunchScreen.xib in Resources */, 396 | CE6F72451AF513810019CB55 /* Images.xcassets in Resources */, 397 | ); 398 | runOnlyForDeploymentPostprocessing = 0; 399 | }; 400 | CE8FFEAD1BAB22C100D43F38 /* Resources */ = { 401 | isa = PBXResourcesBuildPhase; 402 | buildActionMask = 2147483647; 403 | files = ( 404 | ); 405 | runOnlyForDeploymentPostprocessing = 0; 406 | }; 407 | CEB21D5D1B18C169003E9EEC /* Resources */ = { 408 | isa = PBXResourcesBuildPhase; 409 | buildActionMask = 2147483647; 410 | files = ( 411 | ); 412 | runOnlyForDeploymentPostprocessing = 0; 413 | }; 414 | /* End PBXResourcesBuildPhase section */ 415 | 416 | /* Begin PBXShellScriptBuildPhase section */ 417 | 100D5242A835D0DF21B997AB /* [CP] Embed Pods Frameworks */ = { 418 | isa = PBXShellScriptBuildPhase; 419 | buildActionMask = 2147483647; 420 | files = ( 421 | ); 422 | inputPaths = ( 423 | "${PODS_ROOT}/Target Support Files/Pods-SnappingStepperExample/Pods-SnappingStepperExample-frameworks.sh", 424 | "${BUILT_PRODUCTS_DIR}/DynamicColor/DynamicColor.framework", 425 | ); 426 | name = "[CP] Embed Pods Frameworks"; 427 | outputPaths = ( 428 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DynamicColor.framework", 429 | ); 430 | runOnlyForDeploymentPostprocessing = 0; 431 | shellPath = /bin/sh; 432 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SnappingStepperExample/Pods-SnappingStepperExample-frameworks.sh\"\n"; 433 | showEnvVarsInLog = 0; 434 | }; 435 | 57B8D188DD6B7CA8FF787373 /* [CP] Embed Pods Frameworks */ = { 436 | isa = PBXShellScriptBuildPhase; 437 | buildActionMask = 2147483647; 438 | files = ( 439 | ); 440 | inputPaths = ( 441 | "${PODS_ROOT}/Target Support Files/Pods-Tests/Pods-Tests-frameworks.sh", 442 | "${BUILT_PRODUCTS_DIR}/DynamicColor/DynamicColor.framework", 443 | ); 444 | name = "[CP] Embed Pods Frameworks"; 445 | outputPaths = ( 446 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DynamicColor.framework", 447 | ); 448 | runOnlyForDeploymentPostprocessing = 0; 449 | shellPath = /bin/sh; 450 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Tests/Pods-Tests-frameworks.sh\"\n"; 451 | showEnvVarsInLog = 0; 452 | }; 453 | 5A8FDD7C7C23F1CB73E73FAD /* [CP] Check Pods Manifest.lock */ = { 454 | isa = PBXShellScriptBuildPhase; 455 | buildActionMask = 2147483647; 456 | files = ( 457 | ); 458 | inputPaths = ( 459 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 460 | "${PODS_ROOT}/Manifest.lock", 461 | ); 462 | name = "[CP] Check Pods Manifest.lock"; 463 | outputPaths = ( 464 | "$(DERIVED_FILE_DIR)/Pods-SnappingStepper-checkManifestLockResult.txt", 465 | ); 466 | runOnlyForDeploymentPostprocessing = 0; 467 | shellPath = /bin/sh; 468 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 469 | showEnvVarsInLog = 0; 470 | }; 471 | 7CA4F1F5B55B4EA73B14C706 /* [CP] Check Pods Manifest.lock */ = { 472 | isa = PBXShellScriptBuildPhase; 473 | buildActionMask = 2147483647; 474 | files = ( 475 | ); 476 | inputPaths = ( 477 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 478 | "${PODS_ROOT}/Manifest.lock", 479 | ); 480 | name = "[CP] Check Pods Manifest.lock"; 481 | outputPaths = ( 482 | "$(DERIVED_FILE_DIR)/Pods-SnappingStepperExample-checkManifestLockResult.txt", 483 | ); 484 | runOnlyForDeploymentPostprocessing = 0; 485 | shellPath = /bin/sh; 486 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 487 | showEnvVarsInLog = 0; 488 | }; 489 | AEABFEC876ECB92809E6C351 /* [CP] Check Pods Manifest.lock */ = { 490 | isa = PBXShellScriptBuildPhase; 491 | buildActionMask = 2147483647; 492 | files = ( 493 | ); 494 | inputPaths = ( 495 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 496 | "${PODS_ROOT}/Manifest.lock", 497 | ); 498 | name = "[CP] Check Pods Manifest.lock"; 499 | outputPaths = ( 500 | "$(DERIVED_FILE_DIR)/Pods-Tests-checkManifestLockResult.txt", 501 | ); 502 | runOnlyForDeploymentPostprocessing = 0; 503 | shellPath = /bin/sh; 504 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 505 | showEnvVarsInLog = 0; 506 | }; 507 | /* End PBXShellScriptBuildPhase section */ 508 | 509 | /* Begin PBXSourcesBuildPhase section */ 510 | CE6F72341AF513810019CB55 /* Sources */ = { 511 | isa = PBXSourcesBuildPhase; 512 | buildActionMask = 2147483647; 513 | files = ( 514 | CE6F72401AF513810019CB55 /* ViewController.swift in Sources */, 515 | CE972EF81D044B7E00EDE4A7 /* StyledLabel.swift in Sources */, 516 | 5B1EFC7E1D71932E0040AD50 /* StyledControlDirection.swift in Sources */, 517 | CE972EF41D044B7E00EDE4A7 /* SnappingStepper.swift in Sources */, 518 | CE972EF01D044B7E00EDE4A7 /* ShapeStyle.swift in Sources */, 519 | CE972EEE1D044B7E00EDE4A7 /* CustomShapeLayer.swift in Sources */, 520 | CE132D681D0880EB00CA6DB5 /* UIBuilder.swift in Sources */, 521 | CE6F723E1AF513810019CB55 /* AppDelegate.swift in Sources */, 522 | CE972EFA1D044B7E00EDE4A7 /* UITouchGestureRecognizer.swift in Sources */, 523 | CE972F041D0453D800EDE4A7 /* AutoRepeatHelper.swift in Sources */, 524 | CE972EF61D044B7E00EDE4A7 /* SnappingStepperBehavior.swift in Sources */, 525 | CE972EF21D044B7E00EDE4A7 /* SnappingStepper+Internal.swift in Sources */, 526 | ); 527 | runOnlyForDeploymentPostprocessing = 0; 528 | }; 529 | CE8FFEAA1BAB22C100D43F38 /* Sources */ = { 530 | isa = PBXSourcesBuildPhase; 531 | buildActionMask = 2147483647; 532 | files = ( 533 | 5B1EFC7F1D71932E0040AD50 /* StyledControlDirection.swift in Sources */, 534 | CE972EEF1D044B7E00EDE4A7 /* CustomShapeLayer.swift in Sources */, 535 | CE972EF91D044B7E00EDE4A7 /* StyledLabel.swift in Sources */, 536 | CE972EF31D044B7E00EDE4A7 /* SnappingStepper+Internal.swift in Sources */, 537 | CE972EF11D044B7E00EDE4A7 /* ShapeStyle.swift in Sources */, 538 | CE972EFB1D044B7E00EDE4A7 /* UITouchGestureRecognizer.swift in Sources */, 539 | CE132D691D0880EB00CA6DB5 /* UIBuilder.swift in Sources */, 540 | CE972F051D0453D800EDE4A7 /* AutoRepeatHelper.swift in Sources */, 541 | CE972EF51D044B7E00EDE4A7 /* SnappingStepper.swift in Sources */, 542 | CE972EF71D044B7E00EDE4A7 /* SnappingStepperBehavior.swift in Sources */, 543 | ); 544 | runOnlyForDeploymentPostprocessing = 0; 545 | }; 546 | CEB21D5B1B18C169003E9EEC /* Sources */ = { 547 | isa = PBXSourcesBuildPhase; 548 | buildActionMask = 2147483647; 549 | files = ( 550 | CE972F011D044B8200EDE4A7 /* StyledLabel.swift in Sources */, 551 | CE972EFF1D044B8200EDE4A7 /* SnappingStepper.swift in Sources */, 552 | CE972EFD1D044B8200EDE4A7 /* ShapeStyle.swift in Sources */, 553 | CE972EFC1D044B8200EDE4A7 /* CustomShapeLayer.swift in Sources */, 554 | 5B1EFC801D71932E0040AD50 /* StyledControlDirection.swift in Sources */, 555 | CEB21D691B18C18A003E9EEC /* SnappingStepperTests.swift in Sources */, 556 | CE132D6A1D08810C00CA6DB5 /* UIBuilder.swift in Sources */, 557 | CE972F021D044B8200EDE4A7 /* UITouchGestureRecognizer.swift in Sources */, 558 | CE972F061D0453D800EDE4A7 /* AutoRepeatHelper.swift in Sources */, 559 | CE972F001D044B8200EDE4A7 /* SnappingStepperBehavior.swift in Sources */, 560 | CE972EFE1D044B8200EDE4A7 /* SnappingStepper+Internal.swift in Sources */, 561 | ); 562 | runOnlyForDeploymentPostprocessing = 0; 563 | }; 564 | /* End PBXSourcesBuildPhase section */ 565 | 566 | /* Begin PBXTargetDependency section */ 567 | CE8FFEC31BAB22C100D43F38 /* PBXTargetDependency */ = { 568 | isa = PBXTargetDependency; 569 | target = CE8FFEAE1BAB22C100D43F38 /* SnappingStepper */; 570 | targetProxy = CE8FFEC21BAB22C100D43F38 /* PBXContainerItemProxy */; 571 | }; 572 | CEB21D6F1B18E480003E9EEC /* PBXTargetDependency */ = { 573 | isa = PBXTargetDependency; 574 | target = CE6F72371AF513810019CB55 /* SnappingStepperExample */; 575 | targetProxy = CEB21D6E1B18E480003E9EEC /* PBXContainerItemProxy */; 576 | }; 577 | /* End PBXTargetDependency section */ 578 | 579 | /* Begin PBXVariantGroup section */ 580 | CE6F72411AF513810019CB55 /* Main.storyboard */ = { 581 | isa = PBXVariantGroup; 582 | children = ( 583 | CE6F72421AF513810019CB55 /* Base */, 584 | ); 585 | name = Main.storyboard; 586 | sourceTree = ""; 587 | }; 588 | CE6F72461AF513810019CB55 /* LaunchScreen.xib */ = { 589 | isa = PBXVariantGroup; 590 | children = ( 591 | CE6F72471AF513810019CB55 /* Base */, 592 | ); 593 | name = LaunchScreen.xib; 594 | sourceTree = ""; 595 | }; 596 | /* End PBXVariantGroup section */ 597 | 598 | /* Begin XCBuildConfiguration section */ 599 | CE6F72551AF513810019CB55 /* Debug */ = { 600 | isa = XCBuildConfiguration; 601 | buildSettings = { 602 | ALWAYS_SEARCH_USER_PATHS = NO; 603 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 604 | CLANG_CXX_LIBRARY = "libc++"; 605 | CLANG_ENABLE_MODULES = YES; 606 | CLANG_ENABLE_OBJC_ARC = YES; 607 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 608 | CLANG_WARN_BOOL_CONVERSION = YES; 609 | CLANG_WARN_COMMA = YES; 610 | CLANG_WARN_CONSTANT_CONVERSION = YES; 611 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 612 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 613 | CLANG_WARN_EMPTY_BODY = YES; 614 | CLANG_WARN_ENUM_CONVERSION = YES; 615 | CLANG_WARN_INFINITE_RECURSION = YES; 616 | CLANG_WARN_INT_CONVERSION = YES; 617 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 618 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 619 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 620 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 621 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 622 | CLANG_WARN_STRICT_PROTOTYPES = YES; 623 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 624 | CLANG_WARN_UNREACHABLE_CODE = YES; 625 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 626 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 627 | COPY_PHASE_STRIP = NO; 628 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 629 | ENABLE_STRICT_OBJC_MSGSEND = YES; 630 | ENABLE_TESTABILITY = YES; 631 | GCC_C_LANGUAGE_STANDARD = gnu99; 632 | GCC_DYNAMIC_NO_PIC = NO; 633 | GCC_NO_COMMON_BLOCKS = YES; 634 | GCC_OPTIMIZATION_LEVEL = 0; 635 | GCC_PREPROCESSOR_DEFINITIONS = ( 636 | "DEBUG=1", 637 | "$(inherited)", 638 | ); 639 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 640 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 641 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 642 | GCC_WARN_UNDECLARED_SELECTOR = YES; 643 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 644 | GCC_WARN_UNUSED_FUNCTION = YES; 645 | GCC_WARN_UNUSED_VARIABLE = YES; 646 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 647 | MTL_ENABLE_DEBUG_INFO = YES; 648 | ONLY_ACTIVE_ARCH = YES; 649 | SDKROOT = iphoneos; 650 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 651 | SWIFT_VERSION = 4.2; 652 | TARGETED_DEVICE_FAMILY = "1,2"; 653 | }; 654 | name = Debug; 655 | }; 656 | CE6F72561AF513810019CB55 /* Release */ = { 657 | isa = XCBuildConfiguration; 658 | buildSettings = { 659 | ALWAYS_SEARCH_USER_PATHS = NO; 660 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 661 | CLANG_CXX_LIBRARY = "libc++"; 662 | CLANG_ENABLE_MODULES = YES; 663 | CLANG_ENABLE_OBJC_ARC = YES; 664 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 665 | CLANG_WARN_BOOL_CONVERSION = YES; 666 | CLANG_WARN_COMMA = YES; 667 | CLANG_WARN_CONSTANT_CONVERSION = YES; 668 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 669 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 670 | CLANG_WARN_EMPTY_BODY = YES; 671 | CLANG_WARN_ENUM_CONVERSION = YES; 672 | CLANG_WARN_INFINITE_RECURSION = YES; 673 | CLANG_WARN_INT_CONVERSION = YES; 674 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 675 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 676 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 677 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 678 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 679 | CLANG_WARN_STRICT_PROTOTYPES = YES; 680 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 681 | CLANG_WARN_UNREACHABLE_CODE = YES; 682 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 683 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 684 | COPY_PHASE_STRIP = NO; 685 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 686 | ENABLE_NS_ASSERTIONS = NO; 687 | ENABLE_STRICT_OBJC_MSGSEND = YES; 688 | GCC_C_LANGUAGE_STANDARD = gnu99; 689 | GCC_NO_COMMON_BLOCKS = YES; 690 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 691 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 692 | GCC_WARN_UNDECLARED_SELECTOR = YES; 693 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 694 | GCC_WARN_UNUSED_FUNCTION = YES; 695 | GCC_WARN_UNUSED_VARIABLE = YES; 696 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 697 | MTL_ENABLE_DEBUG_INFO = NO; 698 | SDKROOT = iphoneos; 699 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 700 | SWIFT_VERSION = 4.2; 701 | TARGETED_DEVICE_FAMILY = "1,2"; 702 | VALIDATE_PRODUCT = YES; 703 | }; 704 | name = Release; 705 | }; 706 | CE6F72581AF513810019CB55 /* Debug */ = { 707 | isa = XCBuildConfiguration; 708 | baseConfigurationReference = B2E3DBDEDC6BE1779FC7B5FA /* Pods-SnappingStepperExample.debug.xcconfig */; 709 | buildSettings = { 710 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 711 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 712 | INFOPLIST_FILE = SnappingStepperExample/Info.plist; 713 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 714 | PRODUCT_BUNDLE_IDENTIFIER = "com.yannickloriot.$(PRODUCT_NAME:rfc1034identifier)"; 715 | PRODUCT_NAME = "$(TARGET_NAME)"; 716 | SWIFT_VERSION = 4.2; 717 | }; 718 | name = Debug; 719 | }; 720 | CE6F72591AF513810019CB55 /* Release */ = { 721 | isa = XCBuildConfiguration; 722 | baseConfigurationReference = 8B6D05BEEE35E75A880B7639 /* Pods-SnappingStepperExample.release.xcconfig */; 723 | buildSettings = { 724 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 725 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 726 | INFOPLIST_FILE = SnappingStepperExample/Info.plist; 727 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 728 | PRODUCT_BUNDLE_IDENTIFIER = "com.yannickloriot.$(PRODUCT_NAME:rfc1034identifier)"; 729 | PRODUCT_NAME = "$(TARGET_NAME)"; 730 | SWIFT_VERSION = 4.2; 731 | }; 732 | name = Release; 733 | }; 734 | CE8FFEC71BAB22C100D43F38 /* Debug */ = { 735 | isa = XCBuildConfiguration; 736 | baseConfigurationReference = B096075174A29C2DEC848639 /* Pods-SnappingStepper.debug.xcconfig */; 737 | buildSettings = { 738 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 739 | CURRENT_PROJECT_VERSION = 1; 740 | DEBUG_INFORMATION_FORMAT = dwarf; 741 | DEFINES_MODULE = YES; 742 | DYLIB_COMPATIBILITY_VERSION = 1; 743 | DYLIB_CURRENT_VERSION = 1; 744 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 745 | INFOPLIST_FILE = SnappingStepper/Info.plist; 746 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 747 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 748 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 749 | PRODUCT_BUNDLE_IDENTIFIER = com.yannickloriot.SnappingStepper; 750 | PRODUCT_NAME = "$(TARGET_NAME)"; 751 | SKIP_INSTALL = YES; 752 | SWIFT_VERSION = 4.2; 753 | VERSIONING_SYSTEM = "apple-generic"; 754 | VERSION_INFO_PREFIX = ""; 755 | }; 756 | name = Debug; 757 | }; 758 | CE8FFEC81BAB22C100D43F38 /* Release */ = { 759 | isa = XCBuildConfiguration; 760 | baseConfigurationReference = AA5888D5090F31444D20DBDA /* Pods-SnappingStepper.release.xcconfig */; 761 | buildSettings = { 762 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 763 | CURRENT_PROJECT_VERSION = 1; 764 | DEFINES_MODULE = YES; 765 | DYLIB_COMPATIBILITY_VERSION = 1; 766 | DYLIB_CURRENT_VERSION = 1; 767 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 768 | INFOPLIST_FILE = SnappingStepper/Info.plist; 769 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 770 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 771 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 772 | PRODUCT_BUNDLE_IDENTIFIER = com.yannickloriot.SnappingStepper; 773 | PRODUCT_NAME = "$(TARGET_NAME)"; 774 | SKIP_INSTALL = YES; 775 | SWIFT_VERSION = 4.2; 776 | VERSIONING_SYSTEM = "apple-generic"; 777 | VERSION_INFO_PREFIX = ""; 778 | }; 779 | name = Release; 780 | }; 781 | CEB21D661B18C169003E9EEC /* Debug */ = { 782 | isa = XCBuildConfiguration; 783 | baseConfigurationReference = FA07CA8AF812FD143261A66A /* Pods-Tests.debug.xcconfig */; 784 | buildSettings = { 785 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 786 | FRAMEWORK_SEARCH_PATHS = ( 787 | "$(SDKROOT)/Developer/Library/Frameworks", 788 | "$(inherited)", 789 | ); 790 | GCC_GENERATE_TEST_COVERAGE_FILES = YES; 791 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; 792 | GCC_PREPROCESSOR_DEFINITIONS = ( 793 | "DEBUG=1", 794 | "$(inherited)", 795 | ); 796 | INFOPLIST_FILE = Tests/Info.plist; 797 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 798 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 799 | PRODUCT_BUNDLE_IDENTIFIER = "com.yannickloriot.$(PRODUCT_NAME:rfc1034identifier)"; 800 | PRODUCT_NAME = "$(TARGET_NAME)"; 801 | SWIFT_VERSION = 4.2; 802 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SnappingStepperExample.app/SnappingStepperExample"; 803 | }; 804 | name = Debug; 805 | }; 806 | CEB21D671B18C169003E9EEC /* Release */ = { 807 | isa = XCBuildConfiguration; 808 | baseConfigurationReference = 7C4C30B228B621354D490313 /* Pods-Tests.release.xcconfig */; 809 | buildSettings = { 810 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 811 | FRAMEWORK_SEARCH_PATHS = ( 812 | "$(SDKROOT)/Developer/Library/Frameworks", 813 | "$(inherited)", 814 | ); 815 | GCC_GENERATE_TEST_COVERAGE_FILES = YES; 816 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; 817 | INFOPLIST_FILE = Tests/Info.plist; 818 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 819 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 820 | PRODUCT_BUNDLE_IDENTIFIER = "com.yannickloriot.$(PRODUCT_NAME:rfc1034identifier)"; 821 | PRODUCT_NAME = "$(TARGET_NAME)"; 822 | SWIFT_VERSION = 4.2; 823 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SnappingStepperExample.app/SnappingStepperExample"; 824 | }; 825 | name = Release; 826 | }; 827 | /* End XCBuildConfiguration section */ 828 | 829 | /* Begin XCConfigurationList section */ 830 | CE6F72331AF513810019CB55 /* Build configuration list for PBXProject "SnappingStepperExample" */ = { 831 | isa = XCConfigurationList; 832 | buildConfigurations = ( 833 | CE6F72551AF513810019CB55 /* Debug */, 834 | CE6F72561AF513810019CB55 /* Release */, 835 | ); 836 | defaultConfigurationIsVisible = 0; 837 | defaultConfigurationName = Release; 838 | }; 839 | CE6F72571AF513810019CB55 /* Build configuration list for PBXNativeTarget "SnappingStepperExample" */ = { 840 | isa = XCConfigurationList; 841 | buildConfigurations = ( 842 | CE6F72581AF513810019CB55 /* Debug */, 843 | CE6F72591AF513810019CB55 /* Release */, 844 | ); 845 | defaultConfigurationIsVisible = 0; 846 | defaultConfigurationName = Release; 847 | }; 848 | CE8FFEC61BAB22C100D43F38 /* Build configuration list for PBXNativeTarget "SnappingStepper" */ = { 849 | isa = XCConfigurationList; 850 | buildConfigurations = ( 851 | CE8FFEC71BAB22C100D43F38 /* Debug */, 852 | CE8FFEC81BAB22C100D43F38 /* Release */, 853 | ); 854 | defaultConfigurationIsVisible = 0; 855 | defaultConfigurationName = Release; 856 | }; 857 | CEB21D651B18C169003E9EEC /* Build configuration list for PBXNativeTarget "Tests" */ = { 858 | isa = XCConfigurationList; 859 | buildConfigurations = ( 860 | CEB21D661B18C169003E9EEC /* Debug */, 861 | CEB21D671B18C169003E9EEC /* Release */, 862 | ); 863 | defaultConfigurationIsVisible = 0; 864 | defaultConfigurationName = Release; 865 | }; 866 | /* End XCConfigurationList section */ 867 | }; 868 | rootObject = CE6F72301AF513810019CB55 /* Project object */; 869 | } 870 | -------------------------------------------------------------------------------- /Example/SnappingStepperExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/SnappingStepperExample.xcodeproj/xcshareddata/xcschemes/SnappingStepper.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Example/SnappingStepperExample.xcodeproj/xcshareddata/xcschemes/SnappingStepperExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 61 | 67 | 68 | 69 | 71 | 77 | 78 | 79 | 81 | 87 | 88 | 89 | 90 | 91 | 97 | 98 | 99 | 100 | 101 | 102 | 112 | 114 | 120 | 121 | 122 | 123 | 124 | 125 | 131 | 133 | 139 | 140 | 141 | 142 | 144 | 145 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /Example/SnappingStepperExample.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 54 | 55 | 56 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 79 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 98 | 100 | 101 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /Example/SnappingStepperExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SnappingStepperExample 4 | // 5 | // Created by Yannick Loriot on 02/05/15. 6 | // Copyright (c) 2015 Yannick Loriot. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // 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. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // 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. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // 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. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Example/SnappingStepperExample/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Example/SnappingStepperExample/Base.lproj/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 | 25 | 26 | 27 | 28 | 29 | 30 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 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 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /Example/SnappingStepperExample/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Example/SnappingStepperExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | 3.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarHidden 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | UIViewControllerBasedStatusBarAppearance 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Example/SnappingStepperExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SnappingStepperExample 4 | // 5 | // Created by Yannick Loriot on 02/05/15. 6 | // Copyright (c) 2015 Yannick Loriot. All rights reserved. 7 | // 8 | 9 | import DynamicColor 10 | import UIKit 11 | 12 | class ViewController: UIViewController { 13 | @IBOutlet weak var classicStepper: SnappingStepper! 14 | @IBOutlet weak var valueLabel: UILabel! 15 | 16 | @IBOutlet weak var tubeStepper: SnappingStepper! 17 | @IBOutlet weak var roundedStepper: SnappingStepper! 18 | @IBOutlet weak var customStepper: SnappingStepper! 19 | @IBOutlet weak var verticalRoundedStepper: SnappingStepper! 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | assignStepperDefaultSettings(classicStepper) 25 | 26 | assignStepperDefaultSettings(tubeStepper) 27 | tubeStepper.style = .tube 28 | tubeStepper.thumbStyle = .tube 29 | tubeStepper.backgroundColor = UIColor(hex: 0xB2DFDB) 30 | tubeStepper.thumbBackgroundColor = UIColor(hex: 0x009688) 31 | tubeStepper.hintStyle = .box 32 | 33 | assignStepperDefaultSettings(roundedStepper) 34 | roundedStepper.style = .rounded 35 | roundedStepper.thumbStyle = .rounded 36 | roundedStepper.backgroundColor = .clear 37 | roundedStepper.thumbBackgroundColor = UIColor(hex: 0xFFC107) 38 | roundedStepper.borderColor = UIColor(hex: 0xFFC107) 39 | roundedStepper.borderWidth = 0.5 40 | roundedStepper.hintStyle = .thumb 41 | 42 | assignStepperDefaultSettings(customStepper) 43 | customStepper.style = .custom(path: customDoubleArrowPath()) 44 | customStepper.thumbStyle = .thumb 45 | customStepper.backgroundColor = .clear 46 | customStepper.borderColor = UIColor(hex: 0x607D8B) 47 | customStepper.thumbBackgroundColor = UIColor(hex: 0x607D8B) 48 | customStepper.borderWidth = 0.5 49 | customStepper.hintStyle = .rounded 50 | 51 | assignStepperDefaultSettings(verticalRoundedStepper) 52 | verticalRoundedStepper.style = .rounded 53 | verticalRoundedStepper.thumbStyle = .rounded 54 | verticalRoundedStepper.backgroundColor = .clear 55 | verticalRoundedStepper.thumbBackgroundColor = UIColor(hex: 0xFFC107) 56 | verticalRoundedStepper.borderColor = UIColor(hex: 0xFFC107) 57 | verticalRoundedStepper.borderWidth = 0.5 58 | verticalRoundedStepper.hintStyle = .thumb 59 | verticalRoundedStepper.direction = .vertical 60 | } 61 | 62 | func assignStepperDefaultSettings(_ snappingStepper: SnappingStepper) { 63 | snappingStepper.symbolFont = UIFont(name: "TrebuchetMS-Bold", size: 20) 64 | snappingStepper.symbolFontColor = .black 65 | snappingStepper.backgroundColor = UIColor(hex: 0xc0392b) 66 | snappingStepper.thumbWidthRatio = 0.4 67 | snappingStepper.thumbText = nil 68 | snappingStepper.thumbFont = UIFont(name: "TrebuchetMS-Bold", size: 18) 69 | snappingStepper.thumbBackgroundColor = UIColor(hex: 0xe74c3c) 70 | snappingStepper.thumbTextColor = .black 71 | 72 | snappingStepper.continuous = true 73 | snappingStepper.autorepeat = true 74 | snappingStepper.wraps = false 75 | snappingStepper.minimumValue = 0 76 | snappingStepper.maximumValue = 1000 77 | snappingStepper.stepValue = 1 78 | } 79 | 80 | func customDoubleArrowPath() -> UIBezierPath { 81 | let da = UIBezierPath() 82 | 83 | da.move(to: CGPoint(x: 232, y: 969)) 84 | da.addLine(to: CGPoint(x: 189, y: 941)) 85 | da.addLine(to: CGPoint(x: 189, y: 955)) 86 | da.addLine(to: CGPoint(x: 62, y: 955)) 87 | da.addLine(to: CGPoint(x: 62, y: 941)) 88 | da.addLine(to: CGPoint(x: 17, y: 972)) 89 | da.addLine(to: CGPoint(x: 62, y: 1000)) 90 | da.addLine(to: CGPoint(x: 62, y: 986)) 91 | da.addLine(to: CGPoint(x: 189, y: 986)) 92 | da.addLine(to: CGPoint(x: 189, y: 1000)) 93 | da.addLine(to: CGPoint(x: 232, y: 972)) 94 | da.close() 95 | 96 | return da 97 | } 98 | 99 | func updateThumbAttributes(_ snappingStepper: SnappingStepper, index: Int) { 100 | switch index { 101 | case 1: 102 | snappingStepper.thumbText = "" 103 | case 2: 104 | snappingStepper.thumbText = "Move Me" 105 | snappingStepper.thumbFont = UIFont(name: "TrebuchetMS-Bold", size: 10) 106 | default: 107 | snappingStepper.thumbText = nil 108 | snappingStepper.thumbFont = UIFont(name: "TrebuchetMS-Bold", size: 20) 109 | } 110 | } 111 | 112 | @IBAction func stepperValueChangedAction(_ sender: SnappingStepper) { 113 | for stepper in [classicStepper, tubeStepper, roundedStepper, verticalRoundedStepper, customStepper] { 114 | if stepper != sender { 115 | stepper?.value = sender.value 116 | } 117 | } 118 | 119 | valueLabel.text = "\(sender.value)" 120 | } 121 | 122 | @IBAction func segmentedValueChangedAction(_ sender: UISegmentedControl) { 123 | updateThumbAttributes(classicStepper, index: sender.selectedSegmentIndex) 124 | updateThumbAttributes(customStepper, index: sender.selectedSegmentIndex) 125 | updateThumbAttributes(tubeStepper, index: sender.selectedSegmentIndex) 126 | updateThumbAttributes(roundedStepper, index: sender.selectedSegmentIndex) 127 | updateThumbAttributes(verticalRoundedStepper, index: sender.selectedSegmentIndex) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Example/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-present Yannick Loriot 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * SnappingStepper 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import PackageDescription 28 | 29 | let package = Package( 30 | name: "SnappingStepper", 31 | dependencies: [ 32 | .Package(url: "https://github.com/yannickl/DynamicColor", majorVersion: 2, minorVersion: 4) 33 | ] 34 | ) 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | SnappingStepper 3 |

4 | 5 | [![License](https://cocoapod-badges.herokuapp.com/l/SnappingStepper/badge.svg)](http://cocoadocs.org/docsets/SnappingStepper/) [![Supported Plateforms](https://cocoapod-badges.herokuapp.com/p/SnappingStepper/badge.svg)](http://cocoadocs.org/docsets/SnappingStepper/) [![Version](https://cocoapod-badges.herokuapp.com/v/SnappingStepper/badge.svg)](http://cocoadocs.org/docsets/SnappingStepper/) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 6 | [![Build Status](https://travis-ci.org/yannickl/SnappingStepper.svg?branch=master)](https://travis-ci.org/yannickl/SnappingStepper) [![codecov.io](http://codecov.io/github/yannickl/SnappingStepper/coverage.svg?branch=master)](http://codecov.io/github/yannickl/SnappingStepper?branch=master) [![codebeat badge](https://codebeat.co/badges/da2160dc-b263-42e7-b65e-ae39dc41cda8)](https://codebeat.co/projects/github-com-yannickl-snappingstepper) 7 | 8 | An elegant alternative to the `UIStepper` in Swift with a thumb slider addition to control the value update with more flexibility. 9 | 10 |

11 | screenshot 12 |

13 | 14 | # Usage 15 | 16 | ```swift 17 | let stepper = SnappingStepper(frame: CGRect(x: 0, y: 0, width: 100, height: 40)) 18 | 19 | // Configure the stepper like any other UIStepper. For example: 20 | // 21 | // stepper.continuous = true 22 | // stepper.autorepeat = true 23 | // stepper.wraps = false 24 | // stepper.minimumValue = 0 25 | // stepper.maximumValue = 100 26 | // stepper.stepValue = 1 27 | 28 | stepper.symbolFont = UIFont(name: "TrebuchetMS-Bold", size: 20) 29 | stepper.symbolFontColor = .black 30 | stepper.backgroundColor = UIColor(hex: 0xc0392b) 31 | stepper.thumbWidthRatio = 0.5 32 | stepper.thumbText = "" 33 | stepper.thumbFont = UIFont(name: "TrebuchetMS-Bold", size: 20) 34 | stepper.thumbBackgroundColor = UIColor(hex: 0xe74c3c) 35 | stepper.thumbTextColor = .black 36 | 37 | stepper.addTarget(self, action: "valueChanged:", forControlEvents: .valueChanged) 38 | 39 | // If you don't want using the traditional `addTarget:action:` pattern you can use 40 | // the `valueChangedBlock` 41 | // snappingStepper.valueChangeBlock = { (value: Double) in 42 | // println("value: \(value)") 43 | // } 44 | 45 | func valueChanged(sender: AnyObject) { 46 | // Retrieve the value: stepper.value 47 | } 48 | ``` 49 | 50 | To go further, take a look at the example project. 51 | 52 | # Installation 53 | 54 | ## CocoaPods 55 | 56 | Install CocoaPods if not already available: 57 | 58 | ``` bash 59 | $ [sudo] gem install cocoapods 60 | $ pod setup 61 | ``` 62 | Go to the directory of your Xcode project, and Create and Edit your Podfile and add _SnappingStepper_: 63 | 64 | ``` bash 65 | $ cd /path/to/MyProject 66 | $ touch Podfile 67 | $ edit Podfile 68 | source 'https://github.com/CocoaPods/Specs.git' 69 | platform :ios, '8.0' 70 | 71 | use_frameworks! 72 | pod 'SnappingStepper', '~> 3.0.0' 73 | ``` 74 | 75 | Install into your project: 76 | 77 | ``` bash 78 | $ pod install 79 | ``` 80 | 81 | Open your project in Xcode from the .xcworkspace file (not the usual project file): 82 | 83 | ``` bash 84 | $ open MyProject.xcworkspace 85 | ``` 86 | 87 | You can now `import SnappingStepper` framework into your files. 88 | 89 | ## Carthage 90 | 91 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that automates the process of adding frameworks to your Cocoa application. 92 | 93 | You can install Carthage with [Homebrew](http://brew.sh/) using the following command: 94 | 95 | ```bash 96 | $ brew update 97 | $ brew install carthage 98 | ``` 99 | 100 | To integrate `SnappingStepper` into your Xcode project using Carthage, specify it in your `Cartfile` file: 101 | 102 | ```ogdl 103 | github "yannickl/SnappingStepper" >= 3.0.0 104 | ``` 105 | 106 | ## Swift Package Manager 107 | You can use [The Swift Package Manager](https://swift.org/package-manager) to install `SnappingStepper` by adding the proper description to your `Package.swift` file: 108 | 109 | ```swift 110 | import PackageDescription 111 | 112 | let package = Package( 113 | name: "YOUR_PROJECT_NAME", 114 | targets: [], 115 | dependencies: [ 116 | .Package(url: "https://github.com/yannickl/SnappingStepper.git", versions: "3.0.0" ..< Version.max) 117 | ] 118 | ) 119 | ``` 120 | 121 | Note that the [Swift Package Manager](https://swift.org/package-manager) (SPM) is still in early design and development, for more infomation checkout its [GitHub Page](https://github.com/apple/swift-package-manager). 122 | 123 | ## Manually 124 | 125 | [Download](https://github.com/YannickL/SnappingStepper/archive/master.zip) the project and copy the `SnappingStepper` folder into your project to use it in. 126 | 127 | ## Contribution 128 | 129 | Contributions are welcomed and encouraged *♡*. 130 | 131 | # Contact 132 | 133 | Yannick Loriot 134 | - [https://21.co/yannickl/](https://21.co/yannickl/) 135 | - [https://twitter.com/yannickloriot](https://twitter.com/yannickloriot) 136 | 137 | # License (MIT) 138 | 139 | Copyright (c) 2015-present - Yannick Loriot 140 | 141 | Permission is hereby granted, free of charge, to any person obtaining a copy 142 | of this software and associated documentation files (the "Software"), to deal 143 | in the Software without restriction, including without limitation the rights 144 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 145 | copies of the Software, and to permit persons to whom the Software is 146 | furnished to do so, subject to the following conditions: 147 | 148 | The above copyright notice and this permission notice shall be included in 149 | all copies or substantial portions of the Software. 150 | 151 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 152 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 153 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 154 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 155 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 156 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 157 | THE SOFTWARE. 158 | -------------------------------------------------------------------------------- /SnappingStepper.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'SnappingStepper' 3 | s.version = '3.0.0' 4 | s.license = 'MIT' 5 | s.summary = 'An elegant alternative to the UIStepper written in Swift' 6 | s.homepage = 'https://github.com/yannickl/SnappingStepper.git' 7 | s.social_media_url = 'https://twitter.com/yannickloriot' 8 | s.authors = { 'Yannick Loriot' => 'contact@yannickloriot.com' } 9 | s.source = { :git => 'https://github.com/yannickl/SnappingStepper.git', :tag => s.version } 10 | s.screenshot = 'http://yannickloriot.com/resources/snappingstepper-screenshot.png' 11 | s.swift_version = '4.2' 12 | 13 | s.ios.deployment_target = '8.0' 14 | 15 | s.dependency 'DynamicColor', '~> 4.0' 16 | 17 | s.framework = 'UIKit' 18 | s.source_files = 'Sources/*.swift' 19 | s.requires_arc = true 20 | end 21 | -------------------------------------------------------------------------------- /Sources/AutoRepeatHelper.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * SnappingStepper 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import Foundation 28 | 29 | final class AutoRepeatHelper { 30 | private var timer: Timer? 31 | private var autorepeatCount = 0 32 | private var tickBlock: (() -> Void)? 33 | 34 | deinit { 35 | stop() 36 | } 37 | 38 | // MARK: - Managing Autorepeat 39 | 40 | func stop() { 41 | timer?.invalidate() 42 | } 43 | 44 | func start(autorepeatCount count: Int = 0, tickBlock block: @escaping () -> Void) { 45 | // if let _timer = timer where _timer.valid { 46 | // return 47 | // } 48 | 49 | autorepeatCount = count 50 | tickBlock = block 51 | 52 | repeatTick(sender: nil) 53 | 54 | let newTimer = Timer(timeInterval: 0.1, target: self, selector: #selector(AutoRepeatHelper.repeatTick), userInfo: nil, repeats: true) 55 | timer = newTimer 56 | 57 | RunLoop.current.add(newTimer, forMode: .common) 58 | } 59 | 60 | @objc func repeatTick(sender: AnyObject?) { 61 | let needsIncrement: Bool 62 | 63 | if autorepeatCount < 35 { 64 | if autorepeatCount < 10 { 65 | needsIncrement = autorepeatCount % 5 == 0 66 | } 67 | else if autorepeatCount < 20 { 68 | needsIncrement = autorepeatCount % 4 == 0 69 | } 70 | else if autorepeatCount < 25 { 71 | needsIncrement = autorepeatCount % 3 == 0 72 | } 73 | else if autorepeatCount < 30 { 74 | needsIncrement = autorepeatCount % 2 == 0 75 | } 76 | else { 77 | needsIncrement = autorepeatCount % 1 == 0 78 | } 79 | 80 | autorepeatCount += 1 81 | } 82 | else { 83 | needsIncrement = true 84 | } 85 | 86 | if needsIncrement { 87 | tickBlock?() 88 | } 89 | } 90 | } 91 | 92 | -------------------------------------------------------------------------------- /Sources/CustomShapeLayer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * CustomShapeLayer 3 | * Created by Martin Rehder. 4 | * 5 | * SnappingStepper 6 | * 7 | * Copyright 2015-present Yannick Loriot. 8 | * http://yannickloriot.com 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in 18 | * all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | * THE SOFTWARE. 27 | * 28 | */ 29 | 30 | import UIKit 31 | 32 | final class CustomShapeLayer { 33 | static func createShape(style: ShapeStyle, bounds: CGRect, color: UIColor) -> CAShapeLayer { 34 | let shape = CAShapeLayer() 35 | 36 | let path = CustomShapeLayer.shapePathForStyle(style: style, bounds: bounds) 37 | shape.path = path.cgPath 38 | shape.fillColor = color.cgColor 39 | 40 | return shape 41 | } 42 | 43 | static func createShape(style: ShapeStyle, bounds: CGRect, color: UIColor, borderColor: UIColor, borderWidth: CGFloat) -> CAShapeLayer { 44 | let shape = CAShapeLayer() 45 | 46 | let path = CustomShapeLayer.shapePathForStyle(style: style, bounds: bounds) 47 | shape.path = path.cgPath 48 | shape.fillColor = color.cgColor 49 | shape.strokeColor = borderColor.cgColor 50 | shape.lineWidth = borderWidth 51 | 52 | return shape 53 | } 54 | 55 | static func shapePathForStyle(style: ShapeStyle, bounds: CGRect) -> UIBezierPath { 56 | var path = UIBezierPath() 57 | 58 | switch style { 59 | case .box, .none: 60 | path = UIBezierPath(rect: bounds) 61 | case .rounded: 62 | path = UIBezierPath(roundedRect: bounds, cornerRadius: max(1.0, min(bounds.size.width, bounds.size.height) * 0.2)) 63 | case .roundedFixed(let cornerRadius): 64 | path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius) 65 | case .thumb: 66 | let s = min(bounds.size.width, bounds.size.height) 67 | let xOff = (bounds.size.width - s) * 0.5 68 | path = UIBezierPath(ovalIn: CGRect(x: bounds.origin.x + xOff, y: bounds.origin.y, width: s, height: s)) 69 | case .tube: 70 | path = UIBezierPath(roundedRect: bounds, cornerRadius: max(1.0, min(bounds.size.width, bounds.size.height) * 0.5)) 71 | case .custom(let cpath): 72 | path = CustomShapeLayer.getScaledPath(path: cpath.cgPath, size: bounds.size) 73 | } 74 | 75 | return path 76 | } 77 | 78 | static func getScaledPath(path: CGPath, size: CGSize) -> UIBezierPath { 79 | let rect = CGRect(origin:CGPoint(x:0, y:0), size:CGSize(width: size.width, height: size.height)) 80 | let boundingBox = path.boundingBox 81 | 82 | let scaleFactorX = rect.width / boundingBox.width 83 | let scaleFactorY = rect.height / boundingBox.height 84 | 85 | var scaleTransform = CGAffineTransform.identity 86 | scaleTransform = scaleTransform.scaledBy(x: scaleFactorX, y: scaleFactorY) 87 | scaleTransform = scaleTransform.translatedBy(x: -boundingBox.minX, y: -boundingBox.minY) 88 | 89 | 90 | let scaledSize = boundingBox.size.applying(CGAffineTransform(scaleX: scaleFactorX, y: scaleFactorY)) 91 | let centerOffset = CGSize(width: (rect.width - scaledSize.width) / (scaleFactorX * 2.0), height:(rect.height - scaledSize.height) / (scaleFactorY * 2.0)) 92 | scaleTransform = scaleTransform.translatedBy(x: centerOffset.width, y: centerOffset.height) 93 | 94 | let scaledPath = path.copy(using: &scaleTransform)! 95 | 96 | return UIBezierPath(cgPath: scaledPath) 97 | } 98 | 99 | static func createHintShapeLayer(label: StyledLabel, fillColor: CGColor?) { 100 | let shape = CAShapeLayer() 101 | let cp1 = CGPoint(x: label.bounds.width * 0.35, y: label.bounds.height) 102 | let cp2 = CGPoint(x: label.bounds.width * 0.65, y: label.bounds.height) 103 | let cpc = CGPoint(x: label.bounds.width / 2.0, y: label.bounds.height * 1.25) 104 | let sp = CGPoint(x: label.bounds.width / 2.0, y: label.bounds.height * 1.5) 105 | 106 | let myBezier = UIBezierPath() 107 | myBezier.move(to: sp) 108 | myBezier.addCurve(to: CGPoint(x: label.bounds.width * 0.2, y: label.bounds.height), controlPoint1: cpc, controlPoint2: cp1) 109 | myBezier.addLine(to: CGPoint(x: label.bounds.width * 0.8, y: label.bounds.height)) 110 | myBezier.addCurve(to: sp, controlPoint1: cp2, controlPoint2: cpc) 111 | myBezier.close() 112 | 113 | shape.path = myBezier.cgPath 114 | shape.fillColor = fillColor 115 | 116 | label.layer.addSublayer(shape) 117 | } 118 | } 119 | 120 | -------------------------------------------------------------------------------- /Sources/ShapeStyle.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * ShapeStyle 3 | * Created by Martin Rehder. 4 | * 5 | * SnappingStepper 6 | * 7 | * Copyright 2015-present Yannick Loriot. 8 | * http://yannickloriot.com 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in 18 | * all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | * THE SOFTWARE. 27 | * 28 | */ 29 | 30 | import UIKit 31 | 32 | /// Specifies the shape style of the snapping stepper. 33 | public enum ShapeStyle { 34 | /// No shape 35 | case none 36 | /// A box shape. 37 | case box 38 | /// A round shape. 39 | case rounded 40 | /// A round shape with given corner radius. 41 | case roundedFixed(cornerRadius: CGFloat) 42 | /// A thumb shape. 43 | case thumb 44 | /// A tube shape. 45 | case tube 46 | /// A custom shape. 47 | case custom(path: UIBezierPath) 48 | } 49 | -------------------------------------------------------------------------------- /Sources/SnappingStepper+Internal.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * SnappingStepper 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import UIKit 28 | 29 | extension SnappingStepper { 30 | // MARK: - Managing the Components 31 | 32 | func initComponents() { 33 | self.layer.addSublayer(styleLayer) 34 | 35 | hintLabel.font = thumbFont 36 | hintLabel.textColor = thumbTextColor 37 | 38 | minusSymbolLabel.text = "−" 39 | minusSymbolLabel.font = symbolFont 40 | minusSymbolLabel.textColor = symbolFontColor 41 | 42 | addSubview(minusSymbolLabel) 43 | 44 | plusSymbolLabel.text = "+" 45 | plusSymbolLabel.font = symbolFont 46 | plusSymbolLabel.textColor = symbolFontColor 47 | addSubview(plusSymbolLabel) 48 | 49 | thumbLabel.font = thumbFont 50 | thumbLabel.textColor = thumbTextColor 51 | addSubview(thumbLabel) 52 | } 53 | 54 | func setupGestures() { 55 | let panGesture = UIPanGestureRecognizer(target: self, action: #selector(SnappingStepper.sliderPanned)) 56 | thumbLabel.addGestureRecognizer(panGesture) 57 | 58 | let touchGesture = UITouchGestureRecognizer(target: self, action: #selector(SnappingStepper.stepperTouched)) 59 | touchGesture.require(toFail: panGesture) 60 | addGestureRecognizer(touchGesture) 61 | } 62 | 63 | func layoutComponents() { 64 | let bw = self.direction.principalSize(size: bounds.size) 65 | let bh = self.direction.nonPrincipalSize(size: bounds.size) 66 | let thumbWidth = bw * thumbWidthRatio 67 | let symbolWidth = (bw - thumbWidth) / 2 68 | 69 | // It makes most sense to have the + on the top of the view, when the direction is vertical 70 | let mpPosM: CGFloat 71 | let mpPosP: CGFloat 72 | if self.direction == .horizontal { 73 | mpPosM = 0 74 | mpPosP = symbolWidth + thumbWidth 75 | } 76 | else { 77 | mpPosM = symbolWidth + thumbWidth 78 | mpPosP = 0 79 | } 80 | 81 | let minusSymbolLabelFrame = CGRect(x: mpPosM, y: 0, width: symbolWidth, height: bh) 82 | let plusSymbolLabelFrame = CGRect(x: mpPosP, y: 0, width: symbolWidth, height: bh) 83 | let thumbLabelFrame = CGRect(x: symbolWidth, y: 0, width: thumbWidth, height: bh) 84 | let hintLabelFrame = CGRect(x: symbolWidth, y: -bounds.height * 1.5, width: thumbWidth, height: bh) 85 | 86 | minusSymbolLabel.frame = CGRect(origin: self.direction.getPosition(p: minusSymbolLabelFrame.origin), size: self.direction.getSize(size: minusSymbolLabelFrame.size)) 87 | plusSymbolLabel.frame = CGRect(origin: self.direction.getPosition(p: plusSymbolLabelFrame.origin), size: self.direction.getSize(size: plusSymbolLabelFrame.size)) 88 | thumbLabel.frame = CGRect(origin: self.direction.getPosition(p: thumbLabelFrame.origin), size: self.direction.getSize(size: thumbLabelFrame.size)) 89 | 90 | // The hint label is not direction dependent 91 | hintLabel.frame = hintLabelFrame 92 | 93 | snappingBehavior = SnappingStepperBehavior(item: thumbLabel, snapToPoint: CGPoint(x: bounds.size.width * 0.5, y: bounds.size.height * 0.5)) 94 | 95 | CustomShapeLayer.createHintShapeLayer(label: hintLabel, fillColor: thumbBackgroundColor?.lighter().cgColor) 96 | 97 | applyThumbStyle(style: thumbStyle) 98 | applyStyle(style: style) 99 | applyHintStyle(style: hintStyle) 100 | } 101 | 102 | func applyThumbStyle(style: ShapeStyle) { 103 | thumbLabel.style = style 104 | thumbLabel.borderColor = thumbBorderColor 105 | thumbLabel.borderWidth = thumbBorderWidth 106 | } 107 | 108 | func applyHintStyle(style: ShapeStyle) { 109 | hintLabel.style = style 110 | } 111 | 112 | func applyStyle(style: ShapeStyle) { 113 | let bgColor: UIColor = .clear 114 | let sLayer: CAShapeLayer 115 | 116 | if let borderColor = borderColor { 117 | sLayer = CustomShapeLayer.createShape(style: style, bounds: bounds, color: bgColor, borderColor: borderColor, borderWidth: borderWidth) 118 | } 119 | else { 120 | sLayer = CustomShapeLayer.createShape(style: style, bounds: bounds, color: bgColor) 121 | } 122 | 123 | if styleLayer.superlayer != nil { 124 | layer.replaceSublayer(styleLayer, with: sLayer) 125 | } 126 | 127 | styleLayer = sLayer 128 | } 129 | 130 | // MARK: - Responding to Gesture Events 131 | 132 | @objc func stepperTouched(sender: UITouchGestureRecognizer) { 133 | let touchLocation = sender.location(in: self) 134 | let hitView = hitTest(touchLocation, with: nil) 135 | 136 | factorValue = hitView == minusSymbolLabel ? -1 : 1 137 | 138 | switch (sender.state, hitView) { 139 | case (.began, .some(let v)) where v == minusSymbolLabel || v == plusSymbolLabel: 140 | if autorepeat { 141 | startAutorepeat() 142 | } 143 | else { 144 | let value = _value + stepValue * factorValue 145 | 146 | updateValue(value: value, finished: true) 147 | } 148 | 149 | v.backgroundColor = backgroundColor?.darkened() 150 | case (.changed, .some(let v)): 151 | if v == minusSymbolLabel || v == plusSymbolLabel { 152 | v.backgroundColor = backgroundColor?.darkened() 153 | 154 | if autorepeat { 155 | startAutorepeat() 156 | } 157 | } 158 | else { 159 | minusSymbolLabel.backgroundColor = backgroundColor 160 | plusSymbolLabel.backgroundColor = backgroundColor 161 | 162 | autorepeatHelper.stop() 163 | } 164 | default: 165 | minusSymbolLabel.backgroundColor = backgroundColor 166 | plusSymbolLabel.backgroundColor = backgroundColor 167 | 168 | if autorepeat { 169 | autorepeatHelper.stop() 170 | 171 | factorValue = 0 172 | 173 | updateValue(value: _value, finished: true) 174 | } 175 | } 176 | } 177 | 178 | @objc func sliderPanned(sender: UIPanGestureRecognizer) { 179 | switch sender.state { 180 | case .began: 181 | if case .none = hintStyle {} else { 182 | hintLabel.alpha = 0 183 | hintLabel.center = CGPoint(x: center.x, y: center.y - (bounds.size.height * 0.5 + hintLabel.bounds.height)) 184 | 185 | superview?.addSubview(hintLabel) 186 | 187 | UIView.animate(withDuration: 0.2) { 188 | self.hintLabel.alpha = 1.0 189 | } 190 | } 191 | 192 | touchesBeganPoint = self.direction.getPosition(p: sender.translation(in: thumbLabel)) 193 | dynamicButtonAnimator.removeBehavior(snappingBehavior) 194 | 195 | thumbLabel.backgroundColor = thumbBackgroundColor?.lighter() 196 | hintLabel.backgroundColor = thumbBackgroundColor?.lighter() 197 | 198 | if autorepeat { 199 | startAutorepeat(autorepeatCount: Int.max) 200 | } 201 | else { 202 | initialValue = _value 203 | } 204 | case .changed: 205 | let translationInView = self.direction.getPosition(p: sender.translation(in: thumbLabel)) 206 | let bw = self.direction.principalSize(size: bounds.size) 207 | let tbw = self.direction.principalSize(size: thumbLabel.bounds.size) 208 | let tcenter = self.direction.getPosition(p: thumbLabel.center) 209 | 210 | var centerX = (bw * 0.5) + ((touchesBeganPoint.x + translationInView.x) * 0.4) 211 | centerX = max(tbw / 2, min(centerX, bw - tbw / 2)) 212 | 213 | thumbLabel.center = self.direction.getPosition(p: CGPoint(x: centerX, y: tcenter.y)) 214 | 215 | let locationRatio: CGFloat 216 | if self.direction == .horizontal { 217 | locationRatio = (tcenter.x - bounds.midX) / ((bounds.width - thumbLabel.bounds.width) / 2) 218 | } 219 | else { 220 | // The + is on top of the control in vertical layout, so the locationRatio must be reversed! 221 | locationRatio = (bounds.midY - tcenter.x) / ((bounds.height - thumbLabel.bounds.height) / 2) 222 | } 223 | 224 | let ratio = Double(Int(locationRatio * 10)) / 10 225 | let factorValue = ((maximumValue - minimumValue) / 100) * ratio 226 | 227 | if autorepeat { 228 | self.factorValue = factorValue 229 | } 230 | else { 231 | _value = initialValue + stepValue * factorValue 232 | 233 | updateValue(value: _value, finished: true) 234 | } 235 | case .ended, .failed, .cancelled: 236 | if case .none = hintStyle {} else { 237 | UIView.animate(withDuration: 0.2, animations: { 238 | self.hintLabel.alpha = 0.0 239 | }) { _ in 240 | self.hintLabel.removeFromSuperview() 241 | } 242 | } 243 | 244 | dynamicButtonAnimator.addBehavior(snappingBehavior) 245 | 246 | thumbLabel.backgroundColor = thumbBackgroundColor ?? backgroundColor?.lighter() 247 | 248 | if autorepeat { 249 | autorepeatHelper.stop() 250 | 251 | factorValue = 0 252 | 253 | updateValue(value: _value, finished: true) 254 | } 255 | case .possible: 256 | break 257 | } 258 | } 259 | 260 | // MARK: - Updating the Value 261 | 262 | func startAutorepeat(autorepeatCount count: Int = 0) { 263 | autorepeatHelper.start(autorepeatCount: count) { [weak self] in 264 | if let weakSelf = self { 265 | let value = weakSelf._value + weakSelf.stepValue * weakSelf.factorValue 266 | 267 | weakSelf.updateValue(value: value, finished: false) 268 | } 269 | } 270 | } 271 | 272 | func updateValue(value: Double, finished: Bool = true) { 273 | if !wraps { 274 | _value = max(minimumValue, min(value, maximumValue)) 275 | } 276 | else if value < minimumValue { 277 | _value = maximumValue 278 | } 279 | else if value > maximumValue { 280 | _value = minimumValue 281 | } 282 | 283 | if (continuous || finished) && oldValue != _value { 284 | oldValue = _value 285 | 286 | sendActions(for: .valueChanged) 287 | 288 | if let _valueChangedBlock = valueChangedBlock { 289 | _valueChangedBlock(_value) 290 | } 291 | } 292 | } 293 | 294 | func valueAsText() -> String { 295 | return (value.truncatingRemainder(dividingBy: 1) == 0) ? "\(Int(value))" : "\(value)" 296 | } 297 | } 298 | 299 | -------------------------------------------------------------------------------- /Sources/SnappingStepper.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * SnappingStepper 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import DynamicColor 28 | import UIKit 29 | 30 | /** 31 | A stepper control provides a user interface for incrementing or decrementing a value. 32 | 33 | The `SnappingStepper` addings a thumb in the middle to allow the user to update the value by sliding it either to the left or the right side. It can also be customizable to display the current value or custom text. 34 | */ 35 | @IBDesignable public final class SnappingStepper: UIControl { 36 | // MARK: - Preparing and Sending Messages using Blocks 37 | 38 | /** 39 | Block to be notify when the value of the stepper change. 40 | 41 | This is a convenient alternative to the `addTarget:Action:forControlEvents:` method of the `UIControl`. 42 | */ 43 | public var valueChangedBlock: ((_ value: Double) -> Void)? 44 | 45 | // MARK: - Configuring the Stepper 46 | 47 | /** 48 | The continuous vs. noncontinuous state of the stepper. 49 | 50 | If true, value change events are sent immediately when the value changes during user interaction. If false, a value change event is sent when user interaction ends. 51 | 52 | The default value for this property is true. 53 | */ 54 | @IBInspectable public var continuous: Bool = true 55 | 56 | /** 57 | The automatic vs. nonautomatic repeat state of the stepper. 58 | 59 | If true, the user pressing and holding on the stepper repeatedly alters value. 60 | 61 | The default value for this property is true. 62 | */ 63 | @IBInspectable public var autorepeat: Bool = true 64 | 65 | /** 66 | The wrap vs. no-wrap state of the stepper. 67 | 68 | If true, incrementing beyond maximumValue sets value to minimumValue; likewise, decrementing below minimumValue sets value to maximumValue. If false, the stepper does not increment beyond maximumValue nor does it decrement below minimumValue but rather holds at those values. 69 | 70 | The default value for this property is false. 71 | */ 72 | @IBInspectable public var wraps: Bool = false 73 | 74 | /** 75 | The direction of the control 76 | 77 | The default is horizontal 78 | */ 79 | @IBInspectable public var direction: StyledControlDirection = .horizontal { 80 | didSet { 81 | self.layoutComponents() 82 | } 83 | } 84 | 85 | /** 86 | The lowest possible numeric value for the stepper. 87 | 88 | Must be numerically less than maximumValue. If you attempt to set a value equal to or greater than maximumValue, the system raises an NSInvalidArgumentException exception. 89 | 90 | The default value for this property is 0. 91 | */ 92 | @IBInspectable public var minimumValue: Double = 0 { 93 | didSet { 94 | if minimumValue > maximumValue { 95 | maximumValue = minimumValue 96 | } 97 | 98 | updateValue(value: max(_value, minimumValue), finished: true) 99 | } 100 | } 101 | 102 | /** 103 | The highest possible numeric value for the stepper. 104 | 105 | Must be numerically greater than minimumValue. If you attempt to set a value equal to or lower than minimumValue, the system raises an NSInvalidArgumentException exception. 106 | 107 | The default value of this property is 100. 108 | */ 109 | @IBInspectable public var maximumValue: Double = 100 { 110 | didSet { 111 | if maximumValue < minimumValue { 112 | minimumValue = maximumValue 113 | } 114 | 115 | updateValue(value: min(_value, maximumValue), finished: true) 116 | } 117 | } 118 | 119 | /** 120 | The step, or increment, value for the stepper. 121 | 122 | Must be numerically greater than 0. If you attempt to set this property’s value to 0 or to a negative number, the system raises an NSInvalidArgumentException exception. 123 | 124 | The default value for this property is 1. 125 | */ 126 | @IBInspectable public var stepValue: Double = 1 127 | 128 | // MARK: - Accessing the Stepper’s Value 129 | 130 | /** 131 | The numeric value of the snapping stepper. 132 | 133 | When the value changes, the stepper sends the UIControlEventValueChanged flag to its target (see addTarget:action:forControlEvents:). Refer to the description of the continuous property for information about whether value change events are sent continuously or when user interaction ends. 134 | 135 | The default value for this property is 0. This property is clamped at its lower extreme to minimumValue and is clamped at its upper extreme to maximumValue. 136 | */ 137 | @IBInspectable public var value: Double { 138 | get { 139 | return _value 140 | } 141 | set (newValue) { 142 | updateValue(value: newValue, finished: true) 143 | } 144 | } 145 | 146 | var _value: Double = 0 { 147 | didSet { 148 | if thumbText == nil { 149 | thumbLabel.text = valueAsText() 150 | } 151 | 152 | hintLabel.text = valueAsText() 153 | } 154 | } 155 | 156 | // MARK: - Setting the Stepper Visual Appearance 157 | 158 | /// The font of the text symbols (`minus` and `plus`). 159 | @IBInspectable public var symbolFont = UIFont(name: "TrebuchetMS-Bold", size: 20) { 160 | didSet { 161 | minusSymbolLabel.font = symbolFont 162 | plusSymbolLabel.font = symbolFont 163 | } 164 | } 165 | 166 | /// The color of the text symbols (`minus` and `plus`). 167 | @IBInspectable public var symbolFontColor: UIColor = .black { 168 | didSet { 169 | minusSymbolLabel.textColor = symbolFontColor 170 | plusSymbolLabel.textColor = symbolFontColor 171 | } 172 | } 173 | 174 | /// The thumb width represented as a ratio of the component width. For example if the width of the stepper is 30px and the ratio is 0.5, the thumb width will be equal to 15px. Defaults to 0.5. 175 | @IBInspectable public var thumbWidthRatio: CGFloat = 0.5 { 176 | didSet { 177 | layoutComponents() 178 | } 179 | } 180 | 181 | /// The font of the thumb label. 182 | @IBInspectable public var thumbFont = UIFont(name: "TrebuchetMS-Bold", size: 20) { 183 | didSet { 184 | thumbLabel.font = thumbFont 185 | } 186 | } 187 | 188 | /// The thumb's background color. If nil the thumb color will be lighter than the background color. Defaults to nil. 189 | @IBInspectable public var thumbBackgroundColor: UIColor? { 190 | didSet { 191 | thumbLabel.backgroundColor = thumbBackgroundColor 192 | } 193 | } 194 | 195 | /// The thumb's text color. Default's to black 196 | @IBInspectable public var thumbTextColor: UIColor = .black { 197 | didSet { 198 | thumbLabel.textColor = thumbTextColor 199 | } 200 | } 201 | 202 | /// The thumb's style. Default's to box 203 | public var thumbStyle: ShapeStyle = .box { 204 | didSet { 205 | self.applyThumbStyle(style: thumbStyle) 206 | } 207 | } 208 | 209 | /// The view's style. Default's to box. 210 | public var style: ShapeStyle = .box { 211 | didSet { 212 | self.applyStyle(style: style) 213 | } 214 | } 215 | 216 | /// The hint's style. Default's to none, so no hint will be displayed. 217 | public var hintStyle: ShapeStyle = .none { 218 | didSet { 219 | self.applyHintStyle(style: hintStyle) 220 | } 221 | } 222 | 223 | /// The view's border color. 224 | @IBInspectable public var borderColor: UIColor? { 225 | didSet { 226 | self.applyStyle(style: style) 227 | } 228 | } 229 | 230 | /// The thumbs's border color. 231 | @IBInspectable public var thumbBorderColor: UIColor? { 232 | didSet { 233 | self.applyThumbStyle(style: thumbStyle) 234 | } 235 | } 236 | 237 | /// The view's border width. Default's to 1.0 238 | @IBInspectable public var borderWidth: CGFloat = 1.0 { 239 | didSet { 240 | self.applyStyle(style: style) 241 | } 242 | } 243 | 244 | /// The thumbs's border width. Default's to 1.0 245 | @IBInspectable public var thumbBorderWidth: CGFloat = 1.0 { 246 | didSet { 247 | self.applyThumbStyle(style: thumbStyle) 248 | } 249 | } 250 | 251 | /// The view’s background color. 252 | override public var backgroundColor: UIColor? { 253 | didSet { 254 | self.styleColor = backgroundColor 255 | self.applyStyle(style: self.style) 256 | 257 | if thumbBackgroundColor == nil { 258 | thumbLabel.backgroundColor = backgroundColor?.lighter() 259 | } 260 | } 261 | } 262 | 263 | // MARK: - Displaying Thumb Text 264 | 265 | /// The thumb text to display. If the text is nil it will display the current value of the stepper. Defaults with empty string. 266 | @IBInspectable public var thumbText: String? = "" { 267 | didSet { 268 | if thumbText == nil { 269 | thumbLabel.text = valueAsText() 270 | } 271 | else { 272 | thumbLabel.text = thumbText 273 | } 274 | 275 | hintLabel.text = valueAsText() 276 | } 277 | } 278 | 279 | // MARK: - Deallocating Snappinf Stepper 280 | 281 | deinit { 282 | autorepeatHelper.stop() 283 | } 284 | 285 | // MARK: - Initializing a Snapping Stepper 286 | 287 | /// Initializes and returns a newly allocated view object with the specified frame rectangle. 288 | override public init(frame: CGRect) { 289 | super.init(frame: frame) 290 | 291 | initComponents() 292 | setupGestures() 293 | } 294 | 295 | /// Returns an object initialized from data in a given unarchiver. 296 | required public init?(coder aDecoder: NSCoder) { 297 | super.init(coder: aDecoder) 298 | 299 | initComponents() 300 | setupGestures() 301 | } 302 | 303 | // MARK: - Laying out Subviews 304 | 305 | /// Lays out subviews 306 | public override func layoutSubviews() { 307 | super.layoutSubviews() 308 | 309 | layoutComponents() 310 | } 311 | 312 | // MARK: - Internal Properties 313 | 314 | /// The value label that represents the thumb button 315 | lazy var thumbLabel: StyledLabel = UIBuilder.defaultStyledLabel() 316 | 317 | /// The hint label 318 | lazy var hintLabel: StyledLabel = UIBuilder.defaultStyledLabel() 319 | 320 | /// The minus label 321 | lazy var minusSymbolLabel: UILabel = UIBuilder.defaultLabel() 322 | 323 | /// The plus label 324 | lazy var plusSymbolLabel: UILabel = UIBuilder.defaultLabel() 325 | 326 | let autorepeatHelper = AutoRepeatHelper() 327 | let dynamicButtonAnimator = UIDynamicAnimator() 328 | var snappingBehavior = SnappingStepperBehavior(item: nil, snapToPoint: CGPoint.zero) 329 | 330 | var styleLayer = CAShapeLayer() 331 | var styleColor: UIColor? = .clear 332 | 333 | var touchesBeganPoint = CGPoint.zero 334 | var initialValue: Double = -1 335 | var factorValue: Double = 0 336 | var oldValue = Double.infinity * -1 337 | } 338 | 339 | -------------------------------------------------------------------------------- /Sources/SnappingStepperBehavior.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * SnappingStepper 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import UIKit 28 | 29 | /// The snapping dynamic behavic. 30 | final internal class SnappingStepperBehavior: UIDynamicBehavior { 31 | init(item: UIDynamicItem?, snapToPoint point: CGPoint) { 32 | super.init() 33 | 34 | if let _item = item { 35 | let dynamicItemBehavior = UIDynamicItemBehavior(items: [_item]) 36 | dynamicItemBehavior.allowsRotation = false 37 | 38 | let snapBehavior = UISnapBehavior(item: _item, snapTo: point) 39 | snapBehavior.damping = 0.25 40 | 41 | addChildBehavior(dynamicItemBehavior) 42 | addChildBehavior(snapBehavior) 43 | } 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Sources/StyledControlDirection.swift: -------------------------------------------------------------------------------- 1 | // ORIGINAL COPYRIGHT NOTE / ORIGINATES FROM MJRFlexStyleComponents 2 | // 3 | // StyledControlDirection.swift 4 | // MJRFlexStyleComponents 5 | // 6 | // Created by Martin Rehder on 16.07.16. 7 | /* 8 | * Copyright 2016-present Martin Jacob Rehder. 9 | * http://www.rehsco.com 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in 19 | * all copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | * 29 | */ 30 | 31 | import UIKit 32 | 33 | @objc public enum StyledControlDirection: Int { 34 | case horizontal 35 | case vertical 36 | 37 | /** 38 | The principal size is the axis of the direction. Width when the direction is horizontal and height when the direction is vertical. 39 | */ 40 | func principalSize(size: CGSize) -> CGFloat { 41 | switch self { 42 | case .horizontal: 43 | return size.width 44 | case .vertical: 45 | return size.height 46 | } 47 | } 48 | 49 | /** 50 | The non-principal size is the perpendicular axis of the direction. Height when the direction is horizontal and width when the direction is vertical. 51 | */ 52 | func nonPrincipalSize(size: CGSize) -> CGFloat { 53 | switch self { 54 | case .horizontal: 55 | return size.height 56 | case .vertical: 57 | return size.width 58 | } 59 | } 60 | 61 | /** 62 | The principal size of the direction is applied. Vertical direction will flip the width and the height in the size. 63 | */ 64 | func getSize(size: CGSize) -> CGSize { 65 | switch self { 66 | case .horizontal: 67 | return size 68 | case .vertical: 69 | return CGSize(width: size.width, height: size.height) 70 | } 71 | } 72 | 73 | /** 74 | The principal position of the direction is applied. Vertical direction will flip the X and the Y in the point. 75 | */ 76 | func getPosition(p: CGPoint) -> CGPoint { 77 | switch self { 78 | case .horizontal: 79 | return p 80 | case .vertical: 81 | return CGPoint(x: p.y, y: p.x) 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Sources/StyledLabel.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * StyledLabel 3 | * Created by Martin Rehder. 4 | * 5 | * SnappingStepper 6 | * 7 | * Copyright 2015-present Yannick Loriot. 8 | * http://yannickloriot.com 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in 18 | * all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | * THE SOFTWARE. 27 | * 28 | */ 29 | 30 | import UIKit 31 | 32 | /// The `StyledLabel` object is an `UILabel` with a custom shape. 33 | class StyledLabel: UIView { 34 | var label = UILabel() 35 | var styleColor: UIColor? = .clear 36 | var shapeLayer = CAShapeLayer() 37 | 38 | var style: ShapeStyle = .box { 39 | didSet { 40 | applyStyle() 41 | } 42 | } 43 | 44 | var text: String? { 45 | didSet { 46 | label.text = text 47 | } 48 | } 49 | 50 | var textColor: UIColor = .black { 51 | didSet { 52 | label.textColor = textColor 53 | } 54 | } 55 | 56 | override var backgroundColor: UIColor? { 57 | get { 58 | return .clear 59 | } 60 | set { 61 | styleColor = newValue 62 | 63 | applyStyle() 64 | } 65 | } 66 | 67 | var borderColor: UIColor? { 68 | didSet { 69 | applyStyle() 70 | } 71 | } 72 | 73 | var borderWidth: CGFloat = 1.0 { 74 | didSet { 75 | applyStyle() 76 | } 77 | } 78 | 79 | var font: UIFont? { 80 | didSet { 81 | self.label.font = font 82 | } 83 | } 84 | 85 | var textAlignment: NSTextAlignment = .center { 86 | didSet { 87 | label.textAlignment = textAlignment 88 | } 89 | } 90 | 91 | var rotationInRadians: CGFloat = 0 { 92 | didSet { 93 | self.setNeedsLayout() 94 | } 95 | } 96 | 97 | init() { 98 | super.init(frame: CGRect.zero) 99 | 100 | self.layer.addSublayer(self.shapeLayer) 101 | } 102 | 103 | required init?(coder aDecoder: NSCoder) { 104 | fatalError("init(coder:) has not been implemented") 105 | } 106 | 107 | override func layoutSubviews() { 108 | super.layoutSubviews() 109 | 110 | self.applyStyle() 111 | label.removeFromSuperview() 112 | 113 | self.label.frame = bounds 114 | self.label.transform = CGAffineTransform(rotationAngle: self.rotationInRadians) 115 | self.label.frame = bounds 116 | self.addSubview(label) 117 | } 118 | 119 | func applyStyle() { 120 | let bgColor = styleColor ?? UIColor.clear 121 | let sLayer: CAShapeLayer 122 | 123 | if let borderColor = borderColor { 124 | sLayer = CustomShapeLayer.createShape(style: style, bounds: bounds, color: bgColor, borderColor: borderColor, borderWidth: borderWidth) 125 | } 126 | else { 127 | sLayer = CustomShapeLayer.createShape(style: style, bounds: bounds, color: bgColor) 128 | } 129 | 130 | if self.shapeLayer.superlayer != nil { 131 | self.layer.replaceSublayer(shapeLayer, with: sLayer) 132 | } 133 | 134 | self.shapeLayer = sLayer 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Sources/UIBuilder.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * SnappingStepper 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import UIKit 28 | 29 | final class UIBuilder { 30 | static func defaultLabel() -> UILabel { 31 | let label = UILabel() 32 | label.textAlignment = .center 33 | label.isUserInteractionEnabled = true 34 | 35 | return label 36 | } 37 | 38 | static func defaultStyledLabel() -> StyledLabel { 39 | let label = StyledLabel() 40 | label.textAlignment = .center 41 | label.text = "" 42 | 43 | return label 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Sources/UITouchGestureRecognizer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * SnappingStepper 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import UIKit 28 | import UIKit.UIGestureRecognizerSubclass 29 | 30 | /// Gesture to know whether the touch is inside the view 31 | final class UITouchGestureRecognizer: UIGestureRecognizer { 32 | var isTouchInside = true 33 | 34 | override func touchesBegan(_ touches: Set, with event: UIEvent) { 35 | if state == .possible { 36 | state = .began 37 | } 38 | } 39 | 40 | override func touchesMoved(_ touches: Set, with event: UIEvent) { 41 | if let touch = touches.first, let view = view { 42 | let touchLocation = touch.location(in: view) 43 | let touchAreaRect = view.bounds.insetBy(dx: -10, dy: -10) 44 | 45 | let isInside = touchAreaRect.contains(touchLocation) 46 | 47 | if !isTouchInside && isInside { 48 | isTouchInside = true 49 | 50 | state = .changed 51 | } 52 | else if isTouchInside && !isInside { 53 | isTouchInside = false 54 | 55 | state = .changed 56 | } 57 | } 58 | } 59 | 60 | override func touchesEnded(_ touches: Set, with event: UIEvent) { 61 | state = .failed 62 | } 63 | 64 | override func touchesCancelled(_ touches: Set, with event: UIEvent) { 65 | state = .failed 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Tests/SnappingStepperTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SnappingStepperTests.swift 3 | // SnappingStepperExample 4 | // 5 | // Created by Yannick LORIOT on 29/05/15. 6 | // Copyright (c) 2015 Yannick Loriot. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class SnappingStepperTests: XCTestCase { 13 | func testDefaultValues() { 14 | let stepper = SnappingStepper() 15 | 16 | XCTAssert(stepper.continuous, "'continuous' attributes should be false by default") 17 | XCTAssert(stepper.autorepeat, "'autorepeat' attributes should be false by default") 18 | XCTAssert(!stepper.wraps, "'wraps' attributes should be false by default") 19 | XCTAssert(stepper.minimumValue == 0, "'minimumValue' attributes should be equal to 0 by default") 20 | XCTAssert(stepper.maximumValue == 100, "'maximumValue' attributes should be equal to 100 by default") 21 | XCTAssert(stepper.stepValue == 1, "'stepValue' attributes should be equal to 1 by default") 22 | XCTAssert(stepper.value == 0, "'value' attributes should be equal to 0 by default") 23 | } 24 | 25 | func testMinimumValue() { 26 | let stepper = SnappingStepper() 27 | 28 | XCTAssert(stepper.minimumValue <= stepper.maximumValue, "'minimum' value should always be lower or equal than the 'maximum' value") 29 | 30 | stepper.maximumValue = 50 31 | stepper.minimumValue = 50 32 | 33 | XCTAssert(stepper.minimumValue <= stepper.maximumValue, "'minimum' value should always be lower or equal than the 'maximum' value") 34 | XCTAssert(stepper.minimumValue <= stepper.value, "value should not be lower than the 'minimum' value") 35 | 36 | stepper.maximumValue = -10 37 | 38 | XCTAssert(stepper.minimumValue <= stepper.maximumValue, "'minimum' value should always be lower or equal than the 'maximum' value") 39 | XCTAssert(stepper.minimumValue == -10, "'minimum' value should be equal to -10") 40 | XCTAssert(stepper.minimumValue <= stepper.value, "value should not be lower than the 'minimum' value") 41 | 42 | stepper.value = -200 43 | 44 | XCTAssert(stepper.minimumValue <= stepper.value, "value should not be lower than the 'minimum' value") 45 | 46 | stepper.minimumValue = 0 47 | 48 | XCTAssert(stepper.minimumValue <= stepper.value, "value should not be lower than the 'minimum' value") 49 | } 50 | 51 | func testMaximumValue() { 52 | let stepper = SnappingStepper() 53 | 54 | XCTAssert(stepper.maximumValue >= stepper.minimumValue, "'maximum' value should always be greater or equal than the 'minimum' value") 55 | 56 | stepper.minimumValue = 50 57 | stepper.maximumValue = 40 58 | 59 | XCTAssert(stepper.maximumValue >= stepper.minimumValue, "'maximum' value should always be greater or equal than the 'minimum' value") 60 | XCTAssert(stepper.value <= stepper.maximumValue, "value should not be greater than the 'maximum' value") 61 | 62 | stepper.minimumValue = 200 63 | 64 | XCTAssert(stepper.maximumValue >= stepper.minimumValue, "'maximum' value should always be greater or equal than the 'minimum' value") 65 | XCTAssert(stepper.maximumValue == 200, "'maximum' value should be equal to 200") 66 | XCTAssert(stepper.value <= stepper.maximumValue, "value should not be greater than the 'maximum' value") 67 | 68 | stepper.value = 300 69 | 70 | XCTAssert(stepper.value <= stepper.maximumValue, "value should not be greater than the 'maximum' value") 71 | 72 | stepper.maximumValue = -10 73 | 74 | XCTAssert(stepper.value <= stepper.maximumValue, "value should not be greater than the 'maximum' value") 75 | } 76 | 77 | func testWrap() { 78 | let stepper = SnappingStepper() 79 | 80 | stepper.wraps = false 81 | stepper.maximumValue = 100 82 | stepper.minimumValue = 0 83 | stepper.value = 105 84 | 85 | XCTAssert(stepper.value == 100, "'value' should be equal to the 'maximum' value") 86 | 87 | stepper.value = -4 88 | 89 | XCTAssert(stepper.value == 0, "'value' should be equal to the 'minimum' value") 90 | 91 | stepper.wraps = true 92 | stepper.value = 105 93 | 94 | XCTAssert(stepper.value == 0, "'value' should be equal to the 'minimum' value") 95 | 96 | stepper.value = -4 97 | 98 | XCTAssert(stepper.value == 100, "'value' should be equal to the 'maximum' value") 99 | } 100 | 101 | func testContinuous() { 102 | let expect = expectation(description: "Value changed") 103 | 104 | let stepper = SnappingStepper() 105 | var changeCount = 0 106 | 107 | stepper.continuous = true 108 | stepper.valueChangedBlock = { (value) in 109 | changeCount += 1 110 | 111 | if changeCount == 2 { 112 | expect.fulfill() 113 | } 114 | } 115 | 116 | stepper.value = 10 117 | stepper.value = 10 118 | stepper.value = 11 119 | 120 | waitForExpectations(timeout: 0.1) { (error) in } 121 | } 122 | 123 | func testNonContinuous() { 124 | let expect = expectation(description: "Value changed") 125 | 126 | let stepper = SnappingStepper() 127 | var changeCount = 0 128 | 129 | stepper.continuous = false 130 | stepper.valueChangedBlock = { (value) in 131 | changeCount += 1 132 | 133 | if changeCount == 2 { 134 | expect.fulfill() 135 | } 136 | } 137 | 138 | stepper.updateValue(value: 10, finished: false) 139 | stepper.updateValue(value: 10, finished: true) 140 | stepper.updateValue(value: 11, finished: false) 141 | stepper.updateValue(value: 12, finished: false) 142 | stepper.updateValue(value: 13, finished: false) 143 | stepper.updateValue(value: 14, finished: true) 144 | 145 | waitForExpectations(timeout: 0.1) { (error) in } 146 | } 147 | 148 | func testThumbLabelTextValue() { 149 | let stepper = SnappingStepper() 150 | stepper.value = 100 151 | 152 | XCTAssert(stepper.value == 100, "'value' should be equal to the 'maximum' value") 153 | XCTAssert(stepper.thumbLabel.text == "", "'thumbLabel.text' should be equal to empty string") 154 | 155 | stepper.thumbText = nil 156 | XCTAssert(stepper.thumbLabel.text == "100", "'thumbLabel.text' should be equal to \"100\"") 157 | 158 | stepper.value = 50 159 | XCTAssert(stepper.thumbLabel.text == "50", "'thumbLabel.text' should be equal to \"50\"") 160 | 161 | stepper.value = 50.0 162 | XCTAssert(stepper.thumbLabel.text == "50", "'thumbLabel.text' should be equal to \"50\"") 163 | 164 | stepper.value = 50.2 165 | XCTAssert(stepper.thumbLabel.text == "50.2", "'thumbLabel.text' should be equal to \"50.2\"") 166 | 167 | stepper.value = 150 168 | XCTAssert(stepper.thumbLabel.text == "100", "'thumbLabel.text' should be equal to \"100\"") 169 | 170 | stepper.thumbText = "Move Me!" 171 | XCTAssert(stepper.thumbLabel.text == "Move Me!", "'thumbLabel.text' should be equal to \"Move Me!\"") 172 | 173 | stepper.value = 50 174 | XCTAssert(stepper.thumbLabel.text == "Move Me!", "'thumbLabel.text' should be equal to \"Move Me!\"") 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | ignore: 3 | - Example/* 4 | - Tests/* 5 | --------------------------------------------------------------------------------