├── .DS_Store ├── .gitignore ├── LICENSE ├── README.md ├── WLUnitField.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── WLUnitField ├── .DS_Store ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Classes │ ├── WLUnitField.h │ ├── WLUnitField.m │ ├── WLUnitFieldTextPosition.h │ ├── WLUnitFieldTextPosition.m │ ├── WLUnitFieldTextRange.h │ └── WLUnitFieldTextRange.m ├── Info.plist ├── RandomColor │ ├── YGColorDefinition.h │ ├── YGColorDefinition.m │ └── iOS │ │ ├── UIColor+randomColor.h │ │ └── UIColor+randomColor.m ├── ViewController.h ├── ViewController.m └── main.m ├── demo.gif └── line.png /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhwayne/WLUnitField/8e545525e3546cd7d097e2df85f17b7e432fca93/.DS_Store -------------------------------------------------------------------------------- /.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 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | # CocoaPods 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 35 | # 36 | # Pods/ 37 | 38 | # Carthage 39 | # 40 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 41 | # Carthage/Checkouts 42 | 43 | Carthage/Build 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 51 | 52 | fastlane/report.xml 53 | fastlane/screenshots 54 | 55 | #Code Injection 56 | # 57 | # After new code Injection tools there's a generated folder /iOSInjectionProject 58 | # https://github.com/johnno1962/injectionforxcode 59 | 60 | iOSInjectionProject/ 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Wayne 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 | # 简介(Introduction) 2 | 3 | 这是一个优雅简洁的密码/验证码输入框,你可以像使用`UITextField`一样去使用`WLUnitField`。 4 | This is an elegant and concise password/verification code text field. You can use `WLUnitField` just like `UITextField`. 5 | 6 | ![](./demo.gif) 7 | 8 | # 功能特点(Features) 9 | - 支持自动布局(Auto layout supports) 10 | - 提供两种界面面样式:边框和下划线(Provide two UI styles: border-based and underline-based) 11 | - 支持自动填充验证码,仅限 iOS 12 系统(Autofill One time code supports, only for iOS 12+) 12 | 13 | # 使用方式(Usage) 14 | 15 | `WLUnitField`的使用非常简单。它继承自`UIControl`,你可以给它添加以下 3 种`UIControlEvents`: 16 | `WLUnitField` is very sample to use. You use the following 3 kinds of `UIControlEvents`: 17 | 18 | * UIControlEventEditingDidBegin 19 | * UIControlEventEditingChanged 20 | * UIControlEventEditingDidEnd 21 | 22 | > 其他一些非必须的 event 已被忽略。 23 | > Some other non-essential events have been ignored. 24 | 25 | 使用示例(Case): 26 | 27 | ``` Objective-C 28 | - (void)viewDidLoad { 29 | [super viewDidLoad]; 30 | // Do any additional setup after loading the view, typically from a nib. 31 | 32 | WLUnitField *uniField = [[WLUnitField alloc] initWithInputUnitCount:4]; 33 | uniField.frame = CGRectMake(40, 40, 240, 1); 34 | uniField.delegate = self; 35 | uniField.unitSpace = 12; 36 | uniField.borderRadius = 4; 37 | [uniField sizeToFit]; 38 | [uniField addTarget:self action:@selector(unitFieldEditingChanged:) forControlEvents:UIControlEventEditingChanged]; 39 | 40 | [self.view addSubview:uniField]; 41 | } 42 | 43 | - (IBAction)unitFieldEditingChanged:(WLUnitField *)sender { 44 | NSLog(@"%s, %@", __FUNCTION__, sender.text); 45 | } 46 | ``` 47 | 48 | # License 49 | MIT License 50 | -------------------------------------------------------------------------------- /WLUnitField.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B0130E881DE56EE0001E6466 /* UIColor+randomColor.m in Sources */ = {isa = PBXBuildFile; fileRef = B0130E851DE56EE0001E6466 /* UIColor+randomColor.m */; }; 11 | B0130E891DE56EE0001E6466 /* YGColorDefinition.m in Sources */ = {isa = PBXBuildFile; fileRef = B0130E871DE56EE0001E6466 /* YGColorDefinition.m */; }; 12 | B089872D1DE41A91003B3A6E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = B089872C1DE41A91003B3A6E /* main.m */; }; 13 | B08987301DE41A91003B3A6E /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = B089872F1DE41A91003B3A6E /* AppDelegate.m */; }; 14 | B08987331DE41A91003B3A6E /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B08987321DE41A91003B3A6E /* ViewController.m */; }; 15 | B08987361DE41A91003B3A6E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B08987341DE41A91003B3A6E /* Main.storyboard */; }; 16 | B08987381DE41A92003B3A6E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B08987371DE41A92003B3A6E /* Assets.xcassets */; }; 17 | B089873B1DE41A92003B3A6E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B08987391DE41A92003B3A6E /* LaunchScreen.storyboard */; }; 18 | B0C1E17E1F591E1600E75FCC /* WLUnitField.m in Sources */ = {isa = PBXBuildFile; fileRef = B0C1E17D1F591E1600E75FCC /* WLUnitField.m */; }; 19 | B0E2575D225480B5008C34FB /* WLUnitFieldTextRange.m in Sources */ = {isa = PBXBuildFile; fileRef = B0E2575C225480B5008C34FB /* WLUnitFieldTextRange.m */; }; 20 | B0E25760225480D2008C34FB /* WLUnitFieldTextPosition.m in Sources */ = {isa = PBXBuildFile; fileRef = B0E2575F225480D2008C34FB /* WLUnitFieldTextPosition.m */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXFileReference section */ 24 | B0130E841DE56EE0001E6466 /* UIColor+randomColor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIColor+randomColor.h"; sourceTree = ""; }; 25 | B0130E851DE56EE0001E6466 /* UIColor+randomColor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIColor+randomColor.m"; sourceTree = ""; }; 26 | B0130E861DE56EE0001E6466 /* YGColorDefinition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YGColorDefinition.h; sourceTree = ""; }; 27 | B0130E871DE56EE0001E6466 /* YGColorDefinition.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YGColorDefinition.m; sourceTree = ""; }; 28 | B08987281DE41A91003B3A6E /* WLUnitField.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WLUnitField.app; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | B089872C1DE41A91003B3A6E /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 30 | B089872E1DE41A91003B3A6E /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 31 | B089872F1DE41A91003B3A6E /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 32 | B08987311DE41A91003B3A6E /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 33 | B08987321DE41A91003B3A6E /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 34 | B08987351DE41A91003B3A6E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 35 | B08987371DE41A92003B3A6E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 36 | B089873A1DE41A92003B3A6E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 37 | B089873C1DE41A92003B3A6E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 38 | B0C1E17C1F591E1600E75FCC /* WLUnitField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WLUnitField.h; sourceTree = ""; }; 39 | B0C1E17D1F591E1600E75FCC /* WLUnitField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WLUnitField.m; sourceTree = ""; }; 40 | B0E2575B225480B5008C34FB /* WLUnitFieldTextRange.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WLUnitFieldTextRange.h; sourceTree = ""; }; 41 | B0E2575C225480B5008C34FB /* WLUnitFieldTextRange.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WLUnitFieldTextRange.m; sourceTree = ""; }; 42 | B0E2575E225480D2008C34FB /* WLUnitFieldTextPosition.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WLUnitFieldTextPosition.h; sourceTree = ""; }; 43 | B0E2575F225480D2008C34FB /* WLUnitFieldTextPosition.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WLUnitFieldTextPosition.m; sourceTree = ""; }; 44 | /* End PBXFileReference section */ 45 | 46 | /* Begin PBXFrameworksBuildPhase section */ 47 | B08987251DE41A91003B3A6E /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | /* End PBXFrameworksBuildPhase section */ 55 | 56 | /* Begin PBXGroup section */ 57 | B0130E821DE56EE0001E6466 /* RandomColor */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | B0130E831DE56EE0001E6466 /* iOS */, 61 | B0130E861DE56EE0001E6466 /* YGColorDefinition.h */, 62 | B0130E871DE56EE0001E6466 /* YGColorDefinition.m */, 63 | ); 64 | path = RandomColor; 65 | sourceTree = ""; 66 | }; 67 | B0130E831DE56EE0001E6466 /* iOS */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | B0130E841DE56EE0001E6466 /* UIColor+randomColor.h */, 71 | B0130E851DE56EE0001E6466 /* UIColor+randomColor.m */, 72 | ); 73 | path = iOS; 74 | sourceTree = ""; 75 | }; 76 | B089871F1DE41A91003B3A6E = { 77 | isa = PBXGroup; 78 | children = ( 79 | B089872A1DE41A91003B3A6E /* WLUnitField */, 80 | B08987291DE41A91003B3A6E /* Products */, 81 | ); 82 | sourceTree = ""; 83 | }; 84 | B08987291DE41A91003B3A6E /* Products */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | B08987281DE41A91003B3A6E /* WLUnitField.app */, 88 | ); 89 | name = Products; 90 | sourceTree = ""; 91 | }; 92 | B089872A1DE41A91003B3A6E /* WLUnitField */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | B0C1E17B1F591E1600E75FCC /* Classes */, 96 | B0130E821DE56EE0001E6466 /* RandomColor */, 97 | B089872E1DE41A91003B3A6E /* AppDelegate.h */, 98 | B089872F1DE41A91003B3A6E /* AppDelegate.m */, 99 | B08987311DE41A91003B3A6E /* ViewController.h */, 100 | B08987321DE41A91003B3A6E /* ViewController.m */, 101 | B08987341DE41A91003B3A6E /* Main.storyboard */, 102 | B08987371DE41A92003B3A6E /* Assets.xcassets */, 103 | B08987391DE41A92003B3A6E /* LaunchScreen.storyboard */, 104 | B089873C1DE41A92003B3A6E /* Info.plist */, 105 | B089872B1DE41A91003B3A6E /* Supporting Files */, 106 | ); 107 | path = WLUnitField; 108 | sourceTree = ""; 109 | }; 110 | B089872B1DE41A91003B3A6E /* Supporting Files */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | B089872C1DE41A91003B3A6E /* main.m */, 114 | ); 115 | name = "Supporting Files"; 116 | sourceTree = ""; 117 | }; 118 | B0C1E17B1F591E1600E75FCC /* Classes */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | B0C1E17C1F591E1600E75FCC /* WLUnitField.h */, 122 | B0C1E17D1F591E1600E75FCC /* WLUnitField.m */, 123 | B0E2575B225480B5008C34FB /* WLUnitFieldTextRange.h */, 124 | B0E2575C225480B5008C34FB /* WLUnitFieldTextRange.m */, 125 | B0E2575E225480D2008C34FB /* WLUnitFieldTextPosition.h */, 126 | B0E2575F225480D2008C34FB /* WLUnitFieldTextPosition.m */, 127 | ); 128 | path = Classes; 129 | sourceTree = ""; 130 | }; 131 | /* End PBXGroup section */ 132 | 133 | /* Begin PBXNativeTarget section */ 134 | B08987271DE41A91003B3A6E /* WLUnitField */ = { 135 | isa = PBXNativeTarget; 136 | buildConfigurationList = B089873F1DE41A92003B3A6E /* Build configuration list for PBXNativeTarget "WLUnitField" */; 137 | buildPhases = ( 138 | B08987241DE41A91003B3A6E /* Sources */, 139 | B08987251DE41A91003B3A6E /* Frameworks */, 140 | B08987261DE41A91003B3A6E /* Resources */, 141 | ); 142 | buildRules = ( 143 | ); 144 | dependencies = ( 145 | ); 146 | name = WLUnitField; 147 | productName = WLUnitField; 148 | productReference = B08987281DE41A91003B3A6E /* WLUnitField.app */; 149 | productType = "com.apple.product-type.application"; 150 | }; 151 | /* End PBXNativeTarget section */ 152 | 153 | /* Begin PBXProject section */ 154 | B08987201DE41A91003B3A6E /* Project object */ = { 155 | isa = PBXProject; 156 | attributes = { 157 | LastUpgradeCheck = 1020; 158 | ORGANIZATIONNAME = wayne; 159 | TargetAttributes = { 160 | B08987271DE41A91003B3A6E = { 161 | CreatedOnToolsVersion = 8.1; 162 | DevelopmentTeam = BS4TL98W43; 163 | ProvisioningStyle = Automatic; 164 | }; 165 | }; 166 | }; 167 | buildConfigurationList = B08987231DE41A91003B3A6E /* Build configuration list for PBXProject "WLUnitField" */; 168 | compatibilityVersion = "Xcode 3.2"; 169 | developmentRegion = en; 170 | hasScannedForEncodings = 0; 171 | knownRegions = ( 172 | en, 173 | Base, 174 | ); 175 | mainGroup = B089871F1DE41A91003B3A6E; 176 | productRefGroup = B08987291DE41A91003B3A6E /* Products */; 177 | projectDirPath = ""; 178 | projectRoot = ""; 179 | targets = ( 180 | B08987271DE41A91003B3A6E /* WLUnitField */, 181 | ); 182 | }; 183 | /* End PBXProject section */ 184 | 185 | /* Begin PBXResourcesBuildPhase section */ 186 | B08987261DE41A91003B3A6E /* Resources */ = { 187 | isa = PBXResourcesBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | B089873B1DE41A92003B3A6E /* LaunchScreen.storyboard in Resources */, 191 | B08987381DE41A92003B3A6E /* Assets.xcassets in Resources */, 192 | B08987361DE41A91003B3A6E /* Main.storyboard in Resources */, 193 | ); 194 | runOnlyForDeploymentPostprocessing = 0; 195 | }; 196 | /* End PBXResourcesBuildPhase section */ 197 | 198 | /* Begin PBXSourcesBuildPhase section */ 199 | B08987241DE41A91003B3A6E /* Sources */ = { 200 | isa = PBXSourcesBuildPhase; 201 | buildActionMask = 2147483647; 202 | files = ( 203 | B08987331DE41A91003B3A6E /* ViewController.m in Sources */, 204 | B0C1E17E1F591E1600E75FCC /* WLUnitField.m in Sources */, 205 | B08987301DE41A91003B3A6E /* AppDelegate.m in Sources */, 206 | B0E2575D225480B5008C34FB /* WLUnitFieldTextRange.m in Sources */, 207 | B089872D1DE41A91003B3A6E /* main.m in Sources */, 208 | B0E25760225480D2008C34FB /* WLUnitFieldTextPosition.m in Sources */, 209 | B0130E881DE56EE0001E6466 /* UIColor+randomColor.m in Sources */, 210 | B0130E891DE56EE0001E6466 /* YGColorDefinition.m in Sources */, 211 | ); 212 | runOnlyForDeploymentPostprocessing = 0; 213 | }; 214 | /* End PBXSourcesBuildPhase section */ 215 | 216 | /* Begin PBXVariantGroup section */ 217 | B08987341DE41A91003B3A6E /* Main.storyboard */ = { 218 | isa = PBXVariantGroup; 219 | children = ( 220 | B08987351DE41A91003B3A6E /* Base */, 221 | ); 222 | name = Main.storyboard; 223 | sourceTree = ""; 224 | }; 225 | B08987391DE41A92003B3A6E /* LaunchScreen.storyboard */ = { 226 | isa = PBXVariantGroup; 227 | children = ( 228 | B089873A1DE41A92003B3A6E /* Base */, 229 | ); 230 | name = LaunchScreen.storyboard; 231 | sourceTree = ""; 232 | }; 233 | /* End PBXVariantGroup section */ 234 | 235 | /* Begin XCBuildConfiguration section */ 236 | B089873D1DE41A92003B3A6E /* Debug */ = { 237 | isa = XCBuildConfiguration; 238 | buildSettings = { 239 | ALWAYS_SEARCH_USER_PATHS = NO; 240 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 241 | CLANG_ANALYZER_NONNULL = YES; 242 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 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_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 251 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 252 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 253 | CLANG_WARN_EMPTY_BODY = YES; 254 | CLANG_WARN_ENUM_CONVERSION = YES; 255 | CLANG_WARN_INFINITE_RECURSION = YES; 256 | CLANG_WARN_INT_CONVERSION = YES; 257 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 258 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 259 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 260 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 261 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 262 | CLANG_WARN_STRICT_PROTOTYPES = YES; 263 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 264 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 265 | CLANG_WARN_UNREACHABLE_CODE = YES; 266 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 267 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 268 | COPY_PHASE_STRIP = NO; 269 | DEBUG_INFORMATION_FORMAT = dwarf; 270 | ENABLE_STRICT_OBJC_MSGSEND = YES; 271 | ENABLE_TESTABILITY = YES; 272 | GCC_C_LANGUAGE_STANDARD = gnu99; 273 | GCC_DYNAMIC_NO_PIC = NO; 274 | GCC_NO_COMMON_BLOCKS = YES; 275 | GCC_OPTIMIZATION_LEVEL = 0; 276 | GCC_PREPROCESSOR_DEFINITIONS = ( 277 | "DEBUG=1", 278 | "$(inherited)", 279 | ); 280 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 281 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 282 | GCC_WARN_UNDECLARED_SELECTOR = YES; 283 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 284 | GCC_WARN_UNUSED_FUNCTION = YES; 285 | GCC_WARN_UNUSED_VARIABLE = YES; 286 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 287 | MTL_ENABLE_DEBUG_INFO = YES; 288 | ONLY_ACTIVE_ARCH = YES; 289 | SDKROOT = iphoneos; 290 | TARGETED_DEVICE_FAMILY = "1,2"; 291 | }; 292 | name = Debug; 293 | }; 294 | B089873E1DE41A92003B3A6E /* Release */ = { 295 | isa = XCBuildConfiguration; 296 | buildSettings = { 297 | ALWAYS_SEARCH_USER_PATHS = NO; 298 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 299 | CLANG_ANALYZER_NONNULL = YES; 300 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 301 | CLANG_CXX_LIBRARY = "libc++"; 302 | CLANG_ENABLE_MODULES = YES; 303 | CLANG_ENABLE_OBJC_ARC = YES; 304 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 305 | CLANG_WARN_BOOL_CONVERSION = YES; 306 | CLANG_WARN_COMMA = YES; 307 | CLANG_WARN_CONSTANT_CONVERSION = YES; 308 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 309 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 310 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 311 | CLANG_WARN_EMPTY_BODY = YES; 312 | CLANG_WARN_ENUM_CONVERSION = YES; 313 | CLANG_WARN_INFINITE_RECURSION = YES; 314 | CLANG_WARN_INT_CONVERSION = YES; 315 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 316 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 317 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 318 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 319 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 320 | CLANG_WARN_STRICT_PROTOTYPES = YES; 321 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 322 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 323 | CLANG_WARN_UNREACHABLE_CODE = YES; 324 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 325 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 326 | COPY_PHASE_STRIP = NO; 327 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 328 | ENABLE_NS_ASSERTIONS = NO; 329 | ENABLE_STRICT_OBJC_MSGSEND = YES; 330 | GCC_C_LANGUAGE_STANDARD = gnu99; 331 | GCC_NO_COMMON_BLOCKS = YES; 332 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 333 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 334 | GCC_WARN_UNDECLARED_SELECTOR = YES; 335 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 336 | GCC_WARN_UNUSED_FUNCTION = YES; 337 | GCC_WARN_UNUSED_VARIABLE = YES; 338 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 339 | MTL_ENABLE_DEBUG_INFO = NO; 340 | SDKROOT = iphoneos; 341 | TARGETED_DEVICE_FAMILY = "1,2"; 342 | VALIDATE_PRODUCT = YES; 343 | }; 344 | name = Release; 345 | }; 346 | B08987401DE41A92003B3A6E /* Debug */ = { 347 | isa = XCBuildConfiguration; 348 | buildSettings = { 349 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 350 | DEVELOPMENT_TEAM = BS4TL98W43; 351 | INFOPLIST_FILE = WLUnitField/Info.plist; 352 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 353 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 354 | PRODUCT_BUNDLE_IDENTIFIER = com.zhwayne.WLUnitField; 355 | PRODUCT_NAME = "$(TARGET_NAME)"; 356 | }; 357 | name = Debug; 358 | }; 359 | B08987411DE41A92003B3A6E /* Release */ = { 360 | isa = XCBuildConfiguration; 361 | buildSettings = { 362 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 363 | DEVELOPMENT_TEAM = BS4TL98W43; 364 | INFOPLIST_FILE = WLUnitField/Info.plist; 365 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 366 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 367 | PRODUCT_BUNDLE_IDENTIFIER = com.zhwayne.WLUnitField; 368 | PRODUCT_NAME = "$(TARGET_NAME)"; 369 | }; 370 | name = Release; 371 | }; 372 | /* End XCBuildConfiguration section */ 373 | 374 | /* Begin XCConfigurationList section */ 375 | B08987231DE41A91003B3A6E /* Build configuration list for PBXProject "WLUnitField" */ = { 376 | isa = XCConfigurationList; 377 | buildConfigurations = ( 378 | B089873D1DE41A92003B3A6E /* Debug */, 379 | B089873E1DE41A92003B3A6E /* Release */, 380 | ); 381 | defaultConfigurationIsVisible = 0; 382 | defaultConfigurationName = Release; 383 | }; 384 | B089873F1DE41A92003B3A6E /* Build configuration list for PBXNativeTarget "WLUnitField" */ = { 385 | isa = XCConfigurationList; 386 | buildConfigurations = ( 387 | B08987401DE41A92003B3A6E /* Debug */, 388 | B08987411DE41A92003B3A6E /* Release */, 389 | ); 390 | defaultConfigurationIsVisible = 0; 391 | defaultConfigurationName = Release; 392 | }; 393 | /* End XCConfigurationList section */ 394 | }; 395 | rootObject = B08987201DE41A91003B3A6E /* Project object */; 396 | } 397 | -------------------------------------------------------------------------------- /WLUnitField.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /WLUnitField.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /WLUnitField/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhwayne/WLUnitField/8e545525e3546cd7d097e2df85f17b7e432fca93/WLUnitField/.DS_Store -------------------------------------------------------------------------------- /WLUnitField/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // WLUnitField 4 | // 5 | // Created by wayne on 16/11/22. 6 | // Copyright © 2016年 wayne. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /WLUnitField/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // WLUnitField 4 | // 5 | // Created by wayne on 16/11/22. 6 | // Copyright © 2016年 wayne. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // 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. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // 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. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // 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. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // 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. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /WLUnitField/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 | } -------------------------------------------------------------------------------- /WLUnitField/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 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /WLUnitField/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 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 99 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 122 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 145 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 168 | 175 | 176 | 177 | 178 | 179 | 180 | 186 | 193 | 194 | 195 | 196 | 197 | 198 | 204 | 211 | 212 | 213 | 214 | 215 | 216 | 222 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | -------------------------------------------------------------------------------- /WLUnitField/Classes/WLUnitField.h: -------------------------------------------------------------------------------- 1 | // 2 | // WLUnitField.h 3 | // WLUnitField 4 | // 5 | // Created by wayne on 16/11/22. 6 | // Copyright © 2016年 wayne. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | 14 | #ifdef NSFoundationVersionNumber_iOS_9_x_Max 15 | UIKIT_EXTERN NSNotificationName const WLUnitFieldDidBecomeFirstResponderNotification; 16 | UIKIT_EXTERN NSNotificationName const WLUnitFieldDidResignFirstResponderNotification; 17 | #else 18 | UIKIT_EXTERN NSString *const WLUnitFieldDidBecomeFirstResponderNotification; 19 | UIKIT_EXTERN NSString *const WLUnitFieldDidResignFirstResponderNotification; 20 | #endif 21 | 22 | /** 23 | UnitField 的外观风格 24 | 25 | - WLUnitFieldStyleBorder: 边框样式, UnitField 的默认样式 26 | - WLUnitFieldStyleUnderline: 下滑线样式 27 | */ 28 | typedef NS_ENUM(NSUInteger, WLUnitFieldStyle) { 29 | WLUnitFieldStyleBorder, 30 | WLUnitFieldStyleUnderline 31 | }; 32 | 33 | @protocol WLUnitFieldDelegate; 34 | 35 | IB_DESIGNABLE 36 | @interface WLUnitField : UIControl 37 | 38 | @property (nullable, nonatomic, weak) id delegate; 39 | 40 | /** 41 | 保留的用户输入的字符串,最好使用数字字符串,因为目前还不支持其他字符。 42 | */ 43 | @property (nullable, nonatomic, copy) IBInspectable NSString *text; 44 | @property(null_unspecified,nonatomic,copy) IBInspectable UITextContentType textContentType NS_AVAILABLE_IOS(10_0); // default is nil 45 | 46 | #if TARGET_INTERFACE_BUILDER 47 | /** 48 | 允许输入的个数。 49 | 目前 WLUnitField 允许的输入单元个数区间控制在 1 ~ 8 个。任何超过该范围内的赋值行为都将被忽略。 50 | */ 51 | @property (nonatomic, assign) IBInspectable NSUInteger inputUnitCount; 52 | 53 | /** 54 | UnitField 的外观风格, 默认为 WLUnitFieldStyleBorder. 55 | */ 56 | @property (nonatomic, assign) IBInspectable NSUInteger style; 57 | #else 58 | @property (nonatomic, assign, readonly) NSUInteger inputUnitCount; 59 | @property (nonatomic, assign, readonly) WLUnitFieldStyle style; 60 | #endif 61 | 62 | 63 | /** 64 | 每个 Unit 之间的距离,默认为 0 65 | ┌┈┈┈┬┈┈┈┬┈┈┈┬┈┈┈┐ 66 | ┆ 1 ┆ 2 ┆ 3 ┆ 4 ┆ unitSpace is 0. 67 | └┈┈┈┴┈┈┈┴┈┈┈┴┈┈┈┘ 68 | ┌┈┈┈┐┌┈┈┈┐┌┈┈┈┐┌┈┈┈┐ 69 | ┆ 1 ┆┆ 2 ┆┆ 3 ┆┆ 4 ┆ unitSpace is 6 70 | └┈┈┈┘└┈┈┈┘└┈┈┈┘└┈┈┈┘ 71 | */ 72 | @property (nonatomic, assign) IBInspectable NSUInteger unitSpace; 73 | 74 | /** 75 | 设置边框圆角 76 | ╭┈┈┈╮╭┈┈┈╮╭┈┈┈╮╭┈┈┈╮ 77 | ┆ 1 ┆┆ 2 ┆┆ 3 ┆┆ 4 ┆ unitSpace is 6, borderRadius is 4. 78 | ╰┈┈┈╯╰┈┈┈╯╰┈┈┈╯╰┈┈┈╯ 79 | ╭┈┈┈┬┈┈┈┬┈┈┈┬┈┈┈╮ 80 | ┆ 1 ┆ 2 ┆ 3 ┆ 4 ┆ unitSpace is 0, borderRadius is 4. 81 | ╰┈┈┈┴┈┈┈┴┈┈┈┴┈┈┈╯ 82 | */ 83 | @property (nonatomic, assign) IBInspectable CGFloat borderRadius; 84 | 85 | /** 86 | 设置边框宽度,默认为 1。 87 | */ 88 | @property (nonatomic, assign) IBInspectable CGFloat borderWidth; 89 | 90 | /** 91 | 设置文本字体 92 | */ 93 | @property (nonatomic, strong) IBInspectable UIFont *textFont; 94 | 95 | /** 96 | 设置文本颜色,默认为黑色。 97 | */ 98 | @property (null_resettable, nonatomic, strong) IBInspectable UIColor *textColor; 99 | 100 | @property (null_resettable, nonatomic, strong) IBInspectable UIColor *tintColor; 101 | 102 | /** 103 | 如果需要完成一个 unit 输入后显示地指定已完成的 unit 颜色,可以设置该属性。默认为 nil。 104 | 注意: 105 | 该属性仅在`unitSpace`属性值大于 2 时有效。在连续模式下,不适合颜色跟踪。可以考虑使用`cursorColor`替代 106 | */ 107 | @property (nullable, nonatomic, strong) IBInspectable UIColor *trackTintColor; 108 | 109 | /** 110 | 用于提示输入的焦点所在位置,设置该值后会产生一个光标闪烁动画,如果设置为空,则不生成光标动画。 111 | */ 112 | @property (nullable, nonatomic, strong) IBInspectable UIColor *cursorColor; 113 | 114 | /** 115 | 当输入完成后,是否需要自动取消第一响应者。默认为 NO。 116 | */ 117 | @property (nonatomic, assign) IBInspectable BOOL autoResignFirstResponderWhenInputFinished; 118 | 119 | /** 120 | 每个 unitfield 的大小, 默认为 44x44 121 | */ 122 | @property (nonatomic, assign) IBInspectable CGSize unitSize; 123 | 124 | - (instancetype)initWithInputUnitCount:(NSUInteger)count; 125 | - (instancetype)initWithStyle:(WLUnitFieldStyle)style inputUnitCount:(NSUInteger)count; 126 | 127 | @end 128 | 129 | 130 | 131 | @protocol WLUnitFieldDelegate 132 | 133 | @optional 134 | - (BOOL)unitField:(WLUnitField *)uniField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string; 135 | 136 | @end 137 | 138 | NS_ASSUME_NONNULL_END 139 | -------------------------------------------------------------------------------- /WLUnitField/Classes/WLUnitField.m: -------------------------------------------------------------------------------- 1 | // 2 | // WLUnitField.m 3 | // WLUnitField 4 | // 5 | // Created by wayne on 16/11/22. 6 | // Copyright © 2016年 wayne. All rights reserved. 7 | // 8 | 9 | #import "WLUnitField.h" 10 | #import "WLUnitFieldTextRange.h" 11 | 12 | #ifdef NSFoundationVersionNumber_iOS_9_x_Max 13 | NSNotificationName const WLUnitFieldDidBecomeFirstResponderNotification = @"WLUnitFieldDidBecomeFirstResponderNotification"; 14 | NSNotificationName const WLUnitFieldDidResignFirstResponderNotification = @"WLUnitFieldDidResignFirstResponderNotification"; 15 | #else 16 | NSString *const WLUnitFieldDidBecomeFirstResponderNotification = @"WLUnitFieldDidBecomeFirstResponderNotification"; 17 | NSString *const WLUnitFieldDidResignFirstResponderNotification = @"WLUnitFieldDidResignFirstResponderNotification"; 18 | #endif 19 | 20 | @interface WLUnitField () 21 | 22 | @property (nonatomic, strong) NSMutableArray *characterArray; 23 | @property (nonatomic, strong) CALayer *cursorLayer; 24 | 25 | @end 26 | 27 | @implementation WLUnitField 28 | { 29 | UIColor *mBackgroundColor; 30 | CGContextRef mCtx; 31 | 32 | NSString *mMarkedText; 33 | } 34 | 35 | @dynamic text; 36 | @synthesize textContentType = _textContentType; 37 | @synthesize secureTextEntry = _secureTextEntry; 38 | @synthesize enablesReturnKeyAutomatically = _enablesReturnKeyAutomatically; 39 | @synthesize keyboardType = _keyboardType; 40 | @synthesize returnKeyType = _returnKeyType; 41 | 42 | @synthesize autocapitalizationType = _autocapitalizationType; 43 | @synthesize autocorrectionType = _autocorrectionType; 44 | 45 | @synthesize inputDelegate = _inputDelegate; 46 | @synthesize selectedTextRange = _selectedTextRange; 47 | @synthesize markedTextStyle = _markedTextStyle; 48 | @synthesize tokenizer = _tokenizer; 49 | 50 | #pragma mark - Life 51 | 52 | - (instancetype)initWithInputUnitCount:(NSUInteger)count { 53 | return [self initWithStyle:WLUnitFieldStyleBorder inputUnitCount:count]; 54 | } 55 | 56 | - (instancetype)initWithStyle:(WLUnitFieldStyle)style inputUnitCount:(NSUInteger)count { 57 | if (self = [super initWithFrame:CGRectZero]) { 58 | NSCAssert(count > 0, @"WLUnitField must have one or more input units."); 59 | NSCAssert(count <= 8, @"WLUnitField can not have more than 8 input units."); 60 | 61 | _style = style; 62 | _inputUnitCount = count; 63 | [self initialize]; 64 | } 65 | 66 | return self; 67 | } 68 | 69 | 70 | - (instancetype)initWithFrame:(CGRect)frame { 71 | if (self = [super initWithFrame:frame]) { 72 | _inputUnitCount = 4; 73 | [self initialize]; 74 | } 75 | 76 | return self; 77 | } 78 | 79 | 80 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 81 | if (self = [super initWithCoder:aDecoder]) { 82 | _inputUnitCount = 4; 83 | [self initialize]; 84 | } 85 | 86 | return self; 87 | } 88 | 89 | - (void)initialize { 90 | [self setBackgroundColor:[UIColor clearColor]]; 91 | self.opaque = NO; 92 | _characterArray = [NSMutableArray array]; 93 | _secureTextEntry = NO; 94 | _unitSpace = 12; 95 | _unitSize = CGSizeMake(44, 44); 96 | _borderRadius = 0; 97 | _borderWidth = 1; 98 | _textFont = [UIFont systemFontOfSize:22]; 99 | _keyboardType = UIKeyboardTypeNumberPad; 100 | _returnKeyType = UIReturnKeyDone; 101 | _enablesReturnKeyAutomatically = YES; 102 | _autoResignFirstResponderWhenInputFinished = NO; 103 | _textColor = [UIColor darkGrayColor]; 104 | _tintColor = [UIColor lightGrayColor]; 105 | _trackTintColor = [UIColor orangeColor]; 106 | _cursorColor = [UIColor orangeColor]; 107 | mBackgroundColor = mBackgroundColor ?: [UIColor clearColor]; 108 | _autocorrectionType = UITextAutocorrectionTypeNo; 109 | _autocapitalizationType = UITextAutocapitalizationTypeNone; 110 | self.cursorLayer.backgroundColor = _cursorColor.CGColor; 111 | 112 | WLUnitFieldTextPosition *point = [WLUnitFieldTextPosition positionWithOffset:0]; 113 | UITextRange *aNewRange = [WLUnitFieldTextRange rangeWithStart:point end:point]; 114 | [self setSelectedTextRange:aNewRange]; 115 | 116 | if (@available(iOS 12.0, *)) { 117 | _textContentType = UITextContentTypeOneTimeCode; 118 | } 119 | 120 | [self.layer addSublayer:self.cursorLayer]; 121 | [self setNeedsDisplay]; 122 | } 123 | 124 | 125 | #pragma mark - Property 126 | 127 | - (NSString *)text { 128 | if (_characterArray.count == 0) return nil; 129 | return [_characterArray componentsJoinedByString:@""]; 130 | } 131 | 132 | 133 | - (void)setText:(NSString *)text { 134 | 135 | [_characterArray removeAllObjects]; 136 | [text enumerateSubstringsInRange:NSMakeRange(0, text.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString * _Nullable substring, NSRange substringRange, NSRange enclosingRange, BOOL * _Nonnull stop) { 137 | if (self.characterArray.count < self.inputUnitCount) 138 | [self.characterArray addObject:substring]; 139 | else 140 | *stop = YES; 141 | }]; 142 | 143 | [self setNeedsDisplay]; 144 | [self _resetCursorStateIfNeeded]; 145 | 146 | /** 147 | Supporting iOS12 SMS verification code, setText will be called when verification code input. 148 | */ 149 | if (_characterArray.count >= _inputUnitCount) { 150 | if (_autoResignFirstResponderWhenInputFinished == YES) { 151 | [[NSOperationQueue mainQueue] addOperationWithBlock:^{ 152 | [self resignFirstResponder]; 153 | }]; 154 | } 155 | return; 156 | } 157 | } 158 | 159 | 160 | - (CALayer *)cursorLayer { 161 | if (!_cursorLayer) { 162 | _cursorLayer = [CALayer layer]; 163 | _cursorLayer.hidden = YES; 164 | _cursorLayer.opacity = 1; 165 | 166 | mMarkedText = nil; 167 | 168 | CABasicAnimation *animate = [CABasicAnimation animationWithKeyPath:@"opacity"]; 169 | animate.fromValue = @(0); 170 | animate.toValue = @(1.5); 171 | animate.duration = 0.5; 172 | animate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 173 | animate.autoreverses = YES; 174 | animate.removedOnCompletion = NO; 175 | animate.fillMode = kCAFillModeForwards; 176 | animate.repeatCount = HUGE_VALF; 177 | 178 | 179 | [_cursorLayer addAnimation:animate forKey:nil]; 180 | 181 | [[NSOperationQueue mainQueue] addOperationWithBlock:^{ 182 | [self layoutIfNeeded]; 183 | 184 | self.cursorLayer.position = CGPointMake(CGRectGetWidth(self.bounds) / self.inputUnitCount / 2, CGRectGetHeight(self.bounds) / 2); 185 | }]; 186 | } 187 | 188 | return _cursorLayer; 189 | } 190 | 191 | 192 | - (void)setSecureTextEntry:(BOOL)secureTextEntry { 193 | _secureTextEntry = secureTextEntry; 194 | [self setNeedsDisplay]; 195 | [self _resetCursorStateIfNeeded]; 196 | } 197 | 198 | #if TARGET_INTERFACE_BUILDER 199 | - (void)setInputUnitCount:(NSUInteger)inputUnitCount { 200 | inputUnitCount = MAX(1, MIN(8, inputUnitCount)); 201 | 202 | _inputUnitCount = inputUnitCount; 203 | [self setNeedsDisplay]; 204 | [self _resetCursorStateIfNeeded]; 205 | } 206 | 207 | - (void)setStyle:(NSUInteger)style { 208 | _style = style; 209 | [self setNeedsDisplay]; 210 | [self _resetCursorStateIfNeeded]; 211 | } 212 | 213 | #endif 214 | 215 | 216 | - (void)setUnitSpace:(NSUInteger)unitSpace { 217 | if (unitSpace < 2) unitSpace = 0; 218 | 219 | _unitSpace = unitSpace; 220 | [self _resize]; 221 | [self setNeedsDisplay]; 222 | [self _resetCursorStateIfNeeded]; 223 | } 224 | 225 | 226 | - (void)setTextFont:(UIFont *)textFont { 227 | if (textFont == nil) { 228 | _textFont = [UIFont systemFontOfSize:22]; 229 | } else { 230 | _textFont = textFont; 231 | } 232 | 233 | [self setNeedsDisplay]; 234 | [self _resetCursorStateIfNeeded]; 235 | } 236 | 237 | 238 | - (void)setTextColor:(UIColor *)textColor { 239 | if (textColor == nil) { 240 | _textColor = [UIColor blackColor]; 241 | } else { 242 | _textColor = textColor; 243 | } 244 | 245 | [self setNeedsDisplay]; 246 | [self _resetCursorStateIfNeeded]; 247 | } 248 | 249 | 250 | - (void)setBorderRadius:(CGFloat)borderRadius { 251 | if (borderRadius < 0) return; 252 | 253 | _borderRadius = borderRadius; 254 | [self setNeedsDisplay]; 255 | [self _resetCursorStateIfNeeded]; 256 | } 257 | 258 | 259 | - (void)setBorderWidth:(CGFloat)borderWidth { 260 | if (borderWidth < 0) return; 261 | 262 | _borderWidth = borderWidth; 263 | [self setNeedsDisplay]; 264 | [self _resetCursorStateIfNeeded]; 265 | } 266 | 267 | 268 | - (void)setBackgroundColor:(UIColor *)backgroundColor { 269 | mBackgroundColor = backgroundColor; 270 | [self setNeedsDisplay]; 271 | [self _resetCursorStateIfNeeded]; 272 | } 273 | 274 | 275 | - (void)setTintColor:(UIColor *)tintColor { 276 | _tintColor = tintColor; 277 | [self setNeedsDisplay]; 278 | [self _resetCursorStateIfNeeded]; 279 | } 280 | 281 | - (void)setTrackTintColor:(UIColor *)trackTintColor { 282 | _trackTintColor = trackTintColor; 283 | [self setNeedsDisplay]; 284 | [self _resetCursorStateIfNeeded]; 285 | } 286 | 287 | - (void)setCursorColor:(UIColor *)cursorColor { 288 | _cursorColor = cursorColor; 289 | _cursorLayer.backgroundColor = _cursorColor.CGColor; 290 | [self _resetCursorStateIfNeeded]; 291 | } 292 | 293 | - (void)setUnitSize:(CGSize)unitSize { 294 | _unitSize = unitSize; 295 | [self setNeedsDisplay]; 296 | [self _resetCursorStateIfNeeded]; 297 | } 298 | 299 | #pragma mark- Event 300 | 301 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 302 | [super touchesBegan:touches withEvent:event]; 303 | [self becomeFirstResponder]; 304 | } 305 | 306 | 307 | #pragma mark - Override 308 | 309 | - (CGSize)intrinsicContentSize { 310 | 311 | return CGSizeMake(_inputUnitCount * (_unitSize.width + _unitSpace) - _unitSpace , 312 | _unitSize.height); 313 | } 314 | 315 | 316 | - (CGSize)sizeThatFits:(CGSize)size { 317 | return [self intrinsicContentSize]; 318 | } 319 | 320 | 321 | - (BOOL)canBecomeFirstResponder { 322 | return YES; 323 | } 324 | 325 | 326 | - (BOOL)becomeFirstResponder { 327 | BOOL result = [super becomeFirstResponder]; 328 | [self _resetCursorStateIfNeeded]; 329 | 330 | if (result == YES) { 331 | [self sendActionsForControlEvents:UIControlEventEditingDidBegin]; 332 | [[NSNotificationCenter defaultCenter] postNotificationName:WLUnitFieldDidBecomeFirstResponderNotification object:nil]; 333 | } 334 | 335 | return result; 336 | } 337 | 338 | 339 | - (BOOL)canResignFirstResponder { 340 | return YES; 341 | } 342 | 343 | 344 | - (BOOL)resignFirstResponder { 345 | BOOL result = [super resignFirstResponder]; 346 | [self _resetCursorStateIfNeeded]; 347 | 348 | if (result) { 349 | [self sendActionsForControlEvents:UIControlEventEditingDidEnd]; 350 | [[NSNotificationCenter defaultCenter] postNotificationName:WLUnitFieldDidResignFirstResponderNotification object:nil]; 351 | } 352 | 353 | return result; 354 | } 355 | 356 | 357 | - (void)drawRect:(CGRect)rect { 358 | /* 359 | * 绘制的线条具有宽度,因此在绘制时需要考虑该因素对绘制效果的影响。 360 | */ 361 | CGSize unitSize = CGSizeMake((rect.size.width + _unitSpace) / _inputUnitCount - _unitSpace, rect.size.height); 362 | mCtx = UIGraphicsGetCurrentContext(); 363 | 364 | [self _fillRect:rect unitSize:unitSize]; 365 | [self _drawBorder:rect unitSize:unitSize]; 366 | [self _drawText:rect unitSize:unitSize]; 367 | [self _drawTrackBorder:rect unitSize:unitSize]; 368 | } 369 | 370 | #pragma mark- Private 371 | 372 | /** 373 | 在 AutoLayout 环境下重新指定控件本身的固有尺寸 374 | 375 | `-drawRect:`方法会计算控件完成自身的绘制所需的合适尺寸,完成一次绘制后会通知 AutoLayout 系统更新尺寸。 376 | */ 377 | - (void)_resize { 378 | [self invalidateIntrinsicContentSize]; 379 | } 380 | 381 | 382 | /** 383 | 绘制背景色,以及剪裁绘制区域 384 | 385 | @param rect 控件绘制的区域 386 | */ 387 | - (void)_fillRect:(CGRect)rect unitSize:(CGSize)unitSize { 388 | [mBackgroundColor setFill]; 389 | CGFloat radius = _style == WLUnitFieldStyleBorder ? _borderRadius : 0; 390 | 391 | if (_unitSpace < 2) { 392 | UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius]; 393 | CGContextAddPath(mCtx, bezierPath.CGPath); 394 | } else { 395 | for (int i = 0; i < _inputUnitCount; ++i) { 396 | CGRect unitRect = CGRectMake(i * (unitSize.width + _unitSpace), 397 | 0, 398 | unitSize.width, 399 | unitSize.height); 400 | unitRect = CGRectInset(unitRect, _borderWidth * 0.5, _borderWidth * 0.5); 401 | UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:unitRect cornerRadius:radius]; 402 | CGContextAddPath(mCtx, bezierPath.CGPath); 403 | } 404 | } 405 | 406 | CGContextFillPath(mCtx); 407 | } 408 | 409 | 410 | /** 411 | 绘制边框 412 | 413 | 边框的绘制分为两种模式:连续和不连续。其模式的切换由`unitSpace`属性决定。 414 | 当`unitSpace`值小于 2 时,采用的是连续模式,即每个 input unit 之间没有间隔。 415 | 反之,每个 input unit 会被边框包围。 416 | 417 | @see unitSpace 418 | 419 | @param rect 控件绘制的区域 420 | @param unitSize 单个 input unit 占据的尺寸 421 | */ 422 | - (void)_drawBorder:(CGRect)rect unitSize:(CGSize)unitSize { 423 | 424 | CGRect bounds = CGRectInset(rect, _borderWidth * 0.5, _borderWidth * 0.5); 425 | 426 | if (_style == WLUnitFieldStyleBorder) { 427 | [self.tintColor setStroke]; 428 | CGContextSetLineWidth(mCtx, _borderWidth); 429 | CGContextSetLineCap(mCtx, kCGLineCapRound); 430 | 431 | if (_unitSpace < 2) { 432 | UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:_borderRadius]; 433 | CGContextAddPath(mCtx, bezierPath.CGPath); 434 | 435 | for (int i = 1; i < _inputUnitCount; ++i) { 436 | CGContextMoveToPoint(mCtx, (i * unitSize.width), 0); 437 | CGContextAddLineToPoint(mCtx, (i * unitSize.width), (unitSize.height)); 438 | } 439 | 440 | } else { 441 | for (int i = (int)_characterArray.count; i < _inputUnitCount; i++) { 442 | CGRect unitRect = CGRectMake(i * (unitSize.width + _unitSpace), 443 | 0, 444 | unitSize.width, 445 | unitSize.height); 446 | unitRect = CGRectInset(unitRect, _borderWidth * 0.5, _borderWidth * 0.5); 447 | UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:unitRect cornerRadius:_borderRadius]; 448 | CGContextAddPath(mCtx, bezierPath.CGPath); 449 | } 450 | } 451 | 452 | CGContextDrawPath(mCtx, kCGPathStroke); 453 | } 454 | else { 455 | 456 | [self.tintColor setFill]; 457 | for (int i = (int)_characterArray.count; i < _inputUnitCount; i++) { 458 | CGRect unitLineRect = CGRectMake(i * (unitSize.width + _unitSpace), 459 | unitSize.height - _borderWidth, 460 | unitSize.width, 461 | _borderWidth); 462 | UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:unitLineRect cornerRadius:_borderRadius]; 463 | CGContextAddPath(mCtx, bezierPath.CGPath); 464 | } 465 | 466 | CGContextDrawPath(mCtx, kCGPathFill); 467 | } 468 | } 469 | 470 | 471 | /** 472 | 绘制文本 473 | 474 | 当处于密文输入模式时,会用圆圈替代文本。 475 | 476 | @param rect 控件绘制的区域 477 | @param unitSize 单个 input unit 占据的尺寸 478 | */ 479 | - (void)_drawText:(CGRect)rect unitSize:(CGSize)unitSize { 480 | if ([self hasText] == NO) return; 481 | 482 | NSDictionary *attr = @{NSForegroundColorAttributeName: _textColor, 483 | NSFontAttributeName: _textFont}; 484 | 485 | for (int i = 0; i < _characterArray.count; i++) { 486 | 487 | CGRect unitRect = CGRectMake(i * (unitSize.width + _unitSpace), 488 | 0, 489 | unitSize.width, 490 | unitSize.height); 491 | 492 | CGFloat yOffset = _style == WLUnitFieldStyleBorder ? 0 : _borderWidth; 493 | 494 | if (_secureTextEntry == NO) { 495 | NSString *subString = [_characterArray objectAtIndex:i]; 496 | 497 | CGSize oneTextSize = [subString sizeWithAttributes:attr]; 498 | CGRect drawRect = CGRectInset(unitRect, 499 | (unitRect.size.width - oneTextSize.width) / 2, 500 | (unitRect.size.height - oneTextSize.height) / 2); 501 | drawRect.size.height -= yOffset; 502 | [subString drawInRect:drawRect withAttributes:attr]; 503 | } else { 504 | CGRect drawRect = CGRectInset(unitRect, 505 | (unitRect.size.width - _textFont.pointSize / 2) / 2, 506 | (unitRect.size.height - _textFont.pointSize / 2) / 2); 507 | drawRect.size.height -= yOffset; 508 | [_textColor setFill]; 509 | CGContextAddEllipseInRect(mCtx, drawRect); 510 | CGContextFillPath(mCtx); 511 | } 512 | } 513 | 514 | } 515 | 516 | 517 | /** 518 | 绘制跟踪框,如果指定的`trackTintColor`为 nil 则不绘制 519 | 520 | @param rect 控件绘制的区域 521 | @param unitSize 单个 input unit 占据的尺寸 522 | */ 523 | - (void)_drawTrackBorder:(CGRect)rect unitSize:(CGSize)unitSize { 524 | if (_trackTintColor == nil) return; 525 | 526 | if (_style == WLUnitFieldStyleBorder) { 527 | if (_unitSpace < 2) return; 528 | 529 | [_trackTintColor setStroke]; 530 | CGContextSetLineWidth(mCtx, _borderWidth); 531 | CGContextSetLineCap(mCtx, kCGLineCapRound); 532 | 533 | for (int i = 0; i < _characterArray.count; i++) { 534 | CGRect unitRect = CGRectMake(i * (unitSize.width + _unitSpace), 535 | 0, 536 | unitSize.width, 537 | unitSize.height); 538 | unitRect = CGRectInset(unitRect, _borderWidth * 0.5, _borderWidth * 0.5); 539 | UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:unitRect cornerRadius:_borderRadius]; 540 | CGContextAddPath(mCtx, bezierPath.CGPath); 541 | } 542 | 543 | CGContextDrawPath(mCtx, kCGPathStroke); 544 | } 545 | else { 546 | [_trackTintColor setFill]; 547 | 548 | for (int i = 0; i < _characterArray.count; i++) { 549 | CGRect unitLineRect = CGRectMake(i * (unitSize.width + _unitSpace), 550 | unitSize.height - _borderWidth, 551 | unitSize.width, 552 | _borderWidth); 553 | UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:unitLineRect cornerRadius:_borderRadius]; 554 | CGContextAddPath(mCtx, bezierPath.CGPath); 555 | } 556 | 557 | CGContextDrawPath(mCtx, kCGPathFill); 558 | } 559 | 560 | } 561 | 562 | 563 | - (void)_resetCursorStateIfNeeded { 564 | dispatch_async(dispatch_get_main_queue(), ^{ 565 | self->_cursorLayer.hidden = !self.isFirstResponder || self->_cursorColor == nil || self->_inputUnitCount == self->_characterArray.count; 566 | 567 | if (self->_cursorLayer.hidden) return; 568 | 569 | CGSize unitSize = CGSizeMake((self.bounds.size.width + self->_unitSpace) / self->_inputUnitCount - self->_unitSpace, self.bounds.size.height); 570 | 571 | CGRect unitRect = CGRectMake(self->_characterArray.count * (unitSize.width + self->_unitSpace), 572 | 0, 573 | unitSize.width, 574 | unitSize.height); 575 | unitRect = CGRectInset(unitRect, 576 | unitRect.size.width / 2 - 1, 577 | (unitRect.size.height - self->_textFont.pointSize) / 2); 578 | 579 | CGFloat yOffset = self->_style == WLUnitFieldStyleBorder ? 0 : self->_borderWidth; 580 | unitRect.size.height -= yOffset; 581 | 582 | [CATransaction begin]; 583 | [CATransaction setDisableActions:NO]; 584 | [CATransaction setAnimationDuration:0]; 585 | [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; 586 | self->_cursorLayer.frame = unitRect; 587 | [CATransaction commit]; 588 | }); 589 | } 590 | 591 | 592 | #pragma mark - UIKeyInput 593 | 594 | - (BOOL)hasText { 595 | return _characterArray != nil && _characterArray.count > 0; 596 | } 597 | 598 | - (void)insertText:(NSString *)text { 599 | if ([text isEqualToString:@"\n"]) { 600 | [self resignFirstResponder]; 601 | return; 602 | } 603 | 604 | if ([text isEqualToString:@" "]) { 605 | return; 606 | } 607 | 608 | if (_characterArray.count >= _inputUnitCount) { 609 | if (_autoResignFirstResponderWhenInputFinished == YES) { 610 | [self resignFirstResponder]; 611 | } 612 | return; 613 | } 614 | 615 | if ([self.delegate respondsToSelector:@selector(unitField:shouldChangeCharactersInRange:replacementString:)]) { 616 | if ([self.delegate unitField:self shouldChangeCharactersInRange:NSMakeRange(self.text.length, text.length) replacementString:text] == NO) { 617 | return; 618 | } 619 | } 620 | 621 | [_inputDelegate textWillChange:self]; 622 | NSRange range; 623 | for (int i = 0; i < text.length; i += range.length) { 624 | range = [text rangeOfComposedCharacterSequenceAtIndex:i]; 625 | [_characterArray addObject:[text substringWithRange:range]]; 626 | } 627 | 628 | if (_characterArray.count >= _inputUnitCount) { 629 | [_characterArray removeObjectsInRange:NSMakeRange(_inputUnitCount, _characterArray.count - _inputUnitCount)]; 630 | if (_autoResignFirstResponderWhenInputFinished == YES) { 631 | [[NSOperationQueue mainQueue] addOperationWithBlock:^{ 632 | [self resignFirstResponder]; 633 | }]; 634 | } 635 | } 636 | 637 | [self sendActionsForControlEvents:UIControlEventEditingChanged]; 638 | 639 | [self setNeedsDisplay]; 640 | [self _resetCursorStateIfNeeded]; 641 | [_inputDelegate textDidChange:self]; 642 | } 643 | 644 | 645 | - (void)deleteBackward { 646 | if ([self hasText] == NO) 647 | return; 648 | 649 | [_inputDelegate textWillChange:self]; 650 | [_characterArray removeLastObject]; 651 | [self sendActionsForControlEvents:UIControlEventEditingChanged]; 652 | 653 | [self setNeedsDisplay]; 654 | [self _resetCursorStateIfNeeded]; 655 | [_inputDelegate textDidChange:self]; 656 | } 657 | 658 | 659 | /** 660 | Supporting iOS12 SMS verification code, keyboardType must be UIKeyboardTypeNumberPad to localizable. 661 | 662 | Must set textContentType to UITextContentTypeOneTimeCode 663 | */ 664 | 665 | 666 | // UITextInput implement. 667 | #pragma mark - UITextInput 668 | 669 | /* Methods for manipulating text. */ 670 | - (nullable NSString *)textInRange:(WLUnitFieldTextRange *)range { 671 | return self.text; 672 | } 673 | 674 | - (void)replaceRange:(WLUnitFieldTextRange *)range withText:(NSString *)text { 675 | } 676 | 677 | 678 | // selectedRange is a range within the markedText 679 | - (void)setMarkedText:(nullable NSString *)markedText selectedRange:(NSRange)selectedRange { 680 | mMarkedText = markedText; 681 | } 682 | 683 | - (void)unmarkText { 684 | if (self.text.length >= self.inputUnitCount) { 685 | mMarkedText = nil; 686 | return; 687 | } 688 | 689 | [self insertText:mMarkedText]; 690 | mMarkedText = nil; 691 | } 692 | 693 | 694 | /* The end and beginning of the the text document. */ 695 | - (UITextPosition *)beginningOfDocument { 696 | return [WLUnitFieldTextPosition positionWithOffset:0]; 697 | } 698 | 699 | - (UITextPosition *)endOfDocument { 700 | if (self.text.length == 0) { 701 | return [WLUnitFieldTextPosition positionWithOffset:0]; 702 | } 703 | return [WLUnitFieldTextPosition positionWithOffset:self.text.length - 1]; 704 | } 705 | 706 | 707 | /* A tokenizer must be provided to inform the text input system about text units of varying granularity. */ 708 | - (id)tokenizer { 709 | if (!_tokenizer) { 710 | _tokenizer = [[UITextInputStringTokenizer alloc] initWithTextInput:self]; 711 | } 712 | return _tokenizer; 713 | } 714 | 715 | 716 | // Nil if no marked text. 717 | - (UITextRange *)markedTextRange { 718 | return nil; 719 | } 720 | 721 | 722 | /* Methods for creating ranges and positions. */ 723 | - (nullable UITextRange *)textRangeFromPosition:(WLUnitFieldTextPosition *)fromPosition toPosition:(WLUnitFieldTextPosition *)toPosition { 724 | NSRange range = NSMakeRange(MIN(fromPosition.offset, toPosition.offset), ABS(toPosition.offset - fromPosition.offset)); 725 | return [WLUnitFieldTextRange rangeWithRange:range]; 726 | } 727 | 728 | - (nullable UITextPosition *)positionFromPosition:(WLUnitFieldTextPosition *)position offset:(NSInteger)offset { 729 | NSInteger end = position.offset + offset; 730 | if (end > self.text.length || end < 0) 731 | return nil; 732 | return [WLUnitFieldTextPosition positionWithOffset:end]; 733 | } 734 | 735 | - (nullable UITextPosition *)positionFromPosition:(WLUnitFieldTextPosition *)position inDirection:(UITextLayoutDirection)direction offset:(NSInteger)offset { 736 | return [WLUnitFieldTextPosition positionWithOffset:position.offset + offset]; 737 | } 738 | 739 | 740 | /* Simple evaluation of positions */ 741 | - (NSComparisonResult)comparePosition:(WLUnitFieldTextPosition *)position toPosition:(WLUnitFieldTextPosition *)other { 742 | if (position.offset < other.offset) return NSOrderedAscending; 743 | if (position.offset > other.offset) return NSOrderedDescending; 744 | return NSOrderedSame; 745 | } 746 | 747 | - (NSInteger)offsetFromPosition:(WLUnitFieldTextPosition *)from toPosition:(WLUnitFieldTextPosition *)toPosition { 748 | return toPosition.offset - from.offset ; 749 | } 750 | 751 | 752 | /* Layout questions. */ 753 | - (nullable UITextPosition *)positionWithinRange:(UITextRange *)range farthestInDirection:(UITextLayoutDirection)direction { return nil; } 754 | - (nullable UITextRange *)characterRangeByExtendingPosition:(WLUnitFieldTextPosition *)position inDirection:(UITextLayoutDirection)direction { return nil; } 755 | 756 | 757 | /* Writing direction */ 758 | - (UITextWritingDirection)baseWritingDirectionForPosition:(WLUnitFieldTextPosition *)position inDirection:(UITextStorageDirection)direction { return UITextWritingDirectionNatural; } 759 | - (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection forRange:(UITextRange *)range {} 760 | 761 | 762 | /* Geometry used to provide, for example, a correction rect. */ 763 | - (NSArray *)selectionRectsForRange:(WLUnitFieldTextRange *)range { return nil; } 764 | - (CGRect)firstRectForRange:(WLUnitFieldTextRange *)range { return CGRectNull; } 765 | - (CGRect)caretRectForPosition:(WLUnitFieldTextPosition *)position { return CGRectNull; } 766 | 767 | 768 | /* Hit testing. */ 769 | - (nullable UITextRange *)characterRangeAtPoint:(CGPoint)point { return nil; } 770 | - (nullable UITextPosition *)closestPositionToPoint:(CGPoint)point withinRange:(WLUnitFieldTextRange *)range { return nil; } 771 | - (nullable UITextPosition *)closestPositionToPoint:(CGPoint)point { return nil; } 772 | 773 | @end 774 | -------------------------------------------------------------------------------- /WLUnitField/Classes/WLUnitFieldTextPosition.h: -------------------------------------------------------------------------------- 1 | // 2 | // WLUnitFieldTextPosition.h 3 | // WLUnitField 4 | // 5 | // Created by 张尉 on 2019/4/3. 6 | // Copyright © 2019 wayne. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface WLUnitFieldTextPosition : UITextPosition 14 | 15 | @property (nonatomic, readonly) NSInteger offset; 16 | 17 | + (instancetype)positionWithOffset:(NSInteger)offset; 18 | 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /WLUnitField/Classes/WLUnitFieldTextPosition.m: -------------------------------------------------------------------------------- 1 | // 2 | // WLUnitFieldTextPosition.m 3 | // WLUnitField 4 | // 5 | // Created by 张尉 on 2019/4/3. 6 | // Copyright © 2019 wayne. All rights reserved. 7 | // 8 | 9 | #import "WLUnitFieldTextPosition.h" 10 | 11 | @implementation WLUnitFieldTextPosition 12 | 13 | + (instancetype)positionWithOffset:(NSInteger)offset { 14 | WLUnitFieldTextPosition *position = [[self alloc] init]; 15 | position->_offset = offset; 16 | return position; 17 | } 18 | 19 | - (instancetype)copyWithZone:(NSZone *)zone { 20 | return [WLUnitFieldTextPosition positionWithOffset:self.offset]; 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /WLUnitField/Classes/WLUnitFieldTextRange.h: -------------------------------------------------------------------------------- 1 | // 2 | // WLUnitFieldTextRange.h 3 | // WLUnitField 4 | // 5 | // Created by 张尉 on 2019/4/3. 6 | // Copyright © 2019 wayne. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "WLUnitFieldTextPosition.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface WLUnitFieldTextRange : UITextRange 15 | 16 | @property (nonatomic, readonly) WLUnitFieldTextPosition *start; 17 | @property (nonatomic, readonly) WLUnitFieldTextPosition *end; 18 | 19 | @property (nonatomic, readonly) NSRange range; 20 | 21 | + (nullable instancetype)rangeWithStart:(WLUnitFieldTextPosition *)start end:(WLUnitFieldTextPosition *)end; 22 | 23 | + (nullable instancetype)rangeWithRange:(NSRange)range; 24 | 25 | @end 26 | 27 | NS_ASSUME_NONNULL_END 28 | -------------------------------------------------------------------------------- /WLUnitField/Classes/WLUnitFieldTextRange.m: -------------------------------------------------------------------------------- 1 | // 2 | // WLUnitFieldTextRange.m 3 | // WLUnitField 4 | // 5 | // Created by 张尉 on 2019/4/3. 6 | // Copyright © 2019 wayne. All rights reserved. 7 | // 8 | 9 | #import "WLUnitFieldTextRange.h" 10 | 11 | @implementation WLUnitFieldTextRange 12 | @dynamic range; 13 | @synthesize start = _start, end = _end; 14 | 15 | 16 | + (instancetype)rangeWithRange:(NSRange)range { 17 | if (range.location == NSNotFound) 18 | return nil; 19 | 20 | WLUnitFieldTextPosition *start = [WLUnitFieldTextPosition positionWithOffset:range.location]; 21 | WLUnitFieldTextPosition *end = [WLUnitFieldTextPosition positionWithOffset:range.location + range.length]; 22 | return [self rangeWithStart:start end:end]; 23 | } 24 | 25 | + (instancetype)rangeWithStart:(WLUnitFieldTextPosition *)start end:(WLUnitFieldTextPosition *)end { 26 | if (!start || !end) return nil; 27 | assert(start.offset <= end.offset); 28 | WLUnitFieldTextRange *range = [[self alloc] init]; 29 | range->_start = start; 30 | range->_end = end; 31 | return range; 32 | } 33 | 34 | - (instancetype)copyWithZone:(NSZone *)zone { 35 | return [WLUnitFieldTextRange rangeWithStart:_start end:_end]; 36 | } 37 | 38 | - (NSRange)range { 39 | return NSMakeRange(_start.offset, _end.offset - _start.offset); 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /WLUnitField/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.1 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 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /WLUnitField/RandomColor/YGColorDefinition.h: -------------------------------------------------------------------------------- 1 | // 2 | // YGColorDefinition.h 3 | // randomColor 4 | // 5 | // Created by Yannick Heinrich on 21/05/15. 6 | // Copyright (c) 2015 yageek. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | typedef NS_ENUM(NSUInteger, YGColorLuminosity) { 12 | YGColorLuminosityDark, 13 | YGColorLuminosityBright, 14 | YGColorLuminosityLight, 15 | YGColorLuminosityRandom 16 | }; 17 | 18 | typedef NS_ENUM(NSUInteger, YGColorHue) { 19 | YGColorHueRed, 20 | YGColorHueOrange, 21 | YGColorHueYellow, 22 | YGColorHueGreen, 23 | YGColorHueBlue, 24 | YGColorHuePurple, 25 | YGColorHuePink, 26 | YGColorHueMonochrome, 27 | YGColorHueRandom 28 | }; 29 | 30 | #pragma mark - YGColorRange 31 | 32 | @interface YGColorRange : NSObject 33 | 34 | + (instancetype) newWithMin:(CGFloat) min max:(CGFloat)max; 35 | - (instancetype) initWithMin:(CGFloat) min max:(CGFloat)max; 36 | 37 | @property(nonatomic)CGFloat min; 38 | @property(nonatomic)CGFloat max; 39 | 40 | @end 41 | 42 | 43 | #pragma mark - YGColorDefinition 44 | 45 | @interface YGColorDefinition : NSObject 46 | 47 | - (id) initWithHueRange:(YGColorRange*) hueRange lowerBounds:(NSArray*) lowerBounds; 48 | 49 | 50 | @property(nonatomic, strong) YGColorRange* hueRange; 51 | @property(nonatomic, strong) YGColorRange* saturationRange; 52 | @property(nonatomic, strong) YGColorRange* brightnessRange; 53 | @property(nonatomic, copy) NSArray* lowerBounds; 54 | 55 | + (CGFloat) pickHueWithEnum:(YGColorHue) hue; 56 | + (CGFloat) pickSaturationWithHueValue:(CGFloat) hue luminosity:(YGColorLuminosity) luminosity monochrome:(BOOL) isMonochrome; 57 | + (CGFloat) pickBrightnessWitHueValue:(CGFloat) hue saturationValue:(CGFloat) saturation luminosity:(YGColorLuminosity) luminosity; 58 | @end 59 | -------------------------------------------------------------------------------- /WLUnitField/RandomColor/YGColorDefinition.m: -------------------------------------------------------------------------------- 1 | // 2 | // YGColorDefinition.m 3 | // randomColor 4 | // 5 | // Created by Yannick Heinrich on 21/05/15. 6 | // Copyright (c) 2015 yageek. All rights reserved. 7 | // 8 | 9 | #import "YGColorDefinition.h" 10 | 11 | #define YGRange(MinVal, MaxVal) [YGColorRange newWithMin:MinVal max:MaxVal] 12 | 13 | @implementation YGColorRange 14 | 15 | - (instancetype) initWithMin:(CGFloat) min max:(CGFloat)max 16 | { 17 | if(self = [super init]) 18 | { 19 | _max = max; 20 | _min = min; 21 | } 22 | return self; 23 | } 24 | 25 | + (instancetype) newWithMin:(CGFloat) min max:(CGFloat)max 26 | { 27 | return [[[self class] alloc] initWithMin:min max:max]; 28 | } 29 | 30 | - (NSString*) description 31 | { 32 | return [NSString stringWithFormat:@"<%p:%@> min:%f max:%f", self, NSStringFromClass([self class]), self.min, self.max]; 33 | } 34 | @end 35 | 36 | 37 | @implementation YGColorDefinition 38 | 39 | - (id) initWithHueRange:(YGColorRange*) hueRange lowerBounds:(NSArray*) lowerBounds 40 | { 41 | if(self = [super init]) 42 | { 43 | 44 | YGColorRange * sMin = [lowerBounds firstObject]; 45 | YGColorRange * sMax = [lowerBounds lastObject]; 46 | 47 | _hueRange = hueRange; 48 | _lowerBounds = [lowerBounds copy]; 49 | _saturationRange = [YGColorRange newWithMin:sMin.min max:sMax.min]; 50 | _brightnessRange = [YGColorRange newWithMin:sMax.max max:sMin.max]; 51 | 52 | } 53 | return self; 54 | } 55 | 56 | + (NSMutableDictionary*) sharedColors 57 | { 58 | static NSMutableDictionary * dict = nil; 59 | static dispatch_once_t once; 60 | dispatch_once(&once, ^{ 61 | dict = [[NSMutableDictionary alloc] init]; 62 | }); 63 | return dict; 64 | } 65 | 66 | + (void) load 67 | { 68 | 69 | 70 | 71 | //Populate the dictionary 72 | [self loadColorBounds]; 73 | } 74 | 75 | + (void) defineColorWithName:(NSString*) name hueRange:(YGColorRange*) hueRange lowerBounds:(NSArray*) lowerBounds 76 | { 77 | YGColorDefinition *definition = [[YGColorDefinition alloc] initWithHueRange:hueRange lowerBounds:lowerBounds]; 78 | 79 | NSMutableDictionary* colorDict = [self sharedColors]; 80 | colorDict[name] = definition; 81 | } 82 | 83 | 84 | + (void) loadColorBounds 85 | { 86 | [self defineColorWithName:@"monochrome" 87 | hueRange:nil 88 | lowerBounds:@[YGRange(0,0),YGRange(100,0)]]; 89 | 90 | [self defineColorWithName:@"red" 91 | hueRange:YGRange(-26,18) 92 | lowerBounds:@[YGRange(20,100),YGRange(30,92),YGRange(40,89),YGRange(50,85),YGRange(60,78),YGRange(70,70),YGRange(80,60),YGRange(90,55),YGRange(100,50)]]; 93 | 94 | 95 | [self defineColorWithName:@"orange" 96 | hueRange:YGRange(19,46) 97 | lowerBounds:@[YGRange(20,100),YGRange(30,93),YGRange(40,88),YGRange(50,86),YGRange(60,85),YGRange(70,70),YGRange(100,70)]]; 98 | 99 | 100 | [self defineColorWithName:@"yellow" 101 | hueRange:YGRange(47, 62) 102 | lowerBounds:@[YGRange(25,100),YGRange(40,94),YGRange(50,89),YGRange(60,86),YGRange(70,84),YGRange(80,82),YGRange(90,80),YGRange(100,75)]]; 103 | 104 | [self defineColorWithName:@"green" 105 | hueRange:YGRange(63, 178) 106 | lowerBounds:@[YGRange(30,100),YGRange(40,90),YGRange(50,85),YGRange(60,81),YGRange(70,74),YGRange(80,64),YGRange(90,50),YGRange(100,40)]]; 107 | 108 | [self defineColorWithName:@"blue" 109 | hueRange:YGRange(179, 257) 110 | lowerBounds:@[YGRange(20,100),YGRange(30,86),YGRange(40,80),YGRange(50,74),YGRange(60,60),YGRange(70,52),YGRange(80,44),YGRange(90,39), YGRange(100, 35)]]; 111 | 112 | [self defineColorWithName:@"purple" 113 | hueRange:YGRange(258, 282) 114 | lowerBounds:@[YGRange(20,100),YGRange(30,87),YGRange(40,79),YGRange(50,70),YGRange(60,65),YGRange(70,59),YGRange(80,52),YGRange(90,45), YGRange(100, 42)]]; 115 | 116 | [self defineColorWithName:@"pink" 117 | hueRange:YGRange(283, 334) 118 | lowerBounds:@[YGRange(20,100),YGRange(30,90),YGRange(40,86),YGRange(60,84),YGRange(80,80),YGRange(90,75),YGRange(100,73)]]; 119 | 120 | } 121 | 122 | + (YGColorRange*) hueRange:(id) colorInput 123 | { 124 | if([colorInput isKindOfClass:[NSNumber class]]) 125 | { 126 | CGFloat number = [colorInput floatValue]; 127 | return (number < 360 && number > 0) ? YGRange(number, number) : YGRange(0, 360); 128 | } 129 | else if([colorInput isKindOfClass:[NSString class]]) 130 | { 131 | YGColorDefinition * color = [self sharedColors][colorInput]; 132 | return color.hueRange ?: YGRange(0, 360); 133 | } 134 | 135 | 136 | return YGRange(0, 360); 137 | 138 | } 139 | 140 | + (CGFloat) pickHueWithEnum:(YGColorHue) hue 141 | { 142 | return [self pickHue:[self colorNameFromEnum:hue]]; 143 | } 144 | + (CGFloat) pickHue:(id) colorInput 145 | { 146 | YGColorRange * range = [[self class] hueRange:colorInput]; 147 | CGFloat hue = [self randomWithin:range]; 148 | if (hue < 0) {hue = 360 + hue;} 149 | return hue; 150 | } 151 | 152 | #pragma mark - Random 153 | 154 | + (CGFloat) randomWithin:(YGColorRange*) range 155 | { 156 | return arc4random_uniform(range.max - range.min) + range.min; 157 | } 158 | 159 | 160 | + (YGColorRange*) saturationRange:(CGFloat) hue 161 | { 162 | return [[self colorInfo:hue] saturationRange]; 163 | } 164 | 165 | + (CGFloat) pickSaturationWithHueValue:(CGFloat) hue luminosity:(YGColorLuminosity) luminosity monochrome:(BOOL) isMonochrome 166 | { 167 | 168 | if(isMonochrome) 169 | return 0; 170 | 171 | YGColorRange *saturationRange = [self saturationRange:hue]; 172 | 173 | CGFloat sMin = saturationRange.min; 174 | CGFloat sMax = saturationRange.max; 175 | 176 | switch (luminosity) { 177 | case YGColorLuminosityBright: 178 | sMin = 55; 179 | break; 180 | case YGColorLuminosityDark: 181 | sMin = sMax -10; 182 | break; 183 | case YGColorLuminosityLight: 184 | sMax = 55; 185 | break; 186 | case YGColorLuminosityRandom: 187 | return [self randomWithin:YGRange(0, 100)]; 188 | break; 189 | } 190 | 191 | return [self randomWithin:YGRange(sMin, sMax)]; 192 | } 193 | 194 | + (CGFloat) minimumBrightnessWithHue:(CGFloat) hue saturation:(CGFloat) saturation 195 | { 196 | NSArray * lowerBounds = [self colorInfo:hue].lowerBounds; 197 | 198 | for(NSUInteger idx = 0; idx < lowerBounds.count -1 ; ++idx) 199 | { 200 | YGColorRange * range = lowerBounds[idx]; 201 | CGFloat s1 = range.min; 202 | CGFloat v1 = range.max; 203 | 204 | CGFloat s2 = [lowerBounds[idx+1] min]; 205 | CGFloat v2 = [lowerBounds[idx+1] max]; 206 | 207 | if(saturation >= s1 && saturation <= s2) 208 | { 209 | CGFloat m = (v2 - v1)/(s2 - s1); 210 | CGFloat b = v1 - m*s1; 211 | return (m*saturation + b); 212 | } 213 | 214 | } 215 | return 0; 216 | } 217 | 218 | 219 | + (CGFloat) pickBrightnessWitHueValue:(CGFloat) hue saturationValue:(CGFloat) saturation luminosity:(YGColorLuminosity) luminosity 220 | { 221 | CGFloat bMin = [self minimumBrightnessWithHue:hue saturation:saturation]; 222 | CGFloat bMax = 100; 223 | 224 | switch (luminosity) { 225 | case YGColorLuminosityDark: 226 | bMax = bMin + 20; 227 | break; 228 | case YGColorLuminosityLight: 229 | bMin = (bMax + bMin)/2.0f; 230 | break; 231 | case YGColorLuminosityRandom: 232 | bMin = 0; 233 | bMax = 100; 234 | break; 235 | default: 236 | break; 237 | } 238 | return [self randomWithin:YGRange(bMin, bMax)]; 239 | } 240 | 241 | 242 | + (YGColorDefinition*) colorInfo:(CGFloat) hue 243 | { 244 | if (hue >= 334 && hue <= 360) { 245 | hue-= 360; 246 | } 247 | 248 | for(YGColorDefinition* color in [[self sharedColors] allValues]) 249 | { 250 | if(color.hueRange && 251 | hue >= color.hueRange.min && 252 | hue <= color.hueRange.max) 253 | return color; 254 | } 255 | 256 | return nil; 257 | } 258 | 259 | + (NSString*) colorNameFromEnum:(YGColorHue) hueEnum 260 | { 261 | switch (hueEnum) 262 | { 263 | case YGColorHueRed : return @"red"; 264 | case YGColorHueOrange: return @"orange"; 265 | case YGColorHueYellow: return @"yellow"; 266 | case YGColorHueGreen: return @"green"; 267 | case YGColorHueBlue: return @"blue"; 268 | case YGColorHuePurple: return @"purple"; 269 | case YGColorHuePink: return @"pink"; 270 | case YGColorHueMonochrome: return @"monochrome"; 271 | case YGColorHueRandom: return nil; 272 | 273 | } 274 | } 275 | 276 | @end 277 | -------------------------------------------------------------------------------- /WLUnitField/RandomColor/iOS/UIColor+randomColor.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+randomColor.h 3 | // randomColor 4 | // 5 | // Created by Yannick Heinrich on 20/05/15. 6 | // Copyright (c) 2015 yageek. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "YGColorDefinition.h" 11 | @interface UIColor (randomColor) 12 | /** 13 | * Generate an array of colors with specified hue and luminosity value 14 | * 15 | * @param colorHue The wanted hue color 16 | * @param luminosity The wanted luminosity 17 | * @param count The number of colors to generate 18 | * 19 | * @return An array of `UIColor` instances 20 | */ 21 | + (NSArray*) randomColorsWithHue:(YGColorHue) colorHue luminosity:(YGColorLuminosity) luminosity count:(NSUInteger) count; 22 | /** 23 | * Generate a color with specified hue and luminosity value 24 | * 25 | * @param colorHue The wanted hue color 26 | * @param luminosity The wanted luminosity 27 | * 28 | * @return An instance of `UIColor` 29 | */ 30 | + (UIColor*) randomColorWithHue:(YGColorHue) colorHue luminosity:(YGColorLuminosity) luminosity; 31 | 32 | /** 33 | * Generate a random color. Same as invoking `randomColorWithHue:luminosity:` with `YGColorHueRandom` and `YGColorLuminosityRandom` 34 | * 35 | * @return A `UIColor` instance 36 | */ 37 | + (UIColor*) randomColor; 38 | /** 39 | * Generate an array of random colors. 40 | * Same as invoking `randomColorsWithHue:luminosity:count:` with `YGColorHueRandom` and `YGColorLuminosityRandom` 41 | * 42 | * @param count The number of colors to generate 43 | * 44 | * @return An array of `UIColor` instances 45 | */ 46 | + (NSArray*) randomColorsWithCount:(NSUInteger) count; 47 | 48 | /** 49 | * Generate a randomcolor with a specified hue value. 50 | * Same as invoking `randomColorWithHue:luminosity:` with a luminosity equals to `YGColorLuminosityRandom` 51 | * 52 | * @param colorHue The wanted hue color 53 | * 54 | * @return A `UIColor` instance 55 | */ 56 | + (UIColor*) randomColorWithHue:(YGColorHue) colorHue; 57 | /** 58 | * Generate an array of random colors with a specified hue value. Same as invoking 59 | * `randomColorsWithHue:luminosity:count:` with with a luminosity equals to `YGColorLuminosityRandom` 60 | * 61 | * @param colorHue The wanted hue value 62 | * @param count The number of colors to generate 63 | * 64 | * @return An array of `UIColor` instances 65 | */ 66 | + (NSArray*) randomColorsWithHue:(YGColorHue) colorHue count:(NSUInteger) count; 67 | 68 | /** 69 | * Generate a randomcolor with a specified luminosity value. 70 | * Same as invoking `randomColorWithHue:luminosity:` with a hue equals to `YGColorHueRandom` 71 | * @param luminosity The wanted luminosity value 72 | * 73 | * @return A `UIColor` instance 74 | */ 75 | + (UIColor*) randomColorWithLuminosity:(YGColorLuminosity) luminosity; 76 | /** 77 | * Generate an array of random colors with a specified luminosity value. Same as invoking 78 | * `randomColorsWithHue:luminosity:count:` with with a hueColor equals to `YGColorHueRandom` 79 | * 80 | * @param luminosity The wanted luminosity 81 | * @param count The number of color to generate 82 | * 83 | * @return An array of `UIColor` instances 84 | */ 85 | + (NSArray*) randomColorsWithLuminosity:(YGColorLuminosity) luminosity count:(NSUInteger) count; 86 | @end 87 | -------------------------------------------------------------------------------- /WLUnitField/RandomColor/iOS/UIColor+randomColor.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+randomColor.m 3 | // randomColor 4 | // 5 | // Created by Yannick Heinrich on 20/05/15. 6 | // Copyright (c) 2015 yageek. All rights reserved. 7 | // 8 | #import 9 | #import "UIColor+randomColor.h" 10 | 11 | 12 | #pragma mark - UIColor random color extension 13 | 14 | @implementation UIColor (randomColor) 15 | 16 | #pragma mark - Public Methods 17 | + (NSArray*) randomColorsWithHue:(YGColorHue) colorHue luminosity:(YGColorLuminosity) luminosity count:(NSUInteger) count 18 | { 19 | NSMutableArray * colors = [NSMutableArray arrayWithCapacity:count]; 20 | 21 | for(NSUInteger i = 0; i < count ; ++i) 22 | { 23 | [colors addObject:[self randomColorWithHue:colorHue luminosity:luminosity]]; 24 | } 25 | return colors; 26 | } 27 | 28 | + (NSArray*) randomColorsWithCount:(NSUInteger) count 29 | { 30 | return [self randomColorsWithHue:YGColorHueRandom luminosity:YGColorLuminosityRandom count:count]; 31 | } 32 | 33 | + (NSArray*) randomColorsWithHue:(YGColorHue) colorHue count:(NSUInteger) count 34 | { 35 | return [self randomColorsWithHue:colorHue luminosity:YGColorLuminosityRandom count:count]; 36 | } 37 | 38 | + (NSArray*) randomColorsWithLuminosity:(YGColorLuminosity) luminosity count:(NSUInteger) count 39 | { 40 | return [self randomColorsWithHue:YGColorHueRandom luminosity:luminosity count:count]; 41 | } 42 | 43 | + (UIColor*) randomColorWithHue:(YGColorHue) colorHue luminosity:(YGColorLuminosity) luminosity 44 | { 45 | CGFloat H = [YGColorDefinition pickHueWithEnum:colorHue]; 46 | CGFloat S = [YGColorDefinition pickSaturationWithHueValue:H luminosity:luminosity monochrome:(colorHue==YGColorHueMonochrome)]; 47 | CGFloat B = [YGColorDefinition pickBrightnessWitHueValue:H saturationValue:S luminosity:luminosity]; 48 | 49 | 50 | return [UIColor colorWithHue:(H/360.0f) saturation:(S/100.0f) brightness:(B/100.0f) alpha:1.0f]; 51 | } 52 | 53 | + (UIColor*) randomColor 54 | { 55 | return [self randomColorWithHue:YGColorHueRandom luminosity:YGColorLuminosityRandom]; 56 | } 57 | + (UIColor*) randomColorWithHue:(YGColorHue) colorHue 58 | { 59 | return [self randomColorWithHue:colorHue luminosity:YGColorLuminosityRandom]; 60 | } 61 | 62 | 63 | 64 | + (UIColor*) randomColorWithLuminosity:(YGColorLuminosity) luminosity 65 | { 66 | return [self randomColorWithHue:YGColorHueRandom luminosity:luminosity]; 67 | } 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /WLUnitField/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // WLUnitField 4 | // 5 | // Created by wayne on 16/11/22. 6 | // Copyright © 2016年 wayne. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /WLUnitField/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // WLUnitField 4 | // 5 | // Created by wayne on 16/11/22. 6 | // Copyright © 2016年 wayne. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "WLUnitField.h" 11 | #import "UIColor+randomColor.h" 12 | 13 | @interface ViewController () 14 | 15 | @property (strong, nonatomic) IBOutlet WLUnitField *unitField; 16 | 17 | @end 18 | 19 | @implementation ViewController 20 | 21 | - (void)viewDidLoad { 22 | [super viewDidLoad]; 23 | // Do any additional setup after loading the view, typically from a nib. 24 | _unitField.delegate = self; 25 | _unitField.keyboardType = UIKeyboardTypeDefault; 26 | // _unitField.text = @"一😀12"; 27 | } 28 | 29 | - (BOOL)unitField:(WLUnitField *)uniField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { 30 | NSString *text = nil; 31 | if (range.location >= uniField.text.length) { 32 | text = [uniField.text stringByAppendingString:string]; 33 | } else { 34 | text = [uniField.text stringByReplacingCharactersInRange:range withString:string]; 35 | } 36 | NSLog(@"******>%@", text); 37 | 38 | return YES; 39 | } 40 | 41 | - (IBAction)unitFieldEditingChanged:(WLUnitField *)sender { 42 | NSLog(@"%s %@", __FUNCTION__, sender.text); 43 | } 44 | 45 | - (IBAction)unitFieldEditingDidBegin:(id)sender { 46 | NSLog(@"%s", __FUNCTION__); 47 | } 48 | 49 | - (IBAction)unitFieldEditingDidEnd:(id)sender { 50 | NSLog(@"%s", __FUNCTION__); 51 | } 52 | 53 | 54 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 55 | [self.view.window endEditing:YES]; 56 | } 57 | 58 | - (IBAction)switchValueChanged:(UISwitch *)sender { 59 | if (sender.tag == 1) { 60 | _unitField.secureTextEntry = sender.isOn; 61 | } else if (sender.tag == 2) { 62 | _unitField.autoResignFirstResponderWhenInputFinished = sender.isOn; 63 | } 64 | } 65 | 66 | - (IBAction)stepperValueChanged:(UIStepper *)sender { 67 | if (sender.tag == 4) { 68 | UILabel *lab = [self.view viewWithTag:3]; 69 | lab.text = [NSString stringWithFormat:@"%@", @(sender.value)]; 70 | _unitField.unitSpace = sender.value; 71 | } else if (sender.tag == 6) { 72 | UILabel *lab = [self.view viewWithTag:5]; 73 | lab.text = [NSString stringWithFormat:@"%@", @(sender.value)]; 74 | _unitField.borderRadius = sender.value; 75 | } else if (sender.tag == 8) { 76 | UILabel *lab = [self.view viewWithTag:7]; 77 | lab.text = [NSString stringWithFormat:@"%@", @(sender.value)]; 78 | _unitField.borderWidth = sender.value; 79 | } 80 | 81 | } 82 | 83 | - (IBAction)buttonTouchUpInside:(UIButton *)sender { 84 | UIColor *color = [UIColor randomColor]; 85 | [sender setBackgroundColor:color]; 86 | 87 | if (sender.tag == 9) { 88 | _unitField.textColor = color; 89 | } else if (sender.tag == 10) { 90 | _unitField.tintColor = color; 91 | } else if (sender.tag == 11) { 92 | _unitField.trackTintColor = color; 93 | } else if (sender.tag == 12) { 94 | _unitField.cursorColor = color; 95 | } 96 | } 97 | 98 | @end 99 | -------------------------------------------------------------------------------- /WLUnitField/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // WLUnitField 4 | // 5 | // Created by wayne on 16/11/22. 6 | // Copyright © 2016年 wayne. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhwayne/WLUnitField/8e545525e3546cd7d097e2df85f17b7e432fca93/demo.gif -------------------------------------------------------------------------------- /line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhwayne/WLUnitField/8e545525e3546cd7d097e2df85f17b7e432fca93/line.png --------------------------------------------------------------------------------