├── .github └── FUNDING.yml ├── .gitignore ├── .swiftpm └── xcode │ ├── package.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── edgaraszigis.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ └── edgaraszigis.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── GaugeSlider.podspec ├── GaugeSlider.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings │ └── xcuserdata │ │ └── edgaraszigis.xcuserdatad │ │ ├── UserInterfaceState.xcuserstate │ │ └── WorkspaceSettings.xcsettings ├── xcshareddata │ └── xcschemes │ │ ├── GaugeSlider.xcscheme │ │ └── GaugeSliderTest.xcscheme └── xcuserdata │ └── edgaraszigis.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── GaugeSlider │ ├── Common │ ├── CgPath+Elements.swift │ ├── ProgressCounter.swift │ └── UIBezierPath+FirstPoint.swift │ ├── GaugeSlider.h │ ├── GaugeSliderView.swift │ └── Info.plist ├── Tests └── GaugeSliderTests │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── snowIcon.imageset │ │ ├── Contents.json │ │ └── snowFlakeIcon.pdf │ └── sunIcon.imageset │ │ ├── Contents.json │ │ └── sunIcon.pdf │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ ├── ViewController.swift │ └── ViewControllerWithoutStoryBoard.swift └── sample.gif /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: edgar-zigis 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcuserdata/edgaraszigis.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edgar-zigis/GaugeSlider/729a11d962bfbfbddb971ac5e36fceb782e97770/.swiftpm/xcode/package.xcworkspace/xcuserdata/edgaraszigis.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /.swiftpm/xcode/xcuserdata/edgaraszigis.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | GaugeSlider.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | GaugeSlider 16 | 17 | primary 18 | 19 | 20 | GaugeSliderTests 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /GaugeSlider.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = "GaugeSlider" 3 | spec.version = "1.2.1" 4 | spec.summary = "Highly customizable GaugeSlider primarily designed for a Smart Home app." 5 | 6 | spec.homepage = "https://github.com/edgar-zigis/GaugeSlider" 7 | spec.screenshots = "https://raw.githubusercontent.com/edgar-zigis/GaugeSlider/master/sample.gif" 8 | 9 | 10 | spec.license = { :type => 'MIT', :file => './LICENSE' } 11 | 12 | spec.author = "Edgar Žigis" 13 | 14 | spec.platform = :ios 15 | spec.ios.deployment_target = '11.0' 16 | spec.swift_version = '5.0' 17 | 18 | spec.source = { :git => "https://github.com/edgar-zigis/GaugeSlider.git", :tag => "#{spec.version}" } 19 | 20 | spec.source_files = "Sources/GaugeSlider/**/*.{swift}" 21 | end 22 | -------------------------------------------------------------------------------- /GaugeSlider.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C03481162496456100A942B1 /* ProgressCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C03481052496456100A942B1 /* ProgressCounter.swift */; }; 11 | C03481182496456100A942B1 /* ProgressCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C03481052496456100A942B1 /* ProgressCounter.swift */; }; 12 | C03481192496456100A942B1 /* CgPath+Elements.swift in Sources */ = {isa = PBXBuildFile; fileRef = C03481062496456100A942B1 /* CgPath+Elements.swift */; }; 13 | C034811B2496456100A942B1 /* CgPath+Elements.swift in Sources */ = {isa = PBXBuildFile; fileRef = C03481062496456100A942B1 /* CgPath+Elements.swift */; }; 14 | C034811C2496456100A942B1 /* UIBezierPath+FirstPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = C03481072496456100A942B1 /* UIBezierPath+FirstPoint.swift */; }; 15 | C034811E2496456100A942B1 /* UIBezierPath+FirstPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = C03481072496456100A942B1 /* UIBezierPath+FirstPoint.swift */; }; 16 | C034811F2496456100A942B1 /* GaugeSliderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C03481082496456100A942B1 /* GaugeSliderView.swift */; }; 17 | C03481212496456100A942B1 /* GaugeSliderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C03481082496456100A942B1 /* GaugeSliderView.swift */; }; 18 | C034813A249645C200A942B1 /* GaugeSlider.h in Headers */ = {isa = PBXBuildFile; fileRef = C03481032496456100A942B1 /* GaugeSlider.h */; settings = {ATTRIBUTES = (Public, ); }; }; 19 | C034814A249647D300A942B1 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0348141249647D300A942B1 /* ViewController.swift */; }; 20 | C034814E249647D300A942B1 /* ViewControllerWithoutStoryBoard.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0348147249647D300A942B1 /* ViewControllerWithoutStoryBoard.swift */; }; 21 | C034814F249647D300A942B1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0348148249647D300A942B1 /* AppDelegate.swift */; }; 22 | C0348151249647FF00A942B1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C0348142249647D300A942B1 /* Assets.xcassets */; }; 23 | C0348152249647FF00A942B1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C0348143249647D300A942B1 /* LaunchScreen.storyboard */; }; 24 | C0348153249647FF00A942B1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C0348145249647D300A942B1 /* Main.storyboard */; }; 25 | C0513886231084B90055FD0B /* GaugeSlider.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C051384723106DF50055FD0B /* GaugeSlider.framework */; }; 26 | C0513887231084B90055FD0B /* GaugeSlider.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C051384723106DF50055FD0B /* GaugeSlider.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 27 | /* End PBXBuildFile section */ 28 | 29 | /* Begin PBXContainerItemProxy section */ 30 | C05138842310838A0055FD0B /* PBXContainerItemProxy */ = { 31 | isa = PBXContainerItemProxy; 32 | containerPortal = C051383E23106DF50055FD0B /* Project object */; 33 | proxyType = 1; 34 | remoteGlobalIDString = C051384623106DF50055FD0B; 35 | remoteInfo = GaugeSlider; 36 | }; 37 | /* End PBXContainerItemProxy section */ 38 | 39 | /* Begin PBXCopyFilesBuildPhase section */ 40 | C0513888231084B90055FD0B /* Embed Frameworks */ = { 41 | isa = PBXCopyFilesBuildPhase; 42 | buildActionMask = 2147483647; 43 | dstPath = ""; 44 | dstSubfolderSpec = 10; 45 | files = ( 46 | C0513887231084B90055FD0B /* GaugeSlider.framework in Embed Frameworks */, 47 | ); 48 | name = "Embed Frameworks"; 49 | runOnlyForDeploymentPostprocessing = 0; 50 | }; 51 | /* End PBXCopyFilesBuildPhase section */ 52 | 53 | /* Begin PBXFileReference section */ 54 | C03481032496456100A942B1 /* GaugeSlider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GaugeSlider.h; sourceTree = ""; }; 55 | C03481052496456100A942B1 /* ProgressCounter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressCounter.swift; sourceTree = ""; }; 56 | C03481062496456100A942B1 /* CgPath+Elements.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CgPath+Elements.swift"; sourceTree = ""; }; 57 | C03481072496456100A942B1 /* UIBezierPath+FirstPoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIBezierPath+FirstPoint.swift"; sourceTree = ""; }; 58 | C03481082496456100A942B1 /* GaugeSliderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GaugeSliderView.swift; sourceTree = ""; }; 59 | C03481092496456100A942B1 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 60 | C0348141249647D300A942B1 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 61 | C0348142249647D300A942B1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 62 | C0348144249647D300A942B1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 63 | C0348146249647D300A942B1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 64 | C0348147249647D300A942B1 /* ViewControllerWithoutStoryBoard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewControllerWithoutStoryBoard.swift; sourceTree = ""; }; 65 | C0348148249647D300A942B1 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 66 | C0348149249647D300A942B1 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 67 | C051384723106DF50055FD0B /* GaugeSlider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GaugeSlider.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 68 | C051386E231075790055FD0B /* GaugeSliderTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GaugeSliderTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; 69 | /* End PBXFileReference section */ 70 | 71 | /* Begin PBXFrameworksBuildPhase section */ 72 | C051384423106DF50055FD0B /* Frameworks */ = { 73 | isa = PBXFrameworksBuildPhase; 74 | buildActionMask = 2147483647; 75 | files = ( 76 | ); 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | C051386B231075790055FD0B /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | C0513886231084B90055FD0B /* GaugeSlider.framework in Frameworks */, 84 | ); 85 | runOnlyForDeploymentPostprocessing = 0; 86 | }; 87 | /* End PBXFrameworksBuildPhase section */ 88 | 89 | /* Begin PBXGroup section */ 90 | C03481012496456100A942B1 /* Sources */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | C03481022496456100A942B1 /* GaugeSlider */, 94 | ); 95 | path = Sources; 96 | sourceTree = ""; 97 | }; 98 | C03481022496456100A942B1 /* GaugeSlider */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | C03481032496456100A942B1 /* GaugeSlider.h */, 102 | C03481042496456100A942B1 /* Common */, 103 | C03481082496456100A942B1 /* GaugeSliderView.swift */, 104 | C03481092496456100A942B1 /* Info.plist */, 105 | ); 106 | path = GaugeSlider; 107 | sourceTree = ""; 108 | }; 109 | C03481042496456100A942B1 /* Common */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | C03481052496456100A942B1 /* ProgressCounter.swift */, 113 | C03481062496456100A942B1 /* CgPath+Elements.swift */, 114 | C03481072496456100A942B1 /* UIBezierPath+FirstPoint.swift */, 115 | ); 116 | path = Common; 117 | sourceTree = ""; 118 | }; 119 | C034813F249647D300A942B1 /* Tests */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | C0348140249647D300A942B1 /* GaugeSliderTests */, 123 | ); 124 | path = Tests; 125 | sourceTree = ""; 126 | }; 127 | C0348140249647D300A942B1 /* GaugeSliderTests */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | C0348141249647D300A942B1 /* ViewController.swift */, 131 | C0348142249647D300A942B1 /* Assets.xcassets */, 132 | C0348143249647D300A942B1 /* LaunchScreen.storyboard */, 133 | C0348145249647D300A942B1 /* Main.storyboard */, 134 | C0348147249647D300A942B1 /* ViewControllerWithoutStoryBoard.swift */, 135 | C0348148249647D300A942B1 /* AppDelegate.swift */, 136 | C0348149249647D300A942B1 /* Info.plist */, 137 | ); 138 | path = GaugeSliderTests; 139 | sourceTree = ""; 140 | }; 141 | C051383D23106DF50055FD0B = { 142 | isa = PBXGroup; 143 | children = ( 144 | C03481012496456100A942B1 /* Sources */, 145 | C034813F249647D300A942B1 /* Tests */, 146 | C051384823106DF50055FD0B /* Products */, 147 | C0513882231081DC0055FD0B /* Frameworks */, 148 | ); 149 | sourceTree = ""; 150 | }; 151 | C051384823106DF50055FD0B /* Products */ = { 152 | isa = PBXGroup; 153 | children = ( 154 | C051384723106DF50055FD0B /* GaugeSlider.framework */, 155 | C051386E231075790055FD0B /* GaugeSliderTest.app */, 156 | ); 157 | name = Products; 158 | sourceTree = ""; 159 | }; 160 | C0513882231081DC0055FD0B /* Frameworks */ = { 161 | isa = PBXGroup; 162 | children = ( 163 | ); 164 | name = Frameworks; 165 | sourceTree = ""; 166 | }; 167 | /* End PBXGroup section */ 168 | 169 | /* Begin PBXHeadersBuildPhase section */ 170 | C051384223106DF50055FD0B /* Headers */ = { 171 | isa = PBXHeadersBuildPhase; 172 | buildActionMask = 2147483647; 173 | files = ( 174 | C034813A249645C200A942B1 /* GaugeSlider.h in Headers */, 175 | ); 176 | runOnlyForDeploymentPostprocessing = 0; 177 | }; 178 | /* End PBXHeadersBuildPhase section */ 179 | 180 | /* Begin PBXNativeTarget section */ 181 | C051384623106DF50055FD0B /* GaugeSlider */ = { 182 | isa = PBXNativeTarget; 183 | buildConfigurationList = C051385B23106DF50055FD0B /* Build configuration list for PBXNativeTarget "GaugeSlider" */; 184 | buildPhases = ( 185 | C051384223106DF50055FD0B /* Headers */, 186 | C051384323106DF50055FD0B /* Sources */, 187 | C051384423106DF50055FD0B /* Frameworks */, 188 | C051384523106DF50055FD0B /* Resources */, 189 | ); 190 | buildRules = ( 191 | ); 192 | dependencies = ( 193 | ); 194 | name = GaugeSlider; 195 | productName = GaugeSlider; 196 | productReference = C051384723106DF50055FD0B /* GaugeSlider.framework */; 197 | productType = "com.apple.product-type.framework"; 198 | }; 199 | C051386D231075790055FD0B /* GaugeSliderTest */ = { 200 | isa = PBXNativeTarget; 201 | buildConfigurationList = C051387F2310757A0055FD0B /* Build configuration list for PBXNativeTarget "GaugeSliderTest" */; 202 | buildPhases = ( 203 | C051386A231075790055FD0B /* Sources */, 204 | C051386B231075790055FD0B /* Frameworks */, 205 | C051386C231075790055FD0B /* Resources */, 206 | C0513888231084B90055FD0B /* Embed Frameworks */, 207 | ); 208 | buildRules = ( 209 | ); 210 | dependencies = ( 211 | C05138852310838A0055FD0B /* PBXTargetDependency */, 212 | ); 213 | name = GaugeSliderTest; 214 | productName = GaugeSliderTest; 215 | productReference = C051386E231075790055FD0B /* GaugeSliderTest.app */; 216 | productType = "com.apple.product-type.application"; 217 | }; 218 | /* End PBXNativeTarget section */ 219 | 220 | /* Begin PBXProject section */ 221 | C051383E23106DF50055FD0B /* Project object */ = { 222 | isa = PBXProject; 223 | attributes = { 224 | LastSwiftUpdateCheck = 1030; 225 | LastUpgradeCheck = 1030; 226 | ORGANIZATIONNAME = "Edgar Žigis"; 227 | TargetAttributes = { 228 | C051384623106DF50055FD0B = { 229 | CreatedOnToolsVersion = 10.3; 230 | LastSwiftMigration = 1030; 231 | }; 232 | C051386D231075790055FD0B = { 233 | CreatedOnToolsVersion = 10.3; 234 | }; 235 | }; 236 | }; 237 | buildConfigurationList = C051384123106DF50055FD0B /* Build configuration list for PBXProject "GaugeSlider" */; 238 | compatibilityVersion = "Xcode 9.3"; 239 | developmentRegion = en; 240 | hasScannedForEncodings = 0; 241 | knownRegions = ( 242 | en, 243 | Base, 244 | ); 245 | mainGroup = C051383D23106DF50055FD0B; 246 | productRefGroup = C051384823106DF50055FD0B /* Products */; 247 | projectDirPath = ""; 248 | projectRoot = ""; 249 | targets = ( 250 | C051384623106DF50055FD0B /* GaugeSlider */, 251 | C051386D231075790055FD0B /* GaugeSliderTest */, 252 | ); 253 | }; 254 | /* End PBXProject section */ 255 | 256 | /* Begin PBXResourcesBuildPhase section */ 257 | C051384523106DF50055FD0B /* Resources */ = { 258 | isa = PBXResourcesBuildPhase; 259 | buildActionMask = 2147483647; 260 | files = ( 261 | ); 262 | runOnlyForDeploymentPostprocessing = 0; 263 | }; 264 | C051386C231075790055FD0B /* Resources */ = { 265 | isa = PBXResourcesBuildPhase; 266 | buildActionMask = 2147483647; 267 | files = ( 268 | C0348151249647FF00A942B1 /* Assets.xcassets in Resources */, 269 | C0348152249647FF00A942B1 /* LaunchScreen.storyboard in Resources */, 270 | C0348153249647FF00A942B1 /* Main.storyboard in Resources */, 271 | ); 272 | runOnlyForDeploymentPostprocessing = 0; 273 | }; 274 | /* End PBXResourcesBuildPhase section */ 275 | 276 | /* Begin PBXSourcesBuildPhase section */ 277 | C051384323106DF50055FD0B /* Sources */ = { 278 | isa = PBXSourcesBuildPhase; 279 | buildActionMask = 2147483647; 280 | files = ( 281 | C03481162496456100A942B1 /* ProgressCounter.swift in Sources */, 282 | C03481192496456100A942B1 /* CgPath+Elements.swift in Sources */, 283 | C034811C2496456100A942B1 /* UIBezierPath+FirstPoint.swift in Sources */, 284 | C034811F2496456100A942B1 /* GaugeSliderView.swift in Sources */, 285 | ); 286 | runOnlyForDeploymentPostprocessing = 0; 287 | }; 288 | C051386A231075790055FD0B /* Sources */ = { 289 | isa = PBXSourcesBuildPhase; 290 | buildActionMask = 2147483647; 291 | files = ( 292 | C03481182496456100A942B1 /* ProgressCounter.swift in Sources */, 293 | C034814E249647D300A942B1 /* ViewControllerWithoutStoryBoard.swift in Sources */, 294 | C034811B2496456100A942B1 /* CgPath+Elements.swift in Sources */, 295 | C034811E2496456100A942B1 /* UIBezierPath+FirstPoint.swift in Sources */, 296 | C034814A249647D300A942B1 /* ViewController.swift in Sources */, 297 | C034814F249647D300A942B1 /* AppDelegate.swift in Sources */, 298 | C03481212496456100A942B1 /* GaugeSliderView.swift in Sources */, 299 | ); 300 | runOnlyForDeploymentPostprocessing = 0; 301 | }; 302 | /* End PBXSourcesBuildPhase section */ 303 | 304 | /* Begin PBXTargetDependency section */ 305 | C05138852310838A0055FD0B /* PBXTargetDependency */ = { 306 | isa = PBXTargetDependency; 307 | target = C051384623106DF50055FD0B /* GaugeSlider */; 308 | targetProxy = C05138842310838A0055FD0B /* PBXContainerItemProxy */; 309 | }; 310 | /* End PBXTargetDependency section */ 311 | 312 | /* Begin PBXVariantGroup section */ 313 | C0348143249647D300A942B1 /* LaunchScreen.storyboard */ = { 314 | isa = PBXVariantGroup; 315 | children = ( 316 | C0348144249647D300A942B1 /* Base */, 317 | ); 318 | name = LaunchScreen.storyboard; 319 | sourceTree = ""; 320 | }; 321 | C0348145249647D300A942B1 /* Main.storyboard */ = { 322 | isa = PBXVariantGroup; 323 | children = ( 324 | C0348146249647D300A942B1 /* Base */, 325 | ); 326 | name = Main.storyboard; 327 | sourceTree = ""; 328 | }; 329 | /* End PBXVariantGroup section */ 330 | 331 | /* Begin XCBuildConfiguration section */ 332 | C051385923106DF50055FD0B /* Debug */ = { 333 | isa = XCBuildConfiguration; 334 | buildSettings = { 335 | ALWAYS_SEARCH_USER_PATHS = NO; 336 | CLANG_ANALYZER_NONNULL = YES; 337 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 338 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 339 | CLANG_CXX_LIBRARY = "libc++"; 340 | CLANG_ENABLE_MODULES = YES; 341 | CLANG_ENABLE_OBJC_ARC = YES; 342 | CLANG_ENABLE_OBJC_WEAK = YES; 343 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 344 | CLANG_WARN_BOOL_CONVERSION = YES; 345 | CLANG_WARN_COMMA = YES; 346 | CLANG_WARN_CONSTANT_CONVERSION = YES; 347 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 348 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 349 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 350 | CLANG_WARN_EMPTY_BODY = YES; 351 | CLANG_WARN_ENUM_CONVERSION = YES; 352 | CLANG_WARN_INFINITE_RECURSION = YES; 353 | CLANG_WARN_INT_CONVERSION = YES; 354 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 355 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 356 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 357 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 358 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 359 | CLANG_WARN_STRICT_PROTOTYPES = YES; 360 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 361 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 362 | CLANG_WARN_UNREACHABLE_CODE = YES; 363 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 364 | CODE_SIGN_IDENTITY = "iPhone Developer"; 365 | COPY_PHASE_STRIP = NO; 366 | CURRENT_PROJECT_VERSION = 1; 367 | DEBUG_INFORMATION_FORMAT = dwarf; 368 | ENABLE_STRICT_OBJC_MSGSEND = YES; 369 | ENABLE_TESTABILITY = YES; 370 | GCC_C_LANGUAGE_STANDARD = gnu11; 371 | GCC_DYNAMIC_NO_PIC = NO; 372 | GCC_NO_COMMON_BLOCKS = YES; 373 | GCC_OPTIMIZATION_LEVEL = 0; 374 | GCC_PREPROCESSOR_DEFINITIONS = ( 375 | "DEBUG=1", 376 | "$(inherited)", 377 | ); 378 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 379 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 380 | GCC_WARN_UNDECLARED_SELECTOR = YES; 381 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 382 | GCC_WARN_UNUSED_FUNCTION = YES; 383 | GCC_WARN_UNUSED_VARIABLE = YES; 384 | IPHONEOS_DEPLOYMENT_TARGET = 13.5; 385 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 386 | MTL_FAST_MATH = YES; 387 | ONLY_ACTIVE_ARCH = YES; 388 | SDKROOT = iphoneos; 389 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 390 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 391 | VERSIONING_SYSTEM = "apple-generic"; 392 | VERSION_INFO_PREFIX = ""; 393 | }; 394 | name = Debug; 395 | }; 396 | C051385A23106DF50055FD0B /* Release */ = { 397 | isa = XCBuildConfiguration; 398 | buildSettings = { 399 | ALWAYS_SEARCH_USER_PATHS = NO; 400 | CLANG_ANALYZER_NONNULL = YES; 401 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 402 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 403 | CLANG_CXX_LIBRARY = "libc++"; 404 | CLANG_ENABLE_MODULES = YES; 405 | CLANG_ENABLE_OBJC_ARC = YES; 406 | CLANG_ENABLE_OBJC_WEAK = YES; 407 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 408 | CLANG_WARN_BOOL_CONVERSION = YES; 409 | CLANG_WARN_COMMA = YES; 410 | CLANG_WARN_CONSTANT_CONVERSION = YES; 411 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 412 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 413 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 414 | CLANG_WARN_EMPTY_BODY = YES; 415 | CLANG_WARN_ENUM_CONVERSION = YES; 416 | CLANG_WARN_INFINITE_RECURSION = YES; 417 | CLANG_WARN_INT_CONVERSION = YES; 418 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 419 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 420 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 421 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 422 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 423 | CLANG_WARN_STRICT_PROTOTYPES = YES; 424 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 425 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 426 | CLANG_WARN_UNREACHABLE_CODE = YES; 427 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 428 | CODE_SIGN_IDENTITY = "iPhone Developer"; 429 | COPY_PHASE_STRIP = NO; 430 | CURRENT_PROJECT_VERSION = 1; 431 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 432 | ENABLE_NS_ASSERTIONS = NO; 433 | ENABLE_STRICT_OBJC_MSGSEND = YES; 434 | GCC_C_LANGUAGE_STANDARD = gnu11; 435 | GCC_NO_COMMON_BLOCKS = YES; 436 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 437 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 438 | GCC_WARN_UNDECLARED_SELECTOR = YES; 439 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 440 | GCC_WARN_UNUSED_FUNCTION = YES; 441 | GCC_WARN_UNUSED_VARIABLE = YES; 442 | IPHONEOS_DEPLOYMENT_TARGET = 13.5; 443 | MTL_ENABLE_DEBUG_INFO = NO; 444 | MTL_FAST_MATH = YES; 445 | SDKROOT = iphoneos; 446 | SWIFT_COMPILATION_MODE = wholemodule; 447 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 448 | VALIDATE_PRODUCT = YES; 449 | VERSIONING_SYSTEM = "apple-generic"; 450 | VERSION_INFO_PREFIX = ""; 451 | }; 452 | name = Release; 453 | }; 454 | C051385C23106DF50055FD0B /* Debug */ = { 455 | isa = XCBuildConfiguration; 456 | buildSettings = { 457 | CLANG_ENABLE_MODULES = YES; 458 | CODE_SIGN_IDENTITY = ""; 459 | CODE_SIGN_STYLE = Automatic; 460 | DEFINES_MODULE = YES; 461 | DEVELOPMENT_TEAM = 22JT5762MG; 462 | DYLIB_COMPATIBILITY_VERSION = 1; 463 | DYLIB_CURRENT_VERSION = 1; 464 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 465 | INFOPLIST_FILE = Sources/GaugeSlider/Info.plist; 466 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 467 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 468 | LD_RUNPATH_SEARCH_PATHS = ( 469 | "$(inherited)", 470 | "@executable_path/Frameworks", 471 | "@loader_path/Frameworks", 472 | ); 473 | PRODUCT_BUNDLE_IDENTIFIER = com.zigis.GaugeSlider; 474 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 475 | SKIP_INSTALL = YES; 476 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 477 | SWIFT_VERSION = 5.0; 478 | TARGETED_DEVICE_FAMILY = "1,2"; 479 | }; 480 | name = Debug; 481 | }; 482 | C051385D23106DF50055FD0B /* Release */ = { 483 | isa = XCBuildConfiguration; 484 | buildSettings = { 485 | CLANG_ENABLE_MODULES = YES; 486 | CODE_SIGN_IDENTITY = ""; 487 | CODE_SIGN_STYLE = Automatic; 488 | DEFINES_MODULE = YES; 489 | DEVELOPMENT_TEAM = 22JT5762MG; 490 | DYLIB_COMPATIBILITY_VERSION = 1; 491 | DYLIB_CURRENT_VERSION = 1; 492 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 493 | INFOPLIST_FILE = Sources/GaugeSlider/Info.plist; 494 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 495 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 496 | LD_RUNPATH_SEARCH_PATHS = ( 497 | "$(inherited)", 498 | "@executable_path/Frameworks", 499 | "@loader_path/Frameworks", 500 | ); 501 | PRODUCT_BUNDLE_IDENTIFIER = com.zigis.GaugeSlider; 502 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 503 | SKIP_INSTALL = YES; 504 | SWIFT_VERSION = 5.0; 505 | TARGETED_DEVICE_FAMILY = "1,2"; 506 | }; 507 | name = Release; 508 | }; 509 | C051387D2310757A0055FD0B /* Debug */ = { 510 | isa = XCBuildConfiguration; 511 | buildSettings = { 512 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 513 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 514 | CODE_SIGN_STYLE = Automatic; 515 | DEVELOPMENT_TEAM = 22JT5762MG; 516 | INFOPLIST_FILE = Tests/GaugeSliderTests/Info.plist; 517 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 518 | LD_RUNPATH_SEARCH_PATHS = ( 519 | "$(inherited)", 520 | "@executable_path/Frameworks", 521 | ); 522 | PRODUCT_BUNDLE_IDENTIFIER = com.zigis.GaugeSliderTest; 523 | PRODUCT_NAME = "$(TARGET_NAME)"; 524 | SWIFT_VERSION = 5.0; 525 | TARGETED_DEVICE_FAMILY = "1,2"; 526 | }; 527 | name = Debug; 528 | }; 529 | C051387E2310757A0055FD0B /* Release */ = { 530 | isa = XCBuildConfiguration; 531 | buildSettings = { 532 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 533 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 534 | CODE_SIGN_STYLE = Automatic; 535 | DEVELOPMENT_TEAM = 22JT5762MG; 536 | INFOPLIST_FILE = Tests/GaugeSliderTests/Info.plist; 537 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 538 | LD_RUNPATH_SEARCH_PATHS = ( 539 | "$(inherited)", 540 | "@executable_path/Frameworks", 541 | ); 542 | PRODUCT_BUNDLE_IDENTIFIER = com.zigis.GaugeSliderTest; 543 | PRODUCT_NAME = "$(TARGET_NAME)"; 544 | SWIFT_VERSION = 5.0; 545 | TARGETED_DEVICE_FAMILY = "1,2"; 546 | }; 547 | name = Release; 548 | }; 549 | /* End XCBuildConfiguration section */ 550 | 551 | /* Begin XCConfigurationList section */ 552 | C051384123106DF50055FD0B /* Build configuration list for PBXProject "GaugeSlider" */ = { 553 | isa = XCConfigurationList; 554 | buildConfigurations = ( 555 | C051385923106DF50055FD0B /* Debug */, 556 | C051385A23106DF50055FD0B /* Release */, 557 | ); 558 | defaultConfigurationIsVisible = 0; 559 | defaultConfigurationName = Release; 560 | }; 561 | C051385B23106DF50055FD0B /* Build configuration list for PBXNativeTarget "GaugeSlider" */ = { 562 | isa = XCConfigurationList; 563 | buildConfigurations = ( 564 | C051385C23106DF50055FD0B /* Debug */, 565 | C051385D23106DF50055FD0B /* Release */, 566 | ); 567 | defaultConfigurationIsVisible = 0; 568 | defaultConfigurationName = Release; 569 | }; 570 | C051387F2310757A0055FD0B /* Build configuration list for PBXNativeTarget "GaugeSliderTest" */ = { 571 | isa = XCConfigurationList; 572 | buildConfigurations = ( 573 | C051387D2310757A0055FD0B /* Debug */, 574 | C051387E2310757A0055FD0B /* Release */, 575 | ); 576 | defaultConfigurationIsVisible = 0; 577 | defaultConfigurationName = Release; 578 | }; 579 | /* End XCConfigurationList section */ 580 | }; 581 | rootObject = C051383E23106DF50055FD0B /* Project object */; 582 | } 583 | -------------------------------------------------------------------------------- /GaugeSlider.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /GaugeSlider.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /GaugeSlider.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /GaugeSlider.xcodeproj/project.xcworkspace/xcuserdata/edgaraszigis.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edgar-zigis/GaugeSlider/729a11d962bfbfbddb971ac5e36fceb782e97770/GaugeSlider.xcodeproj/project.xcworkspace/xcuserdata/edgaraszigis.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /GaugeSlider.xcodeproj/project.xcworkspace/xcuserdata/edgaraszigis.xcuserdatad/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildLocationStyle 6 | UseAppPreferences 7 | CustomBuildLocationType 8 | RelativeToDerivedData 9 | DerivedDataLocationStyle 10 | Default 11 | IssueFilterStyle 12 | ShowActiveSchemeOnly 13 | LiveSourceIssuesEnabled 14 | 15 | ShowSharedSchemesAutomaticallyEnabled 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /GaugeSlider.xcodeproj/xcshareddata/xcschemes/GaugeSlider.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /GaugeSlider.xcodeproj/xcshareddata/xcschemes/GaugeSliderTest.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /GaugeSlider.xcodeproj/xcuserdata/edgaraszigis.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | GaugeSlider.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | GaugeSliderTest.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | C051384623106DF50055FD0B 21 | 22 | primary 23 | 24 | 25 | C051384F23106DF50055FD0B 26 | 27 | primary 28 | 29 | 30 | C051386D231075790055FD0B 31 | 32 | primary 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Edgar Žigis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "GaugeSlider", 8 | platforms: [ 9 | .iOS(.v11) 10 | ], 11 | products: [ 12 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 13 | .library( 14 | name: "GaugeSlider", 15 | targets: ["GaugeSlider"]), 16 | ], 17 | dependencies: [ 18 | // Dependencies declare other packages that this package depends on. 19 | // .package(url: /* package url */, from: "1.0.0"), 20 | ], 21 | targets: [ 22 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 23 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 24 | .target( 25 | name: "GaugeSlider", 26 | dependencies: []), 27 | .testTarget( 28 | name: "GaugeSliderTests", 29 | dependencies: ["GaugeSlider"]), 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GaugeSlider 2 | 3 | Highly customizable GaugeSlider primarily designed for a Smart Home app. 4 | ##### Minimum iOS version 11.0 5 | 6 | ![alt text](https://github.com/edgar-zigis/GaugeSlider/blob/master/sample.gif?raw=true) 7 | 8 | ### Carthage 9 | 10 | ``` 11 | github "edgar-zigis/GaugeSlider" ~> 1.2.1 12 | ``` 13 | ### Cocoapods 14 | 15 | ``` 16 | pod 'GaugeSlider', '~> 1.2.1' 17 | ``` 18 | ### Swift Package Manager 19 | 20 | ``` 21 | dependencies: [ 22 | .package(url: "https://github.com/edgar-zigis/GaugeSlider.git", .upToNextMajor(from: "1.2.1")) 23 | ] 24 | ``` 25 | ### Usage 26 | ``` swift 27 | let v = GaugeSliderView() 28 | v.blankPathColor = UIColor(red: 218/255, green: 218/255, blue: 218/255, alpha: 1) // -> inactive track color 29 | v.fillPathColor = UIColor(red: 74/255, green: 196/255, blue: 192/255, alpha: 1) // -> filled track color 30 | v.indicatorColor = UIColor(red: 94/255, green: 187/255, blue: 169/255, alpha: 1) 31 | v.unitColor = UIColor(red: 74/255, green: 74/255, blue: 74/255, alpha: 1) 32 | v.placeholderColor = UIColor(red: 139/255, green: 154/255, blue: 158/255, alpha: 1) 33 | v.unitIndicatorColor = UIColor(red: 74/255, green: 74/255, blue: 74/255, alpha: 0.2) 34 | v.customControlColor = UIColor(red: 47/255, green: 190/255, blue: 169/255, alpha: 1) 35 | v.unitFont = UIFont.systemFont(ofSize: 67) 36 | v.placeholderFont = UIFont.systemFont(ofSize: 17, weight: .medium) 37 | v.unitIndicatorFont = UIFont.systemFont(ofSize: 16, weight: .medium) 38 | v.customControlButtonTitle = "• Auto" 39 | v.isCustomControlActive = false 40 | v.customControlButtonVisible = true 41 | v.placeholder = "Warming" 42 | v.unit = "°" // -> change default unit from temperature to anything you like 43 | v.progress = 80 // -> 0..100 a way to say percentage 44 | v.value = 10 45 | v.minValue = 5 46 | v.maxValue = 25 47 | v.countingMethod = GaugeSliderCountingMethod.easeInOut // -> sliding animation style 48 | v.delegationMode = .singular // -> or .immediate(interval: Int) 49 | v.leftIcon = UIImage(named: "snowIcon") 50 | v.rightIcon = UIImage(named: "sunIcon") 51 | ``` 52 | ### Remarks 53 | It can be used both programmatically and with story boards. Samples are available at **Tests/GaugeSliderTests** 54 | -------------------------------------------------------------------------------- /Sources/GaugeSlider/Common/CgPath+Elements.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CgPath+Elements.swift 3 | // GaugeSlider 4 | // 5 | // Created by Edgar Žigis on 23/08/2019. 6 | // Copyright © 2019 Edgar Žigis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension CGPath { 12 | 13 | func forEach(body: @escaping @convention(block) (CGPathElement) -> Void) { 14 | typealias Body = @convention(block) (CGPathElement) -> Void 15 | func callback(info: UnsafeMutableRawPointer?, element: UnsafePointer) { 16 | let body = unsafeBitCast(info, to: Body.self) 17 | body(element.pointee) 18 | } 19 | let unsafeBody = unsafeBitCast(body, to: UnsafeMutableRawPointer.self) 20 | self.apply(info: unsafeBody, function: callback) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/GaugeSlider/Common/ProgressCounter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProgressCounter.swift 3 | // GaugeSlider 4 | // 5 | // Created by Edgar Žigis on 23/08/2019. 6 | // Copyright © 2019 Edgar Žigis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | let kCounterRate = Float(3.0) 12 | 13 | public protocol ProgressCounter { 14 | func update(_ t: CGFloat) -> CGFloat 15 | } 16 | 17 | public class ProgressCounterLinear: ProgressCounter { 18 | public func update(_ t: CGFloat) -> CGFloat { 19 | return t 20 | } 21 | } 22 | 23 | public class ProgressCounterEaseIn: ProgressCounter { 24 | public func update(_ t: CGFloat) -> CGFloat { 25 | return CGFloat(powf(Float(t), kCounterRate)) 26 | } 27 | } 28 | 29 | public class ProgressCounterEaseOut: ProgressCounter { 30 | public func update(_ t: CGFloat) -> CGFloat { 31 | return CGFloat(1.0 - powf(Float(1.0 - t), kCounterRate)) 32 | } 33 | } 34 | 35 | public class ProgressCounterEaseInOut: ProgressCounter { 36 | public func update(_ t: CGFloat) -> CGFloat { 37 | let newt: CGFloat = 2 * t 38 | if newt < 1 { 39 | return CGFloat(0.5 * powf (Float(newt), kCounterRate)) 40 | } else { 41 | return CGFloat(0.5 * (2.0 - powf(Float(2.0 - newt), kCounterRate))) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/GaugeSlider/Common/UIBezierPath+FirstPoint.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIBezierPath+FirstPoint.swift 3 | // GaugeSlider 4 | // 5 | // Created by Edgar Žigis on 23/08/2019. 6 | // Copyright © 2019 Edgar Žigis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIBezierPath { 12 | 13 | func firstPoint() -> CGPoint? { 14 | var firstPoint: CGPoint? = nil 15 | cgPath.forEach { element in 16 | guard firstPoint == nil else { return } 17 | assert(element.type == .moveToPoint, "Expected the first point to be a move") 18 | firstPoint = element.points.pointee 19 | } 20 | return firstPoint 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/GaugeSlider/GaugeSlider.h: -------------------------------------------------------------------------------- 1 | // 2 | // GaugeSlider.h 3 | // GaugeSlider 4 | // 5 | // Created by Edgar Žigis on 23/08/2019. 6 | // Copyright © 2019 Edgar Žigis. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for GaugeSlider. 12 | FOUNDATION_EXPORT double GaugeSliderVersionNumber; 13 | 14 | //! Project version string for GaugeSlider. 15 | FOUNDATION_EXPORT const unsigned char GaugeSliderVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Sources/GaugeSlider/GaugeSliderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GaugeSliderView.swift 3 | // GaugeSlider 4 | // 5 | // Created by Edgar Žigis on 23/08/2019. 6 | // Copyright © 2019 Edgar Žigis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public enum GaugeSliderCountingMethod { 12 | case linear 13 | case easeIn 14 | case easeOut 15 | case easeInOut 16 | } 17 | 18 | public enum GaugeSliderDelegationMode: Equatable { 19 | case immediate(interval: CGFloat) 20 | case singular 21 | } 22 | 23 | @IBDesignable 24 | public class GaugeSliderView: UIView { 25 | 26 | // MARK: - Static variables - 27 | 28 | private static let minValue: CGFloat = -1.20 29 | private static let midValue: CGFloat = -0.5 30 | private static let maxValue: CGFloat = 0.20 31 | private static let startingAngle: CGFloat = -45 32 | 33 | // MARK: - Open variables - 34 | 35 | open var onProgressChanged: (Int)->() = { progress in } 36 | open var onButtonAction: ()->() = { } 37 | 38 | /** 39 | * Sets not filled sliding path color 40 | */ 41 | @IBInspectable 42 | open var blankPathColor = UIColor.lightGray { 43 | didSet { 44 | setNeedsDisplay() 45 | } 46 | } 47 | 48 | /** 49 | * Sets filled sliding path color 50 | */ 51 | @IBInspectable 52 | open var fillPathColor = UIColor.green { 53 | didSet { 54 | setNeedsDisplay() 55 | } 56 | } 57 | 58 | /** 59 | * Sets vertical indicator color 60 | */ 61 | @IBInspectable 62 | open var indicatorColor = UIColor.red { 63 | didSet { 64 | setNeedsDisplay() 65 | } 66 | } 67 | 68 | /** 69 | * Sets dashed unit colors 70 | */ 71 | @IBInspectable 72 | open var unitColor = UIColor.darkGray { 73 | didSet { 74 | unitLabel.textColor = unitColor 75 | } 76 | } 77 | 78 | /** 79 | * Sets dashed unit labels colors 80 | */ 81 | @IBInspectable 82 | open var unitIndicatorColor = UIColor.lightGray { 83 | didSet { 84 | unitIndicatorLabels.forEach { 85 | $0.textColor = unitIndicatorColor 86 | } 87 | } 88 | } 89 | 90 | /** 91 | * Sets value placeholder color (eg. Warming) 92 | */ 93 | @IBInspectable 94 | open var placeholderColor = UIColor.lightGray { 95 | didSet { 96 | placeholderLabel.textColor = placeholderColor 97 | } 98 | } 99 | 100 | /** 101 | * Sets custom control color (eg. Automatic) 102 | */ 103 | @IBInspectable 104 | open var customControlColor = UIColor.green { 105 | didSet { 106 | customControlButton.backgroundColor = customControlColor.withAlphaComponent(0.1) 107 | customControlButton.setTitleColor(customControlColor, for: .normal) 108 | } 109 | } 110 | 111 | /** 112 | * Sets sliding path width 113 | */ 114 | @IBInspectable 115 | open var trackWidth: CGFloat = 32 { 116 | didSet { 117 | setNeedsDisplay() 118 | } 119 | } 120 | 121 | /** 122 | * Toggles custom control visibility 123 | */ 124 | @IBInspectable 125 | open var isCustomControlActive = false { 126 | didSet { 127 | customControlButton.backgroundColor = customControlColor.withAlphaComponent(isCustomControlActive ? 0.6 : 0.1) 128 | customControlButton.setTitleColor(isCustomControlActive ? UIColor.white : customControlColor, for: .normal) 129 | } 130 | } 131 | 132 | /** 133 | * Sets slider progress by percentage 134 | */ 135 | @IBInspectable 136 | open var progress: CGFloat = 0 { 137 | didSet { 138 | if Int(oldValue) != Int(progress) && allowCallBack && !internalActionsInProgress { 139 | switch delegationMode { 140 | case .immediate(let interval): 141 | if abs(progress - previousProgressValue) >= interval { 142 | previousProgressValue = progress 143 | onProgressChanged(Int(progress)) 144 | } 145 | default: 146 | onProgressChanged(Int(progress)) 147 | } 148 | } 149 | updateViews() 150 | } 151 | } 152 | 153 | /** 154 | * Sets current slider value 155 | */ 156 | @IBInspectable 157 | open var value: CGFloat { 158 | set { 159 | allowCallBack = false 160 | internalActionsInProgress = true 161 | let amplitude = maxValue - minValue 162 | progress = ((newValue - minValue) / amplitude) * 100 163 | allowCallBack = true 164 | internalActionsInProgress = false 165 | } 166 | get { 167 | return CGFloat(Int(minValue + (maxValue - minValue) * (progress / 100))) 168 | } 169 | } 170 | 171 | /** 172 | * Sets minimum slider value 173 | */ 174 | @IBInspectable 175 | open var minValue: CGFloat = 5 { 176 | didSet { 177 | updateViews() 178 | } 179 | } 180 | 181 | /** 182 | * Sets maximum slider value 183 | */ 184 | @IBInspectable 185 | open var maxValue: CGFloat = 25 { 186 | didSet { 187 | updateViews() 188 | } 189 | } 190 | 191 | /** 192 | * Sets unit value (eg. temperature, speed etc.) 193 | */ 194 | @IBInspectable 195 | open var unit = "°" { 196 | didSet { 197 | updateViews() 198 | } 199 | } 200 | 201 | /** 202 | * Sets value placeholder 203 | */ 204 | @IBInspectable 205 | open var placeholder = "Warming" { 206 | didSet { 207 | updateViews() 208 | } 209 | } 210 | 211 | /** 212 | * Sets custom control title 213 | */ 214 | @IBInspectable 215 | open var customControlButtonTitle = "• Auto" { 216 | didSet { 217 | updateViews() 218 | } 219 | } 220 | 221 | /** 222 | * Sets custom control visibility 223 | */ 224 | @IBInspectable 225 | open var customControlButtonVisible = true { 226 | didSet { 227 | customControlButton.isHidden = !customControlButtonVisible 228 | } 229 | } 230 | 231 | /** 232 | * Sets unit font 233 | */ 234 | @IBInspectable 235 | open var unitFont = UIFont.systemFont(ofSize: 67) { 236 | didSet { 237 | unitLabel.font = unitFont 238 | } 239 | } 240 | 241 | /** 242 | * Sets placeholder font 243 | */ 244 | @IBInspectable 245 | open var placeholderFont = UIFont.systemFont(ofSize: 17, weight: .medium) { 246 | didSet { 247 | placeholderLabel.font = placeholderFont 248 | } 249 | } 250 | 251 | /** 252 | * Sets unit indicator font 253 | */ 254 | @IBInspectable 255 | open var unitIndicatorFont = UIFont.systemFont(ofSize: 16, weight: .medium) { 256 | didSet { 257 | unitIndicatorLabels.forEach { 258 | $0.font = unitIndicatorFont 259 | } 260 | } 261 | } 262 | 263 | /** 264 | * Sets left icon 265 | */ 266 | @IBInspectable 267 | open var leftIcon = UIImage(named: "snowIcon") 268 | 269 | /** 270 | * Sets right icon 271 | */ 272 | @IBInspectable 273 | open var rightIcon = UIImage(named: "sunIcon") 274 | 275 | /** 276 | * Sets counting method animation 277 | */ 278 | open var countingMethod = GaugeSliderCountingMethod.easeInOut 279 | 280 | /** 281 | * .singular will emit only single event after gesture finish 282 | * .immediate will emit multiple events after every progress value change 283 | */ 284 | open var delegationMode = GaugeSliderDelegationMode.singular 285 | 286 | // MARK: - Private variables - 287 | 288 | private var timer: CADisplayLink? 289 | private var startValue: CGFloat = 0 290 | private var destinationValue: CGFloat = 0 291 | private var timerProgress: TimeInterval = 0 292 | private var timerLastUpdate: TimeInterval = 0 293 | private var timerTotalTime: TimeInterval = 0 294 | private var progressCounter: ProgressCounter! 295 | 296 | private var allowCallBack = true { 297 | didSet { 298 | if oldValue == false && allowCallBack && !internalActionsInProgress { 299 | onProgressChanged(Int(progress)) 300 | } 301 | } 302 | } 303 | private var internalActionsInProgress = false 304 | private var previousProgressValue: CGFloat = 0 305 | 306 | private var endPoint = CGPoint.zero 307 | private var totalArcDistance = CGFloat.leastNonzeroMagnitude 308 | 309 | private let unitLabel = UILabel() 310 | private let unitIndicatorLabels = [UILabel(), UILabel(), UILabel()] 311 | private let placeholderLabel = UILabel() 312 | private let customControlButton = UIButton() 313 | 314 | private let leftIconView = UIImageView() 315 | private let rightIconView = UIImageView() 316 | 317 | // MARK: - UI methods - 318 | 319 | public override func draw(_ rect: CGRect) { 320 | super.draw(rect) 321 | drawPath(in: rect, color: blankPathColor, thickness: 1.5, trackWidth: trackWidth, capturePoints: true) 322 | 323 | drawMeter(in: rect, color: blankPathColor, startAngle: .pi * GaugeSliderView.minValue - 0.02, endAngle: -.pi - 0.04) 324 | drawMeter(in: rect, color: blankPathColor, startAngle: -.pi + 0.05, endAngle: .pi * GaugeSliderView.midValue - 0.05) 325 | drawMeter(in: rect, color: blankPathColor, startAngle: .pi * GaugeSliderView.midValue + 0.04, endAngle: 0 - 0.04) 326 | drawMeter(in: rect, color: blankPathColor, startAngle: 0.06, endAngle: .pi * GaugeSliderView.maxValue + 0.03) 327 | 328 | drawMeterIndicator(in: rect, color: blankPathColor, angle: -.pi) 329 | drawMeterIndicator(in: rect, color: blankPathColor, angle: -.pi / 2) 330 | drawMeterIndicator(in: rect, color: blankPathColor, angle: 0) 331 | 332 | let range = GaugeSliderView.maxValue + abs(GaugeSliderView.minValue) 333 | let currentValue = GaugeSliderView.minValue + range * progress / 100 334 | 335 | drawPath(in: rect, color: fillPathColor, thickness: 2.5, trackWidth: trackWidth, endValue: currentValue) 336 | drawPath(in: rect, color: indicatorColor, thickness: 2.5, trackWidth: trackWidth * 1.6, startValue: currentValue - 0.01, endValue: currentValue, drawShadow: true) 337 | } 338 | 339 | public override func layoutSubviews() { 340 | super.layoutSubviews() 341 | backgroundColor = .clear 342 | updateViews() 343 | } 344 | 345 | private func addGestures() { 346 | addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onGesture(_:)))) 347 | addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(onGesture(_:)))) 348 | } 349 | 350 | private func addViews() { 351 | unitLabel.font = unitFont 352 | unitLabel.textColor = unitColor 353 | addSubview(unitLabel) 354 | 355 | placeholderLabel.font = placeholderFont 356 | placeholderLabel.textColor = placeholderColor 357 | addSubview(placeholderLabel) 358 | 359 | unitIndicatorLabels.forEach { 360 | $0.font = unitIndicatorFont 361 | $0.textColor = unitIndicatorColor 362 | addSubview($0) 363 | } 364 | 365 | leftIconView.image = leftIcon 366 | leftIconView.contentMode = .scaleAspectFit 367 | addSubview(leftIconView) 368 | 369 | rightIconView.image = rightIcon 370 | rightIconView.contentMode = .scaleAspectFit 371 | addSubview(rightIconView) 372 | 373 | customControlButton.backgroundColor = customControlColor.withAlphaComponent(0.1) 374 | customControlButton.setTitleColor(customControlColor, for: .normal) 375 | customControlButton.layer.cornerRadius = 16 376 | customControlButton.addTarget(self, action: #selector(onCustomButton), for: .touchUpInside) 377 | addSubview(customControlButton) 378 | 379 | updateViews() 380 | } 381 | 382 | private func positionViews() { 383 | unitLabel.sizeToFit() 384 | unitLabel.frame.origin.x = (frame.width - unitLabel.frame.width) / 2 385 | unitLabel.frame.origin.y = (frame.height - unitLabel.frame.height) / 2 386 | 387 | placeholderLabel.sizeToFit() 388 | placeholderLabel.frame.origin.x = (frame.width - placeholderLabel.frame.width) / 2 389 | placeholderLabel.frame.origin.y = unitLabel.frame.origin.y + unitLabel.frame.height + 4 390 | 391 | unitIndicatorLabels[0].sizeToFit() 392 | unitIndicatorLabels[0].frame.origin.x = trackWidth * 2.4 393 | unitIndicatorLabels[0].frame.origin.y = (frame.height - unitIndicatorLabels[0].frame.height) / 2 394 | 395 | unitIndicatorLabels[1].sizeToFit() 396 | unitIndicatorLabels[1].frame.origin.x = (frame.width - unitIndicatorLabels[1].frame.width) / 2 397 | unitIndicatorLabels[1].frame.origin.y = trackWidth * 2.4 398 | 399 | unitIndicatorLabels[2].sizeToFit() 400 | unitIndicatorLabels[2].frame.origin.x = frame.width - trackWidth * 2.4 - unitIndicatorLabels[2].frame.width 401 | unitIndicatorLabels[2].frame.origin.y = unitIndicatorLabels[0].frame.origin.y 402 | 403 | leftIconView.frame.size = CGSize(width: 24, height: 24) 404 | leftIconView.frame.origin = CGPoint(x: frame.width - endPoint.x - 24 - trackWidth / 2.5, y: endPoint.y + 4) 405 | 406 | rightIconView.frame.size = CGSize(width: 24, height: 24) 407 | rightIconView.frame.origin = CGPoint(x: endPoint.x + trackWidth / 2.5, y: endPoint.y + 4) 408 | 409 | customControlButton.frame.size = CGSize( 410 | width: frame.width - leftIconView.frame.origin.x * 2 - CGFloat(96), 411 | height: 28 412 | ) 413 | customControlButton.frame.origin = CGPoint(x: leftIconView.frame.origin.x + 48, y: endPoint.y + 4) 414 | customControlButton.titleLabel?.font = UIFont.systemFont(ofSize: 13, weight: .medium) 415 | } 416 | 417 | func updateViews() { 418 | setNeedsDisplay() 419 | 420 | let diff = maxValue - minValue 421 | unitLabel.text = "\(Int(minValue + diff * (progress / 100)))\(unit)" 422 | placeholderLabel.text = placeholder 423 | 424 | unitIndicatorLabels[0].text = "\(Int((maxValue - minValue) / 7))" 425 | unitIndicatorLabels[1].text = "\(Int((maxValue - minValue) / 2))" 426 | unitIndicatorLabels[2].text = "\(Int(maxValue - (maxValue - minValue) / 7))" 427 | 428 | customControlButton.setTitle(customControlButtonTitle, for: .normal) 429 | 430 | positionViews() 431 | } 432 | 433 | public func setCurrentValue(_ value: CGFloat, animated: Bool) { 434 | guard value >= minValue && value <= maxValue else { 435 | return 436 | } 437 | let targetValue = (value - minValue) / (maxValue - minValue) * 100 438 | 439 | if animated { 440 | internalActionsInProgress = true 441 | animateProgress(from: progress, to: targetValue) 442 | } else { 443 | if delegationMode == .singular { 444 | allowCallBack = false 445 | } 446 | progress = targetValue 447 | } 448 | } 449 | 450 | // MARK: - Paths - 451 | 452 | private func drawPath( 453 | in rect: CGRect, 454 | color: UIColor, 455 | thickness: CGFloat, 456 | trackWidth: CGFloat, 457 | startValue: CGFloat = GaugeSliderView.minValue, 458 | endValue: CGFloat = GaugeSliderView.maxValue, 459 | drawShadow: Bool = false, 460 | capturePoints: Bool = false 461 | ) { 462 | let context = UIGraphicsGetCurrentContext() 463 | 464 | let path = UIBezierPath() 465 | path.addArc( 466 | withCenter: CGPoint(x: rect.midX, y: rect.midY), 467 | radius: rect.width / 2 - trackWidth + (trackWidth - self.trackWidth) * 1.5, 468 | startAngle: .pi * startValue, 469 | endAngle: .pi * endValue, 470 | clockwise: true 471 | ) 472 | 473 | if capturePoints && endPoint == CGPoint.zero { 474 | if let point = path.firstPoint() { 475 | endPoint = CGPoint(x: rect.width - point.x - trackWidth * 1.5, y: point.y) 476 | totalArcDistance = getPointDistanceFromStart(to: endPoint) 477 | updateViews() 478 | } 479 | } 480 | 481 | color.setStroke() 482 | path.lineWidth = trackWidth 483 | 484 | if drawShadow { 485 | context?.setShadow(offset: CGSize(width: 0, height: 6), blur: 10) 486 | } 487 | 488 | context?.saveGState() 489 | context?.setLineDash(phase: 0, lengths: [thickness, 12.5 - thickness]) 490 | 491 | path.stroke() 492 | 493 | context?.restoreGState() 494 | } 495 | 496 | private func drawMeter(in rect: CGRect, color: UIColor, startAngle: CGFloat, endAngle: CGFloat) { 497 | let context = UIGraphicsGetCurrentContext() 498 | 499 | let path = UIBezierPath() 500 | path.addArc( 501 | withCenter: CGPoint(x: rect.midX, y: rect.midY), 502 | radius: rect.width / 2 - trackWidth * 2 + 2, 503 | startAngle: startAngle, 504 | endAngle: endAngle, 505 | clockwise: true 506 | ) 507 | 508 | color.setStroke() 509 | path.lineWidth = 4 510 | 511 | context?.saveGState() 512 | context?.setLineDash(phase: 0, lengths: [1, 8]) 513 | 514 | path.stroke() 515 | 516 | context?.restoreGState() 517 | } 518 | 519 | private func drawMeterIndicator(in rect: CGRect, color: UIColor, angle: CGFloat) { 520 | let context = UIGraphicsGetCurrentContext() 521 | 522 | let path = UIBezierPath() 523 | path.addArc( 524 | withCenter: CGPoint(x: rect.midX, y: rect.midY), 525 | radius: rect.width / 2 - trackWidth * 2 - 1.5, 526 | startAngle: angle - 0.01, 527 | endAngle: angle, 528 | clockwise: true 529 | ) 530 | 531 | color.setStroke() 532 | path.lineWidth = 12 533 | 534 | context?.saveGState() 535 | context?.setLineDash(phase: 0, lengths: [1, totalArcDistance / 16]) 536 | 537 | path.stroke() 538 | 539 | context?.restoreGState() 540 | } 541 | 542 | // MARK: - Time functions - 543 | 544 | private func animateProgress(from start: CGFloat, to destination: CGFloat) { 545 | startValue = start 546 | destinationValue = destination 547 | 548 | timer?.invalidate() 549 | timer = nil 550 | 551 | if delegationMode == .singular { 552 | allowCallBack = false 553 | } 554 | 555 | timerProgress = 0 556 | timerTotalTime = TimeInterval(0.5) 557 | timerLastUpdate = Date.timeIntervalSinceReferenceDate 558 | 559 | switch countingMethod { 560 | case .linear: 561 | progressCounter = ProgressCounterLinear() 562 | case .easeIn: 563 | progressCounter = ProgressCounterEaseIn() 564 | case .easeOut: 565 | progressCounter = ProgressCounterEaseOut() 566 | case .easeInOut: 567 | progressCounter = ProgressCounterEaseInOut() 568 | } 569 | 570 | timer = CADisplayLink(target: self, selector: #selector(updateProgressValue(_:))) 571 | timer?.preferredFramesPerSecond = 30 572 | timer?.add(to: RunLoop.main, forMode: RunLoop.Mode.default) 573 | timer?.add(to: RunLoop.main, forMode: RunLoop.Mode.tracking) 574 | } 575 | 576 | @objc func updateProgressValue(_ timer: Timer) { 577 | let now = Date.timeIntervalSinceReferenceDate 578 | timerProgress = timerProgress + now - timerLastUpdate 579 | timerLastUpdate = now 580 | 581 | if timerProgress >= timerTotalTime { 582 | self.timer?.invalidate() 583 | self.timer = nil 584 | timerProgress = timerTotalTime 585 | } 586 | 587 | if timerProgress == timerTotalTime { 588 | allowCallBack = true 589 | } 590 | 591 | progress = currentTimerValue() 592 | 593 | if timerProgress == timerTotalTime { 594 | internalActionsInProgress = false 595 | } 596 | } 597 | 598 | @objc private func onCustomButton() { 599 | guard !isCustomControlActive else { 600 | return 601 | } 602 | isCustomControlActive = true 603 | onButtonAction() 604 | } 605 | 606 | private func currentTimerValue() -> CGFloat { 607 | if timerProgress == 0 { 608 | return 0 609 | } else if timerProgress >= timerTotalTime { 610 | return destinationValue 611 | } 612 | let newValue = progressCounter.update(CGFloat(timerProgress / timerTotalTime)) 613 | return startValue + newValue * (destinationValue - startValue) 614 | } 615 | 616 | // MARK: - Gestures - 617 | 618 | @objc private func onGesture(_ recognizer: UIGestureRecognizer) { 619 | let currentPoint = recognizer.location(in: self) 620 | if point(inside: currentPoint, with: nil) { 621 | let currentDistance = getPointDistanceFromStart(to: currentPoint) 622 | let canonicalProgress = (currentDistance / totalArcDistance) * 100 623 | let calculatedValue = max(0, min(canonicalProgress, 100)) 624 | if recognizer is UITapGestureRecognizer { 625 | animateProgress(from: progress, to: calculatedValue) 626 | } else if recognizer is UIPanGestureRecognizer { 627 | if recognizer.state == .began && delegationMode == .singular { 628 | allowCallBack = false 629 | } 630 | progress = calculatedValue 631 | if recognizer.state == .ended && delegationMode == .singular { 632 | allowCallBack = true 633 | } 634 | } 635 | } 636 | } 637 | 638 | public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { 639 | let center = CGPoint(x: bounds.size.width / 2, y: bounds.size.height / 2) 640 | let currentLimit = pow(center.x - point.x, 2) + pow(center.y - point.y, 2) 641 | 642 | let outerBoxLimit = pow(bounds.size.width / 2, 2) 643 | if currentLimit > outerBoxLimit { 644 | return false 645 | } 646 | 647 | let innerBoxLimit = pow(bounds.size.width / 4, 2) 648 | if currentLimit < innerBoxLimit { 649 | return false 650 | } 651 | 652 | let isInsideOfActiveBoundary = point.y < endPoint.y + bounds.size.height / 6 // 16% offset permit 653 | if !isInsideOfActiveBoundary { 654 | return false 655 | } 656 | 657 | return true 658 | } 659 | 660 | private func getPointDistanceFromStart(to point: CGPoint) -> CGFloat { 661 | let radii = bounds.width / 2 - trackWidth 662 | let circumference = radii * 2 * .pi 663 | let maxAngle = 360 + GaugeSliderView.startingAngle * 2 664 | 665 | var angle = radiansToDegrees(atan2(point.x - bounds.midX, point.y - bounds.midY)) + GaugeSliderView.startingAngle + 180.0 666 | angle = (90.0 - angle).truncatingRemainder(dividingBy: 360.0) 667 | 668 | while angle < 0.0 { 669 | angle += 360.0 670 | } 671 | if point.x < radii && angle > maxAngle { 672 | angle = 0 673 | } 674 | angle = max(0, min(angle, maxAngle)) 675 | 676 | return angle / 360 * circumference 677 | } 678 | 679 | private func radiansToDegrees(_ angle: CGFloat) -> CGFloat { 680 | return angle / .pi * 180.0 681 | } 682 | 683 | // MARK: - Init - 684 | 685 | override init(frame: CGRect) { 686 | super.init(frame: frame) 687 | addGestures() 688 | addViews() 689 | } 690 | 691 | required init?(coder aDecoder: NSCoder) { 692 | super.init(coder: aDecoder) 693 | addGestures() 694 | addViews() 695 | } 696 | } 697 | -------------------------------------------------------------------------------- /Sources/GaugeSlider/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/GaugeSliderTests/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // GaugeSliderTest 4 | // 5 | // Created by Edgar Žigis on 23/08/2019. 6 | // Copyright © 2019 Edgar Žigis. 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 invalidate graphics rendering callbacks. 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 active 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 | -------------------------------------------------------------------------------- /Tests/GaugeSliderTests/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Tests/GaugeSliderTests/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Tests/GaugeSliderTests/Assets.xcassets/snowIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "snowFlakeIcon.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /Tests/GaugeSliderTests/Assets.xcassets/snowIcon.imageset/snowFlakeIcon.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edgar-zigis/GaugeSlider/729a11d962bfbfbddb971ac5e36fceb782e97770/Tests/GaugeSliderTests/Assets.xcassets/snowIcon.imageset/snowFlakeIcon.pdf -------------------------------------------------------------------------------- /Tests/GaugeSliderTests/Assets.xcassets/sunIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "sunIcon.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /Tests/GaugeSliderTests/Assets.xcassets/sunIcon.imageset/sunIcon.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edgar-zigis/GaugeSlider/729a11d962bfbfbddb971ac5e36fceb782e97770/Tests/GaugeSliderTests/Assets.xcassets/sunIcon.imageset/sunIcon.pdf -------------------------------------------------------------------------------- /Tests/GaugeSliderTests/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Tests/GaugeSliderTests/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 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /Tests/GaugeSliderTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Tests/GaugeSliderTests/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // GaugeSliderTest 4 | // 5 | // Created by Edgar Žigis on 23/08/2019. 6 | // Copyright © 2019 Edgar Žigis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import GaugeSlider 11 | 12 | class ViewController: UIViewController { 13 | 14 | @IBOutlet weak var versionWithoutStoryBoardButton: UIButton! 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | // Do any additional setup after loading the view. 19 | } 20 | 21 | @IBAction func onVersionWithoutStoryBoard(_ sender: Any) { 22 | present(ViewControllerWithoutStoryBoard(), animated: true, completion: nil) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Tests/GaugeSliderTests/ViewControllerWithoutStoryBoard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewControllerWithoutStoryBoard.swift 3 | // GaugeSliderTest 4 | // 5 | // Created by Edgar Žigis on 23/08/2019. 6 | // Copyright © 2019 Edgar Žigis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import GaugeSlider 11 | 12 | class ViewControllerWithoutStoryBoard: UIViewController { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | 17 | let width = UIScreen.main.bounds.width 18 | 19 | let gaugeSlider = GaugeSliderView() 20 | applyStyle(to: gaugeSlider) 21 | gaugeSlider.frame = CGRect(x: width * 0.05, y: 150, width: width * 0.9, height: width * 0.9) 22 | view.addSubview(gaugeSlider) 23 | 24 | gaugeSlider.onProgressChanged = { [weak self] progress in 25 | print("Progress: \(progress)") 26 | } 27 | 28 | gaugeSlider.onButtonAction = { [weak self] in 29 | print("Action executed") 30 | } 31 | 32 | view.backgroundColor = .white 33 | } 34 | 35 | private func applyStyle(to v: GaugeSliderView) { 36 | v.blankPathColor = UIColor(red: 218/255, green: 218/255, blue: 218/255, alpha: 1) 37 | v.fillPathColor = UIColor(red: 74/255, green: 196/255, blue: 192/255, alpha: 1) 38 | v.indicatorColor = UIColor(red: 94/255, green: 187/255, blue: 169/255, alpha: 1) 39 | v.unitColor = UIColor(red: 74/255, green: 74/255, blue: 74/255, alpha: 1) 40 | v.placeholderColor = UIColor(red: 139/255, green: 154/255, blue: 158/255, alpha: 1) 41 | v.unitIndicatorColor = UIColor(red: 74/255, green: 74/255, blue: 74/255, alpha: 0.2) 42 | v.customControlColor = UIColor(red: 47/255, green: 190/255, blue: 169/255, alpha: 1) 43 | v.customControlButtonTitle = "• Auto" 44 | v.delegationMode = .immediate(interval: 3) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edgar-zigis/GaugeSlider/729a11d962bfbfbddb971ac5e36fceb782e97770/sample.gif --------------------------------------------------------------------------------