├── .gitignore ├── LICENSE ├── README.md └── ScrollRulerViewDemo ├── ScrollRulerViewDemo.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist └── ScrollRulerViewDemo ├── AppDelegate.swift ├── Assets.xcassets └── AppIcon.appiconset │ └── Contents.json ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Info.plist ├── ViewController.swift └── class └── TDScrollRulerView.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Bruce Li 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Swift--一个好用的金融类游标卡尺 2 | > 思路: 3 | > 4 | > 使用UICollectionView制作一个游标卡尺,每一个cell代表一个刻度区间,每个区间间隔值、区间分割份数和卡尺样式等都是可设置的。通过代理协议,监听卡尺的滚动值,设置卡尺的显示值等等。也可以设置默认最大可用金额,当用户滚动卡尺超过最大可用金额时自动回滚等(自己项目中使用到)。[简书](https://www.jianshu.com/p/61289c14ec34) 5 | > 6 | > 7 | 8 | ## 效果图 9 | 10 | ![卡尺效果图1](http://upload-images.jianshu.io/upload_images/877439-cf1ea51440253eec.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 11 | ![卡尺效果图2](http://upload-images.jianshu.io/upload_images/877439-d7c2d80ee72c5afe.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 12 | 13 | ## 初始化方法如下: 14 | ~~~Swift 15 | /// 游标卡尺 16 | /// 17 | /// - Parameters: 18 | /// - frame: 卡尺大小 19 | /// - min: 卡尺最小值 20 | /// - max: 卡尺最大值 21 | /// - part: 每个区被分割的分数 22 | /// - step: 一个区间的大小,默认1000 23 | /// - unit: 单位 24 | /// - numFontSize: 数值文字大小 25 | /// - endNum: 最大有效值 26 | /// - endText: 最大有效值文案 27 | /// - endTextColor: 最大有效值文案颜色 28 | /// - endtextFontSize: 最大有效值文案字体大小 29 | /// - strokeColor: 卡尺颜色 30 | public init(frame: CGRect, strokeColor: UIColor, middleLineColor: UIColor, min: CGFloat, max: CGFloat, part: Int = default, step: Int = default, unit: String = default, numFontSize: CGFloat, endNum: CGFloat, endText: String, endTextColor: UIColor, endtextFontSize: CGFloat) 31 | ~~~ 32 | 33 | ## 如果是网络请求返回的数值,需要实时更新卡尺的,可调用 34 | ~~~Swift 35 | /// 更新游标卡尺 36 | /// 37 | /// - Parameters: 38 | /// - min: 卡尺最小值 39 | /// - max: 卡尺最大值 40 | /// - part: 每个区被分割的分数 41 | /// - step: 一个区间的大小,默认1000 42 | /// - unit: 单位 43 | /// - numFontSize: 数值文字大小 44 | /// - endNum: 最大有效值 45 | /// - endText: 最大有效值文案 46 | /// - endTextColor: 最大有效值文案颜色 47 | /// - endtextFontSize: 最大有效值文案字体大小 48 | internal func reloadRulerView(min: CGFloat, max: CGFloat, part: Int = default, step: Int = default, unit: String = default, numFontSize: CGFloat, endNum: CGFloat, endText: String, endTextColor: UIColor, endtextFontSize: CGFloat) 49 | ~~~ 50 | 51 | ## 设置卡尺的位置方法 52 | ~~~Swift 53 | /// 设置卡尺显示值 54 | /// 55 | /// - Parameter endN: 指定的值 56 | public func scrollToEndNumber(endN: CGFloat, animated: Bool = default) 57 | ~~~ 58 | 59 | ## 卡尺交互协议 60 | ~~~Swift 61 | /// 滚动时,返回当前值 62 | /// 63 | /// - Parameters: 64 | /// - value: 当前值 65 | internal func scrollViewDidScroll(value: CGFloat) 66 | 67 | /// 尺子即将被拖拽 68 | /// 69 | /// - Parameter value: 当前值 70 | internal func scrollViewWillBeginDragging(value: CGFloat) 71 | 72 | /// 尺子停止滚动协议 73 | /// 74 | /// - Parameter value: 停止滚动值 75 | internal func scrollViewDidEndDragging(value: CGFloat) 76 | 77 | /// 尺子滚动动画停止协议 78 | /// 79 | /// - Parameter value: 停止滚动值 80 | internal func scrollViewDidEndScrollingAnimation(value: CGFloat) 81 | ~~~ 82 | 83 | [简书](https://www.jianshu.com/p/61289c14ec34) 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /ScrollRulerViewDemo/ScrollRulerViewDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 995AABF1209BF25F0009AD16 /* TDScrollRulerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 995AABF0209BF25F0009AD16 /* TDScrollRulerView.swift */; }; 11 | 995B050C202ACCB100FCEF43 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 995B050B202ACCB100FCEF43 /* AppDelegate.swift */; }; 12 | 995B050E202ACCB100FCEF43 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 995B050D202ACCB100FCEF43 /* ViewController.swift */; }; 13 | 995B0511202ACCB100FCEF43 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 995B050F202ACCB100FCEF43 /* Main.storyboard */; }; 14 | 995B0513202ACCB100FCEF43 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 995B0512202ACCB100FCEF43 /* Assets.xcassets */; }; 15 | 995B0516202ACCB100FCEF43 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 995B0514202ACCB100FCEF43 /* LaunchScreen.storyboard */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | 995AABF0209BF25F0009AD16 /* TDScrollRulerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TDScrollRulerView.swift; sourceTree = ""; }; 20 | 995B0508202ACCB100FCEF43 /* ScrollRulerViewDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ScrollRulerViewDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | 995B050B202ACCB100FCEF43 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 22 | 995B050D202ACCB100FCEF43 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 23 | 995B0510202ACCB100FCEF43 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 24 | 995B0512202ACCB100FCEF43 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 25 | 995B0515202ACCB100FCEF43 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 26 | 995B0517202ACCB100FCEF43 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 27 | /* End PBXFileReference section */ 28 | 29 | /* Begin PBXFrameworksBuildPhase section */ 30 | 995B0505202ACCB100FCEF43 /* Frameworks */ = { 31 | isa = PBXFrameworksBuildPhase; 32 | buildActionMask = 2147483647; 33 | files = ( 34 | ); 35 | runOnlyForDeploymentPostprocessing = 0; 36 | }; 37 | /* End PBXFrameworksBuildPhase section */ 38 | 39 | /* Begin PBXGroup section */ 40 | 995AABEF209BF25F0009AD16 /* class */ = { 41 | isa = PBXGroup; 42 | children = ( 43 | 995AABF0209BF25F0009AD16 /* TDScrollRulerView.swift */, 44 | ); 45 | path = class; 46 | sourceTree = ""; 47 | }; 48 | 995B04FF202ACCB100FCEF43 = { 49 | isa = PBXGroup; 50 | children = ( 51 | 995B050A202ACCB100FCEF43 /* ScrollRulerViewDemo */, 52 | 995B0509202ACCB100FCEF43 /* Products */, 53 | ); 54 | sourceTree = ""; 55 | }; 56 | 995B0509202ACCB100FCEF43 /* Products */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | 995B0508202ACCB100FCEF43 /* ScrollRulerViewDemo.app */, 60 | ); 61 | name = Products; 62 | sourceTree = ""; 63 | }; 64 | 995B050A202ACCB100FCEF43 /* ScrollRulerViewDemo */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 995AABEF209BF25F0009AD16 /* class */, 68 | 995B050B202ACCB100FCEF43 /* AppDelegate.swift */, 69 | 995B050D202ACCB100FCEF43 /* ViewController.swift */, 70 | 995B050F202ACCB100FCEF43 /* Main.storyboard */, 71 | 995B0512202ACCB100FCEF43 /* Assets.xcassets */, 72 | 995B0514202ACCB100FCEF43 /* LaunchScreen.storyboard */, 73 | 995B0517202ACCB100FCEF43 /* Info.plist */, 74 | ); 75 | path = ScrollRulerViewDemo; 76 | sourceTree = ""; 77 | }; 78 | /* End PBXGroup section */ 79 | 80 | /* Begin PBXNativeTarget section */ 81 | 995B0507202ACCB100FCEF43 /* ScrollRulerViewDemo */ = { 82 | isa = PBXNativeTarget; 83 | buildConfigurationList = 995B051A202ACCB100FCEF43 /* Build configuration list for PBXNativeTarget "ScrollRulerViewDemo" */; 84 | buildPhases = ( 85 | 995B0504202ACCB100FCEF43 /* Sources */, 86 | 995B0505202ACCB100FCEF43 /* Frameworks */, 87 | 995B0506202ACCB100FCEF43 /* Resources */, 88 | ); 89 | buildRules = ( 90 | ); 91 | dependencies = ( 92 | ); 93 | name = ScrollRulerViewDemo; 94 | productName = ScrollRulerViewDemo; 95 | productReference = 995B0508202ACCB100FCEF43 /* ScrollRulerViewDemo.app */; 96 | productType = "com.apple.product-type.application"; 97 | }; 98 | /* End PBXNativeTarget section */ 99 | 100 | /* Begin PBXProject section */ 101 | 995B0500202ACCB100FCEF43 /* Project object */ = { 102 | isa = PBXProject; 103 | attributes = { 104 | LastSwiftUpdateCheck = 0920; 105 | LastUpgradeCheck = 0920; 106 | ORGANIZATIONNAME = tuandai; 107 | TargetAttributes = { 108 | 995B0507202ACCB100FCEF43 = { 109 | CreatedOnToolsVersion = 9.2; 110 | LastSwiftMigration = 1000; 111 | ProvisioningStyle = Automatic; 112 | }; 113 | }; 114 | }; 115 | buildConfigurationList = 995B0503202ACCB100FCEF43 /* Build configuration list for PBXProject "ScrollRulerViewDemo" */; 116 | compatibilityVersion = "Xcode 8.0"; 117 | developmentRegion = en; 118 | hasScannedForEncodings = 0; 119 | knownRegions = ( 120 | en, 121 | Base, 122 | ); 123 | mainGroup = 995B04FF202ACCB100FCEF43; 124 | productRefGroup = 995B0509202ACCB100FCEF43 /* Products */; 125 | projectDirPath = ""; 126 | projectRoot = ""; 127 | targets = ( 128 | 995B0507202ACCB100FCEF43 /* ScrollRulerViewDemo */, 129 | ); 130 | }; 131 | /* End PBXProject section */ 132 | 133 | /* Begin PBXResourcesBuildPhase section */ 134 | 995B0506202ACCB100FCEF43 /* Resources */ = { 135 | isa = PBXResourcesBuildPhase; 136 | buildActionMask = 2147483647; 137 | files = ( 138 | 995B0516202ACCB100FCEF43 /* LaunchScreen.storyboard in Resources */, 139 | 995B0513202ACCB100FCEF43 /* Assets.xcassets in Resources */, 140 | 995B0511202ACCB100FCEF43 /* Main.storyboard in Resources */, 141 | ); 142 | runOnlyForDeploymentPostprocessing = 0; 143 | }; 144 | /* End PBXResourcesBuildPhase section */ 145 | 146 | /* Begin PBXSourcesBuildPhase section */ 147 | 995B0504202ACCB100FCEF43 /* Sources */ = { 148 | isa = PBXSourcesBuildPhase; 149 | buildActionMask = 2147483647; 150 | files = ( 151 | 995B050E202ACCB100FCEF43 /* ViewController.swift in Sources */, 152 | 995AABF1209BF25F0009AD16 /* TDScrollRulerView.swift in Sources */, 153 | 995B050C202ACCB100FCEF43 /* AppDelegate.swift in Sources */, 154 | ); 155 | runOnlyForDeploymentPostprocessing = 0; 156 | }; 157 | /* End PBXSourcesBuildPhase section */ 158 | 159 | /* Begin PBXVariantGroup section */ 160 | 995B050F202ACCB100FCEF43 /* Main.storyboard */ = { 161 | isa = PBXVariantGroup; 162 | children = ( 163 | 995B0510202ACCB100FCEF43 /* Base */, 164 | ); 165 | name = Main.storyboard; 166 | sourceTree = ""; 167 | }; 168 | 995B0514202ACCB100FCEF43 /* LaunchScreen.storyboard */ = { 169 | isa = PBXVariantGroup; 170 | children = ( 171 | 995B0515202ACCB100FCEF43 /* Base */, 172 | ); 173 | name = LaunchScreen.storyboard; 174 | sourceTree = ""; 175 | }; 176 | /* End PBXVariantGroup section */ 177 | 178 | /* Begin XCBuildConfiguration section */ 179 | 995B0518202ACCB100FCEF43 /* Debug */ = { 180 | isa = XCBuildConfiguration; 181 | buildSettings = { 182 | ALWAYS_SEARCH_USER_PATHS = NO; 183 | CLANG_ANALYZER_NONNULL = YES; 184 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 185 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 186 | CLANG_CXX_LIBRARY = "libc++"; 187 | CLANG_ENABLE_MODULES = YES; 188 | CLANG_ENABLE_OBJC_ARC = YES; 189 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 190 | CLANG_WARN_BOOL_CONVERSION = YES; 191 | CLANG_WARN_COMMA = YES; 192 | CLANG_WARN_CONSTANT_CONVERSION = YES; 193 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 194 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 195 | CLANG_WARN_EMPTY_BODY = YES; 196 | CLANG_WARN_ENUM_CONVERSION = YES; 197 | CLANG_WARN_INFINITE_RECURSION = YES; 198 | CLANG_WARN_INT_CONVERSION = YES; 199 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 200 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 201 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 202 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 203 | CLANG_WARN_STRICT_PROTOTYPES = YES; 204 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 205 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 206 | CLANG_WARN_UNREACHABLE_CODE = YES; 207 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 208 | CODE_SIGN_IDENTITY = "iPhone Developer"; 209 | COPY_PHASE_STRIP = NO; 210 | DEBUG_INFORMATION_FORMAT = dwarf; 211 | ENABLE_STRICT_OBJC_MSGSEND = YES; 212 | ENABLE_TESTABILITY = YES; 213 | GCC_C_LANGUAGE_STANDARD = gnu11; 214 | GCC_DYNAMIC_NO_PIC = NO; 215 | GCC_NO_COMMON_BLOCKS = YES; 216 | GCC_OPTIMIZATION_LEVEL = 0; 217 | GCC_PREPROCESSOR_DEFINITIONS = ( 218 | "DEBUG=1", 219 | "$(inherited)", 220 | ); 221 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 222 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 223 | GCC_WARN_UNDECLARED_SELECTOR = YES; 224 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 225 | GCC_WARN_UNUSED_FUNCTION = YES; 226 | GCC_WARN_UNUSED_VARIABLE = YES; 227 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 228 | MTL_ENABLE_DEBUG_INFO = YES; 229 | ONLY_ACTIVE_ARCH = YES; 230 | SDKROOT = iphoneos; 231 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 232 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 233 | }; 234 | name = Debug; 235 | }; 236 | 995B0519202ACCB100FCEF43 /* Release */ = { 237 | isa = XCBuildConfiguration; 238 | buildSettings = { 239 | ALWAYS_SEARCH_USER_PATHS = NO; 240 | CLANG_ANALYZER_NONNULL = YES; 241 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 242 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 243 | CLANG_CXX_LIBRARY = "libc++"; 244 | CLANG_ENABLE_MODULES = YES; 245 | CLANG_ENABLE_OBJC_ARC = YES; 246 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 247 | CLANG_WARN_BOOL_CONVERSION = YES; 248 | CLANG_WARN_COMMA = YES; 249 | CLANG_WARN_CONSTANT_CONVERSION = YES; 250 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 251 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 252 | CLANG_WARN_EMPTY_BODY = YES; 253 | CLANG_WARN_ENUM_CONVERSION = YES; 254 | CLANG_WARN_INFINITE_RECURSION = YES; 255 | CLANG_WARN_INT_CONVERSION = YES; 256 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 257 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 258 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 259 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 260 | CLANG_WARN_STRICT_PROTOTYPES = YES; 261 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 262 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 263 | CLANG_WARN_UNREACHABLE_CODE = YES; 264 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 265 | CODE_SIGN_IDENTITY = "iPhone Developer"; 266 | COPY_PHASE_STRIP = NO; 267 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 268 | ENABLE_NS_ASSERTIONS = NO; 269 | ENABLE_STRICT_OBJC_MSGSEND = YES; 270 | GCC_C_LANGUAGE_STANDARD = gnu11; 271 | GCC_NO_COMMON_BLOCKS = YES; 272 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 273 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 274 | GCC_WARN_UNDECLARED_SELECTOR = YES; 275 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 276 | GCC_WARN_UNUSED_FUNCTION = YES; 277 | GCC_WARN_UNUSED_VARIABLE = YES; 278 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 279 | MTL_ENABLE_DEBUG_INFO = NO; 280 | SDKROOT = iphoneos; 281 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 282 | VALIDATE_PRODUCT = YES; 283 | }; 284 | name = Release; 285 | }; 286 | 995B051B202ACCB100FCEF43 /* Debug */ = { 287 | isa = XCBuildConfiguration; 288 | buildSettings = { 289 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 290 | CODE_SIGN_STYLE = Automatic; 291 | DEVELOPMENT_TEAM = HBBKUSRZMD; 292 | INFOPLIST_FILE = ScrollRulerViewDemo/Info.plist; 293 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 294 | PRODUCT_BUNDLE_IDENTIFIER = com.tuandai.app.ScrollRulerViewDemo; 295 | PRODUCT_NAME = "$(TARGET_NAME)"; 296 | SWIFT_VERSION = 4.2; 297 | TARGETED_DEVICE_FAMILY = "1,2"; 298 | }; 299 | name = Debug; 300 | }; 301 | 995B051C202ACCB100FCEF43 /* Release */ = { 302 | isa = XCBuildConfiguration; 303 | buildSettings = { 304 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 305 | CODE_SIGN_STYLE = Automatic; 306 | DEVELOPMENT_TEAM = HBBKUSRZMD; 307 | INFOPLIST_FILE = ScrollRulerViewDemo/Info.plist; 308 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 309 | PRODUCT_BUNDLE_IDENTIFIER = com.tuandai.app.ScrollRulerViewDemo; 310 | PRODUCT_NAME = "$(TARGET_NAME)"; 311 | SWIFT_VERSION = 4.2; 312 | TARGETED_DEVICE_FAMILY = "1,2"; 313 | }; 314 | name = Release; 315 | }; 316 | /* End XCBuildConfiguration section */ 317 | 318 | /* Begin XCConfigurationList section */ 319 | 995B0503202ACCB100FCEF43 /* Build configuration list for PBXProject "ScrollRulerViewDemo" */ = { 320 | isa = XCConfigurationList; 321 | buildConfigurations = ( 322 | 995B0518202ACCB100FCEF43 /* Debug */, 323 | 995B0519202ACCB100FCEF43 /* Release */, 324 | ); 325 | defaultConfigurationIsVisible = 0; 326 | defaultConfigurationName = Release; 327 | }; 328 | 995B051A202ACCB100FCEF43 /* Build configuration list for PBXNativeTarget "ScrollRulerViewDemo" */ = { 329 | isa = XCConfigurationList; 330 | buildConfigurations = ( 331 | 995B051B202ACCB100FCEF43 /* Debug */, 332 | 995B051C202ACCB100FCEF43 /* Release */, 333 | ); 334 | defaultConfigurationIsVisible = 0; 335 | defaultConfigurationName = Release; 336 | }; 337 | /* End XCConfigurationList section */ 338 | }; 339 | rootObject = 995B0500202ACCB100FCEF43 /* Project object */; 340 | } 341 | -------------------------------------------------------------------------------- /ScrollRulerViewDemo/ScrollRulerViewDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ScrollRulerViewDemo/ScrollRulerViewDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ScrollRulerViewDemo/ScrollRulerViewDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ScrollRulerViewDemo 4 | // 5 | // Created by lisilong on 2018/2/7. 6 | // Copyright © 2018年 tuandai. 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 | -------------------------------------------------------------------------------- /ScrollRulerViewDemo/ScrollRulerViewDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /ScrollRulerViewDemo/ScrollRulerViewDemo/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 | -------------------------------------------------------------------------------- /ScrollRulerViewDemo/ScrollRulerViewDemo/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 | -------------------------------------------------------------------------------- /ScrollRulerViewDemo/ScrollRulerViewDemo/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 | -------------------------------------------------------------------------------- /ScrollRulerViewDemo/ScrollRulerViewDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // ScrollRulerViewDemo 4 | // 5 | // Created by lisilong on 2018/2/7. 6 | // Copyright © 2018年 tuandai. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // 屏幕宽度 12 | let ScreenWidth = UIScreen.main.bounds.width 13 | // 屏幕高度 14 | let ScreenHeight = UIScreen.main.bounds.height 15 | 16 | class ViewController: UIViewController { 17 | lazy var lazyRulerView: TDScrollRulerView = { [unowned self] in 18 | var rulerView = TDScrollRulerView(frame: CGRect(x: 0, y: Int(ScreenHeight/2), width: Int(ScreenWidth), height: 67), 19 | strokeColor: UIColor.lightGray, 20 | middleLineColor: .red, 21 | min: 1000.0, 22 | max: 10000, 23 | numFontSize: 12, 24 | endNum: 150000, 25 | endText: "剩余金额", 26 | endTextColor: UIColor.orange, 27 | endtextFontSize: 11) 28 | rulerView.delegate = self 29 | return rulerView 30 | }() 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | self.view.addSubview(lazyRulerView) 36 | lazyRulerView.reloadRulerView(min: 1000.0, max: 10000, numFontSize: 12, endNum: 5000.0, endText: "剩余金额", endTextColor: UIColor.orange, endtextFontSize: 11) 37 | } 38 | 39 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 40 | lazyRulerView.scrollToEndNumber(endN: 1000.0) 41 | } 42 | } 43 | 44 | extension ViewController: TDScrollRulerDelegate { 45 | func scrollViewWillBeginDragging(value: CGFloat) { 46 | } 47 | 48 | func scrollViewDidEndScrollingAnimation(value: CGFloat) { 49 | } 50 | func scrollViewDidScroll(value: CGFloat) { 51 | print("滑动的值------->"+"\(value)") 52 | } 53 | 54 | func scrollViewDidEndDragging(value: CGFloat) { 55 | print("滑动的值end------->"+"\(value)") 56 | if value > 1000.0 { 57 | lazyRulerView.scrollToEndNumber(endN: 1000.0) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ScrollRulerViewDemo/ScrollRulerViewDemo/class/TDScrollRulerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TDScrollRulerView.swift 3 | // PaishengFinance 4 | // 5 | // Created by lisilong on 2018/2/2. 6 | // Copyright © 2018年 TDW.CN. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | fileprivate var textRulerFont = UIFont.systemFont(ofSize: 12) // 尺子标码字体大小 12 | fileprivate var textFont = UIFont.systemFont(ofSize: 11) // 尺子文案字体大小 13 | 14 | fileprivate let rulerGap = 8 15 | fileprivate let rulerLong = 12 16 | fileprivate let rulerShort = 6 17 | fileprivate var collectionHeight = 55.0 18 | fileprivate let labelHeight = 12.0 19 | fileprivate let merge = 3.0 20 | fileprivate let lineWidth: CGFloat = 0.5 21 | fileprivate let textHeight: CGFloat = 14.0 22 | fileprivate let defaultColor: UIColor = UIColor(red: 221.0/255.0, green: 221.0/255.0, blue: 221.0/255.0, alpha: 1.0) 23 | 24 | 25 | /***************TD************分************割************线***********/ 26 | 27 | class TDRulerView: UIView { 28 | var minValue: CGFloat = 0.0 29 | var maxValue: CGFloat = 0.0 30 | var unit: String = "" 31 | var step: CGFloat = 0.0 32 | var betweenNumber = 0 33 | var strokeColor: UIColor = defaultColor 34 | var startNum: CGFloat = 0.0 { // 起点值 35 | didSet { 36 | var startNumStr = String(format: "%.0f%@", startNum, unit) 37 | if startNum > 1000000 { 38 | startNumStr = String(format: "%.2f万%@", startNum/10000, unit) 39 | } 40 | infoLabel.text = startNumStr 41 | } 42 | } 43 | 44 | lazy var infoLabel: UILabel = { 45 | let info = UILabel() 46 | info.text = "0" 47 | info.font = textRulerFont 48 | info.textAlignment = .center 49 | info.tag = 667 50 | info.isHidden = false 51 | return info 52 | }() 53 | 54 | init(frame: CGRect, strokeColor: UIColor = defaultColor, betweenNumber: Int) { 55 | super.init(frame: frame) 56 | 57 | self.strokeColor = strokeColor 58 | self.betweenNumber = betweenNumber 59 | 60 | // 刻度值 61 | let width: CGFloat = 60.0 62 | let textTopY:CGFloat = frame.size.height - CGFloat(rulerLong) - 5.0 - textHeight 63 | infoLabel.frame = CGRect(x: 0 - width / 2, y: textTopY, width: width, height: textHeight) 64 | infoLabel.textColor = strokeColor 65 | addSubview(infoLabel) 66 | 67 | // 长刻度 68 | let longLineY = frame.size.height - CGFloat(rulerLong) 69 | let line = UIView(frame: CGRect(x: 0, y: longLineY, width: lineWidth, height: CGFloat(rulerLong))) 70 | line.backgroundColor = strokeColor 71 | addSubview(line) 72 | 73 | // 短刻度 74 | let lineCenterX = CGFloat(rulerGap) 75 | let shortLineY = frame.size.height - CGFloat(rulerShort) 76 | 77 | if betweenNumber > 0 { 78 | for i in 0...betweenNumber { 79 | let line = UIView(frame: CGRect(x: lineCenterX*CGFloat(i), y: shortLineY, width: lineWidth, height: CGFloat(rulerShort))) 80 | line.backgroundColor = strokeColor 81 | addSubview(line) 82 | } 83 | } 84 | } 85 | 86 | required init?(coder aDecoder: NSCoder) { 87 | fatalError("init(coder:) has not been implemented") 88 | } 89 | 90 | override func draw(_ rect: CGRect) { 91 | let startX:CGFloat = 0 92 | let lineCenterX = CGFloat(rulerGap) 93 | let textTopY:CGFloat = rect.size.height - CGFloat(rulerLong) - 5.0 - textHeight 94 | 95 | let context = UIGraphicsGetCurrentContext() 96 | context?.setStrokeColor(strokeColor.cgColor) 97 | for i in 0...betweenNumber { 98 | if i%betweenNumber == 0 { 99 | let num = CGFloat(i)*step+minValue 100 | let info: UILabel? = self.viewWithTag(667) as? UILabel 101 | if num == startNum { 102 | info?.isHidden = false 103 | } else { 104 | if i == 0 { // 表示cell的头部文案 105 | info?.isHidden = true 106 | } 107 | 108 | var numStr = String(format: "%.0f%@", num, unit) 109 | if num > 1000000 { 110 | numStr = String(format: "%.2f万%@", num/10000, unit) 111 | } 112 | let attribute:Dictionary = [NSAttributedString.Key.font:textRulerFont,NSAttributedString.Key.foregroundColor: strokeColor] as [NSAttributedString.Key : Any] 113 | let width = numStr.boundingRect(with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), options: NSStringDrawingOptions.usesLineFragmentOrigin, 114 | attributes: attribute,context: nil).size.width 115 | numStr.draw(in: CGRect.init(x: startX+lineCenterX*CGFloat(i)-width/2, y: textTopY, width: width, height: textHeight), withAttributes: attribute) 116 | } 117 | } else {} 118 | context!.strokePath() 119 | } 120 | } 121 | } 122 | 123 | /***************TD************分************割************线***********/ 124 | 125 | class TDFooterRulerView: UIView { 126 | var footerMaxValue = 0 127 | var footerUnit = "" 128 | var strokeColor: UIColor = defaultColor 129 | 130 | init(frame: CGRect, strokeColor: UIColor = defaultColor) { 131 | super.init(frame: frame) 132 | 133 | self.strokeColor = strokeColor 134 | 135 | let shortLineY = frame.size.height - CGFloat(labelHeight + merge + Double(rulerLong)) 136 | let line = UIView.init(frame: CGRect(x: 0, y: shortLineY, width: lineWidth, height: CGFloat(rulerLong))) 137 | line.backgroundColor = strokeColor 138 | self.addSubview(line) 139 | } 140 | 141 | required init?(coder aDecoder: NSCoder) { 142 | fatalError("init(coder:) has not been implemented") 143 | } 144 | 145 | override func draw(_ rect: CGRect) { 146 | let textTopY: CGFloat = rect.size.height - CGFloat(labelHeight + merge) - CGFloat(rulerLong) - 5.0 - textHeight 147 | let context = UIGraphicsGetCurrentContext() 148 | var numStr:NSString = NSString(format: "%d%@", footerMaxValue,footerUnit) 149 | if footerMaxValue > 1000000 { 150 | numStr = NSString(format: "%.2f万%@", footerMaxValue/10000,footerUnit) 151 | } 152 | let attribute:Dictionary = [NSAttributedString.Key.font:textRulerFont,NSAttributedString.Key.foregroundColor:strokeColor] as [NSAttributedString.Key : Any] 153 | let width = numStr.boundingRect(with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), 154 | options: NSStringDrawingOptions.usesLineFragmentOrigin, 155 | attributes: attribute,context: nil).size.width 156 | numStr.draw(in: CGRect(x: CGFloat(0 - width / 2), y: textTopY, width: width, height: textHeight), withAttributes: attribute) 157 | context!.strokePath() 158 | } 159 | } 160 | 161 | /***************TD************分************割************线***********/ 162 | 163 | protocol TDScrollRulerDelegate: NSObjectProtocol { 164 | 165 | /// 滚动时,返回当前值 166 | /// 167 | /// - Parameters: 168 | /// - value: 当前值 169 | func scrollViewDidScroll(value: CGFloat) 170 | 171 | /// 尺子即将被拖拽 172 | /// 173 | /// - Parameter value: 当前值 174 | func scrollViewWillBeginDragging(value: CGFloat) 175 | 176 | /// 尺子停止滚动协议 177 | /// 178 | /// - Parameter value: 停止滚动值 179 | func scrollViewDidEndDragging(value: CGFloat) 180 | 181 | /// 尺子滚动动画停止协议 182 | /// 183 | /// - Parameter value: 停止滚动值 184 | func scrollViewDidEndScrollingAnimation(value: CGFloat) 185 | } 186 | 187 | class TDScrollRulerView: UIView { 188 | private lazy var lazyCollectionView: UICollectionView = { [unowned self] in 189 | let flowLayout = UICollectionViewFlowLayout() 190 | flowLayout.scrollDirection = UICollectionView.ScrollDirection.horizontal 191 | flowLayout.sectionInset = UIEdgeInsets.init(top: 0, left: 0, bottom: 0, right: 0) 192 | let view = UICollectionView.init(frame: CGRect.init(x: 0, y: 0, width: self.bounds.size.width, height: CGFloat(collectionHeight)), collectionViewLayout: flowLayout) 193 | view.backgroundColor = .white 194 | view.bounces = true 195 | view.showsHorizontalScrollIndicator = false 196 | view.showsVerticalScrollIndicator = false 197 | view.delegate = self 198 | view.dataSource = self 199 | view.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "headCell") 200 | view.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "footerCell") 201 | view.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "customeCell") 202 | return view 203 | }() 204 | private var betweenNum:Int = 0 205 | private var stepNum = 0 // 分多少个大区 206 | 207 | var maxValue: CGFloat = 0.0 // 最大值 208 | var minValue: CGFloat = 0.0 // 最小值 209 | var step: CGFloat = 0.0 // 每个区被分割的分数 210 | var unit: String = "" // 单位 211 | 212 | var endNumber: CGFloat = 0.0 // 最大有效值 213 | var endNumText: String = "" // 最大有效值文案 214 | var endNumTextColor: UIColor = .orange // 最大有效值文案 215 | var strokeColor: UIColor = .lightGray // 标尺颜色 216 | 217 | weak var delegate: TDScrollRulerDelegate? 218 | 219 | // init 220 | 221 | /// 游标卡尺 222 | /// 223 | /// - Parameters: 224 | /// - frame: 卡尺大小 225 | /// - min: 卡尺最小值 226 | /// - max: 卡尺最大值 227 | /// - part: 每个区被分割的分数 228 | /// - step: 一个区间的大小,默认1000 229 | /// - unit: 单位 230 | /// - numFontSize: 数值文字大小 231 | /// - endNum: 最大有效值 232 | /// - endText: 最大有效值文案 233 | /// - endTextColor: 最大有效值文案颜色 234 | /// - endtextFontSize: 最大有效值文案字体大小 235 | /// - strokeColor: 卡尺颜色 236 | public init(frame: CGRect, strokeColor: UIColor, middleLineColor: UIColor, min: CGFloat, max: CGFloat, part: Int = 10, step: Int = 1000, unit: String = "", numFontSize: CGFloat, endNum: CGFloat, endText: String, endTextColor: UIColor, endtextFontSize: CGFloat) { 237 | super.init(frame: frame) 238 | backgroundColor = .white 239 | 240 | self.strokeColor = strokeColor 241 | self.maxValue = max 242 | self.minValue = min 243 | self.step = CGFloat(step / 10) 244 | self.unit = unit 245 | self.endNumber = endNum 246 | self.endNumText = endText 247 | self.endNumTextColor = endTextColor 248 | self.betweenNum = part 249 | self.stepNum = Int((max - min) / CGFloat(self.step)) / self.betweenNum 250 | 251 | textRulerFont = UIFont.systemFont(ofSize: numFontSize > 10 ? numFontSize : 12) 252 | 253 | collectionHeight = Double(frame.size.height) 254 | 255 | lazyCollectionView.frame = CGRect.init(x: 0, y: 0, width: frame.size.width, height: CGFloat(collectionHeight)) 256 | self.addSubview(lazyCollectionView) 257 | 258 | let lineH = collectionHeight - labelHeight - merge 259 | let line = UIView.init(frame: CGRect(x: self.bounds.size.width / 2.0 - (lineWidth / 2), y: 0, width: lineWidth, height: CGFloat(lineH))) 260 | line.backgroundColor = middleLineColor 261 | self.addSubview(line) 262 | 263 | let bottomLine = UIView(frame: CGRect(x: 0, y: CGFloat(collectionHeight) - CGFloat(labelHeight + merge), width: frame.size.width, height: lineWidth)) 264 | bottomLine.backgroundColor = strokeColor 265 | addSubview(bottomLine) 266 | } 267 | 268 | required init?(coder aDecoder: NSCoder) { 269 | fatalError("init(coder:) has not been implemented") 270 | } 271 | 272 | func reloadRulerView(min: CGFloat, max: CGFloat, part: Int = 10, step: Int = 1000, unit: String = "", numFontSize: CGFloat, endNum: CGFloat, endText: String, endTextColor: UIColor, endtextFontSize: CGFloat) { 273 | 274 | self.maxValue = max 275 | self.minValue = min 276 | self.step = CGFloat(step / 10) 277 | self.unit = unit 278 | self.endNumber = endNum 279 | self.endNumText = endText 280 | self.endNumTextColor = endTextColor 281 | self.betweenNum = part 282 | self.stepNum = Int((max - min) / CGFloat(self.step)) / self.betweenNum 283 | 284 | textRulerFont = UIFont.systemFont(ofSize: numFontSize > 10 ? numFontSize : 12) 285 | lazyCollectionView.reloadData() 286 | } 287 | 288 | // actions 289 | 290 | /// 滚动到指定值的位置 291 | /// 292 | /// - Parameter endN: 指定的值 293 | public func scrollToEndNumber(endN: CGFloat, animated: Bool = true) { 294 | if (endN - minValue) >= 0.0 { 295 | self.setRealValueAndAnimated(realValue: (endN - minValue) / step, animated: true) 296 | } 297 | } 298 | 299 | func setRealValueAndAnimated(realValue: CGFloat, animated: Bool) { 300 | lazyCollectionView.setContentOffset(CGPoint.init(x: Int(realValue) * rulerGap, y: 0), animated: animated) 301 | } 302 | } 303 | 304 | 305 | // MARK: - 306 | 307 | extension TDScrollRulerView: UICollectionViewDataSource { 308 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 309 | return 2 + stepNum 310 | } 311 | 312 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 313 | if indexPath.item == 0 { 314 | let cell:UICollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "headCell", for: indexPath) 315 | return cell 316 | 317 | } else if indexPath.item == stepNum+1 { 318 | let cell:UICollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "footerCell", for: indexPath) 319 | var footerView:TDFooterRulerView? = cell.contentView.viewWithTag(1001) as? TDFooterRulerView 320 | if footerView == nil { 321 | let frame = CGRect.init(x: 0, y: 0, width: Int(self.frame.size.width/2), height: Int(collectionHeight)) 322 | footerView = TDFooterRulerView(frame: frame, strokeColor: strokeColor) 323 | footerView!.backgroundColor = .clear 324 | footerView!.footerMaxValue = Int(maxValue) 325 | footerView!.footerUnit = unit 326 | footerView!.tag = 1001 327 | footerView!.strokeColor = strokeColor 328 | cell.contentView.addSubview(footerView!) 329 | } 330 | return cell 331 | 332 | } else { 333 | let cell:UICollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "customeCell", for: indexPath) 334 | var rulerView: TDRulerView? = cell.contentView.viewWithTag(1002) as? TDRulerView 335 | if rulerView == nil { 336 | let frame = CGRect.init(x: 0, y: 0, width: rulerGap*betweenNum, height: Int(collectionHeight - labelHeight - merge)) 337 | rulerView = TDRulerView(frame: frame, strokeColor: strokeColor, betweenNumber: betweenNum) 338 | rulerView!.backgroundColor = .white 339 | rulerView!.step = step 340 | rulerView!.unit = unit 341 | rulerView?.startNum = minValue 342 | rulerView!.tag = 1002 343 | cell.contentView.addSubview(rulerView!) 344 | 345 | let endNumLabel = UILabel() 346 | endNumLabel.textColor = endNumTextColor 347 | endNumLabel.textAlignment = .center 348 | endNumLabel.font = textFont 349 | endNumLabel.adjustsFontSizeToFitWidth = true 350 | endNumLabel.text = endNumText 351 | endNumLabel.frame = CGRect(x: 0.0, y: Double(rulerView!.bounds.size.height + CGFloat(merge)), width: Double(cell.bounds.size.width), height: labelHeight) 352 | endNumLabel.isHidden = true 353 | endNumLabel.tag = 9999 354 | cell.contentView.addSubview(endNumLabel) 355 | 356 | let line = UIView() 357 | line.backgroundColor = endNumTextColor 358 | line.tag = 9998 359 | line.isHidden = true 360 | cell.contentView.insertSubview(line, aboveSubview: rulerView!) 361 | } 362 | 363 | rulerView!.minValue = step * CGFloat((indexPath.item-1)) * CGFloat(betweenNum) + minValue 364 | rulerView!.maxValue = step * CGFloat(indexPath.item) * CGFloat(betweenNum) 365 | rulerView!.setNeedsDisplay() 366 | 367 | // 显示限制最大值 368 | let endNumLabel: UILabel? = cell.contentView.viewWithTag(9999) as? UILabel 369 | let line: UIView? = cell.contentView.viewWithTag(9998) 370 | var isHidden = true 371 | if rulerView?.minValue != nil, rulerView?.maxValue != nil { 372 | if endNumber > (rulerView?.minValue)!, endNumber <= (rulerView?.maxValue)! { 373 | isHidden = false 374 | 375 | let end = endNumber - (rulerView?.minValue)! 376 | let n: Int = Int(end / step) 377 | let lineH = (n != 10) ? rulerShort : rulerLong 378 | let x: CGFloat = CGFloat(n) * cell.bounds.size.width / CGFloat(betweenNum) - 0.5 379 | let y: CGFloat = cell.bounds.size.height - CGFloat(lineH) - CGFloat(labelHeight) - CGFloat(merge) 380 | line?.frame = CGRect(x: x, y: y, width: lineWidth, height: CGFloat(lineH)) 381 | endNumLabel?.center.x = (line?.center.x)! 382 | endNumLabel?.text = endNumText 383 | } 384 | } 385 | endNumLabel?.isHidden = isHidden 386 | line?.isHidden = isHidden 387 | return cell 388 | } 389 | } 390 | } 391 | 392 | 393 | // MARK: - 394 | 395 | extension TDScrollRulerView: UICollectionViewDelegate { 396 | 397 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 398 | let value = Int(scrollView.contentOffset.x) / rulerGap 399 | let totalValue = CGFloat(value) * step + minValue 400 | delegate?.scrollViewDidScroll(value: totalValue) 401 | } 402 | 403 | func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { 404 | let value = Int(scrollView.contentOffset.x) / rulerGap 405 | let totalValue = CGFloat(value) * step + minValue 406 | delegate?.scrollViewWillBeginDragging(value: totalValue) 407 | } 408 | 409 | func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { 410 | if !decelerate { 411 | // 1. 当不带滚动动画的时候,调节标尺 412 | self.updateScrollViewContentOffsetX(scrollView) 413 | } else {} 414 | } 415 | 416 | func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { 417 | let value = Int(scrollView.contentOffset.x) / rulerGap 418 | let totalValue = CGFloat(value) * step + minValue 419 | delegate?.scrollViewDidEndScrollingAnimation(value: totalValue) 420 | } 421 | 422 | func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 423 | // 2. 当带滚动动画的时候,调节标尺 424 | self.updateScrollViewContentOffsetX(scrollView) 425 | } 426 | 427 | func updateScrollViewContentOffsetX(_ scrollView: UIScrollView) { 428 | self.setRealValueAndAnimated(realValue: CGFloat(scrollView.contentOffset.x) / CGFloat(rulerGap), animated: true) 429 | let value = Int(scrollView.contentOffset.x) / rulerGap 430 | let totalValue = CGFloat(value) * step + minValue 431 | delegate?.scrollViewDidEndDragging(value: totalValue) 432 | } 433 | } 434 | 435 | 436 | // MARK: - 437 | 438 | extension TDScrollRulerView: UICollectionViewDelegateFlowLayout { 439 | 440 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 441 | if indexPath.item == 0 || indexPath.item == stepNum + 1 { 442 | return CGSize(width: Double(self.frame.size.width / 2), height: collectionHeight) 443 | } 444 | return CGSize(width: Double(rulerGap * betweenNum), height: collectionHeight) 445 | } 446 | 447 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { 448 | return UIEdgeInsets.init(top: 0, left: 0, bottom: 0, right: 0) 449 | } 450 | 451 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { 452 | return 0 453 | } 454 | 455 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { 456 | return 0 457 | } 458 | } 459 | 460 | --------------------------------------------------------------------------------