├── .gitignore ├── CodeTextDemo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── houwan.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── houwan.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── CodeTextDemo ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── CodeTextView │ ├── HWTFCodeBView.h │ ├── HWTFCodeBView.m │ ├── HWTFCodeView.h │ ├── HWTFCodeView.m │ ├── HWTFCursorView.h │ ├── HWTFCursorView.m │ ├── HWTextCodeView.h │ └── HWTextCodeView.m ├── Info.plist ├── ViewController.h ├── ViewController.m └── main.m ├── README.md └── Screen.png /.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 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | # Pods/ 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 52 | 53 | fastlane/report.xml 54 | fastlane/Preview.html 55 | fastlane/screenshots/**/*.png 56 | fastlane/test_output 57 | 58 | # Code Injection 59 | # 60 | # After new code Injection tools there's a generated folder /iOSInjectionProject 61 | # https://github.com/johnno1962/injectionforxcode 62 | 63 | iOSInjectionProject/ 64 | -------------------------------------------------------------------------------- /CodeTextDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 87BA7269215B3DC200A6911D /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 87BA7268215B3DC200A6911D /* AppDelegate.m */; }; 11 | 87BA726C215B3DC200A6911D /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 87BA726B215B3DC200A6911D /* ViewController.m */; }; 12 | 87BA726F215B3DC200A6911D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 87BA726D215B3DC200A6911D /* Main.storyboard */; }; 13 | 87BA7271215B3DC300A6911D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 87BA7270215B3DC300A6911D /* Assets.xcassets */; }; 14 | 87BA7274215B3DC300A6911D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 87BA7272215B3DC300A6911D /* LaunchScreen.storyboard */; }; 15 | 87BA7277215B3DC300A6911D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 87BA7276215B3DC300A6911D /* main.m */; }; 16 | 87BA7284215B3E3B00A6911D /* HWTFCodeBView.m in Sources */ = {isa = PBXBuildFile; fileRef = 87BA727E215B3E3B00A6911D /* HWTFCodeBView.m */; }; 17 | 87BA7285215B3E3B00A6911D /* HWTFCodeView.m in Sources */ = {isa = PBXBuildFile; fileRef = 87BA727F215B3E3B00A6911D /* HWTFCodeView.m */; }; 18 | 87BA7286215B3E3B00A6911D /* HWTextCodeView.m in Sources */ = {isa = PBXBuildFile; fileRef = 87BA7280215B3E3B00A6911D /* HWTextCodeView.m */; }; 19 | 87DCFBBE21C26B2100D4C08D /* HWTFCursorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 87DCFBBD21C26B2100D4C08D /* HWTFCursorView.m */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | 87BA7264215B3DC200A6911D /* CodeTextDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CodeTextDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | 87BA7267215B3DC200A6911D /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 25 | 87BA7268215B3DC200A6911D /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 26 | 87BA726A215B3DC200A6911D /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 27 | 87BA726B215B3DC200A6911D /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 28 | 87BA726E215B3DC200A6911D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 29 | 87BA7270215B3DC300A6911D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 30 | 87BA7273215B3DC300A6911D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 31 | 87BA7275215B3DC300A6911D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 32 | 87BA7276215B3DC300A6911D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 33 | 87BA727E215B3E3B00A6911D /* HWTFCodeBView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HWTFCodeBView.m; sourceTree = ""; }; 34 | 87BA727F215B3E3B00A6911D /* HWTFCodeView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HWTFCodeView.m; sourceTree = ""; }; 35 | 87BA7280215B3E3B00A6911D /* HWTextCodeView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HWTextCodeView.m; sourceTree = ""; }; 36 | 87BA7281215B3E3B00A6911D /* HWTFCodeBView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HWTFCodeBView.h; sourceTree = ""; }; 37 | 87BA7282215B3E3B00A6911D /* HWTextCodeView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HWTextCodeView.h; sourceTree = ""; }; 38 | 87BA7283215B3E3B00A6911D /* HWTFCodeView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HWTFCodeView.h; sourceTree = ""; }; 39 | 87DCFBBC21C26B2100D4C08D /* HWTFCursorView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HWTFCursorView.h; sourceTree = ""; }; 40 | 87DCFBBD21C26B2100D4C08D /* HWTFCursorView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HWTFCursorView.m; sourceTree = ""; }; 41 | /* End PBXFileReference section */ 42 | 43 | /* Begin PBXFrameworksBuildPhase section */ 44 | 87BA7261215B3DC200A6911D /* Frameworks */ = { 45 | isa = PBXFrameworksBuildPhase; 46 | buildActionMask = 2147483647; 47 | files = ( 48 | ); 49 | runOnlyForDeploymentPostprocessing = 0; 50 | }; 51 | /* End PBXFrameworksBuildPhase section */ 52 | 53 | /* Begin PBXGroup section */ 54 | 87BA725B215B3DC200A6911D = { 55 | isa = PBXGroup; 56 | children = ( 57 | 87BA7266215B3DC200A6911D /* CodeTextDemo */, 58 | 87BA7265215B3DC200A6911D /* Products */, 59 | ); 60 | sourceTree = ""; 61 | }; 62 | 87BA7265215B3DC200A6911D /* Products */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | 87BA7264215B3DC200A6911D /* CodeTextDemo.app */, 66 | ); 67 | name = Products; 68 | sourceTree = ""; 69 | }; 70 | 87BA7266215B3DC200A6911D /* CodeTextDemo */ = { 71 | isa = PBXGroup; 72 | children = ( 73 | 87BA727D215B3E3400A6911D /* CodeTextView */, 74 | 87BA7267215B3DC200A6911D /* AppDelegate.h */, 75 | 87BA7268215B3DC200A6911D /* AppDelegate.m */, 76 | 87BA726A215B3DC200A6911D /* ViewController.h */, 77 | 87BA726B215B3DC200A6911D /* ViewController.m */, 78 | 87BA726D215B3DC200A6911D /* Main.storyboard */, 79 | 87BA7270215B3DC300A6911D /* Assets.xcassets */, 80 | 87BA7272215B3DC300A6911D /* LaunchScreen.storyboard */, 81 | 87BA7275215B3DC300A6911D /* Info.plist */, 82 | 87BA7276215B3DC300A6911D /* main.m */, 83 | ); 84 | path = CodeTextDemo; 85 | sourceTree = ""; 86 | }; 87 | 87BA727D215B3E3400A6911D /* CodeTextView */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | 87BA7282215B3E3B00A6911D /* HWTextCodeView.h */, 91 | 87BA7280215B3E3B00A6911D /* HWTextCodeView.m */, 92 | 87BA7281215B3E3B00A6911D /* HWTFCodeBView.h */, 93 | 87BA727E215B3E3B00A6911D /* HWTFCodeBView.m */, 94 | 87BA7283215B3E3B00A6911D /* HWTFCodeView.h */, 95 | 87BA727F215B3E3B00A6911D /* HWTFCodeView.m */, 96 | 87DCFBBC21C26B2100D4C08D /* HWTFCursorView.h */, 97 | 87DCFBBD21C26B2100D4C08D /* HWTFCursorView.m */, 98 | ); 99 | path = CodeTextView; 100 | sourceTree = ""; 101 | }; 102 | /* End PBXGroup section */ 103 | 104 | /* Begin PBXNativeTarget section */ 105 | 87BA7263215B3DC200A6911D /* CodeTextDemo */ = { 106 | isa = PBXNativeTarget; 107 | buildConfigurationList = 87BA727A215B3DC300A6911D /* Build configuration list for PBXNativeTarget "CodeTextDemo" */; 108 | buildPhases = ( 109 | 87BA7260215B3DC200A6911D /* Sources */, 110 | 87BA7261215B3DC200A6911D /* Frameworks */, 111 | 87BA7262215B3DC200A6911D /* Resources */, 112 | ); 113 | buildRules = ( 114 | ); 115 | dependencies = ( 116 | ); 117 | name = CodeTextDemo; 118 | productName = CodeTextDemo; 119 | productReference = 87BA7264215B3DC200A6911D /* CodeTextDemo.app */; 120 | productType = "com.apple.product-type.application"; 121 | }; 122 | /* End PBXNativeTarget section */ 123 | 124 | /* Begin PBXProject section */ 125 | 87BA725C215B3DC200A6911D /* Project object */ = { 126 | isa = PBXProject; 127 | attributes = { 128 | LastUpgradeCheck = 1000; 129 | ORGANIZATIONNAME = "小侯爷"; 130 | TargetAttributes = { 131 | 87BA7263215B3DC200A6911D = { 132 | CreatedOnToolsVersion = 10.0; 133 | }; 134 | }; 135 | }; 136 | buildConfigurationList = 87BA725F215B3DC200A6911D /* Build configuration list for PBXProject "CodeTextDemo" */; 137 | compatibilityVersion = "Xcode 9.3"; 138 | developmentRegion = en; 139 | hasScannedForEncodings = 0; 140 | knownRegions = ( 141 | en, 142 | Base, 143 | ); 144 | mainGroup = 87BA725B215B3DC200A6911D; 145 | productRefGroup = 87BA7265215B3DC200A6911D /* Products */; 146 | projectDirPath = ""; 147 | projectRoot = ""; 148 | targets = ( 149 | 87BA7263215B3DC200A6911D /* CodeTextDemo */, 150 | ); 151 | }; 152 | /* End PBXProject section */ 153 | 154 | /* Begin PBXResourcesBuildPhase section */ 155 | 87BA7262215B3DC200A6911D /* Resources */ = { 156 | isa = PBXResourcesBuildPhase; 157 | buildActionMask = 2147483647; 158 | files = ( 159 | 87BA7274215B3DC300A6911D /* LaunchScreen.storyboard in Resources */, 160 | 87BA7271215B3DC300A6911D /* Assets.xcassets in Resources */, 161 | 87BA726F215B3DC200A6911D /* Main.storyboard in Resources */, 162 | ); 163 | runOnlyForDeploymentPostprocessing = 0; 164 | }; 165 | /* End PBXResourcesBuildPhase section */ 166 | 167 | /* Begin PBXSourcesBuildPhase section */ 168 | 87BA7260215B3DC200A6911D /* Sources */ = { 169 | isa = PBXSourcesBuildPhase; 170 | buildActionMask = 2147483647; 171 | files = ( 172 | 87BA7285215B3E3B00A6911D /* HWTFCodeView.m in Sources */, 173 | 87BA726C215B3DC200A6911D /* ViewController.m in Sources */, 174 | 87BA7277215B3DC300A6911D /* main.m in Sources */, 175 | 87BA7269215B3DC200A6911D /* AppDelegate.m in Sources */, 176 | 87DCFBBE21C26B2100D4C08D /* HWTFCursorView.m in Sources */, 177 | 87BA7284215B3E3B00A6911D /* HWTFCodeBView.m in Sources */, 178 | 87BA7286215B3E3B00A6911D /* HWTextCodeView.m in Sources */, 179 | ); 180 | runOnlyForDeploymentPostprocessing = 0; 181 | }; 182 | /* End PBXSourcesBuildPhase section */ 183 | 184 | /* Begin PBXVariantGroup section */ 185 | 87BA726D215B3DC200A6911D /* Main.storyboard */ = { 186 | isa = PBXVariantGroup; 187 | children = ( 188 | 87BA726E215B3DC200A6911D /* Base */, 189 | ); 190 | name = Main.storyboard; 191 | sourceTree = ""; 192 | }; 193 | 87BA7272215B3DC300A6911D /* LaunchScreen.storyboard */ = { 194 | isa = PBXVariantGroup; 195 | children = ( 196 | 87BA7273215B3DC300A6911D /* Base */, 197 | ); 198 | name = LaunchScreen.storyboard; 199 | sourceTree = ""; 200 | }; 201 | /* End PBXVariantGroup section */ 202 | 203 | /* Begin XCBuildConfiguration section */ 204 | 87BA7278215B3DC300A6911D /* Debug */ = { 205 | isa = XCBuildConfiguration; 206 | buildSettings = { 207 | ALWAYS_SEARCH_USER_PATHS = NO; 208 | CLANG_ANALYZER_NONNULL = YES; 209 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 210 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 211 | CLANG_CXX_LIBRARY = "libc++"; 212 | CLANG_ENABLE_MODULES = YES; 213 | CLANG_ENABLE_OBJC_ARC = YES; 214 | CLANG_ENABLE_OBJC_WEAK = YES; 215 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 216 | CLANG_WARN_BOOL_CONVERSION = YES; 217 | CLANG_WARN_COMMA = YES; 218 | CLANG_WARN_CONSTANT_CONVERSION = YES; 219 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 220 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 221 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 222 | CLANG_WARN_EMPTY_BODY = YES; 223 | CLANG_WARN_ENUM_CONVERSION = YES; 224 | CLANG_WARN_INFINITE_RECURSION = YES; 225 | CLANG_WARN_INT_CONVERSION = YES; 226 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 227 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 228 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 229 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 230 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 231 | CLANG_WARN_STRICT_PROTOTYPES = YES; 232 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 233 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 234 | CLANG_WARN_UNREACHABLE_CODE = YES; 235 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 236 | CODE_SIGN_IDENTITY = "iPhone Developer"; 237 | COPY_PHASE_STRIP = NO; 238 | DEBUG_INFORMATION_FORMAT = dwarf; 239 | ENABLE_STRICT_OBJC_MSGSEND = YES; 240 | ENABLE_TESTABILITY = YES; 241 | GCC_C_LANGUAGE_STANDARD = gnu11; 242 | GCC_DYNAMIC_NO_PIC = NO; 243 | GCC_NO_COMMON_BLOCKS = YES; 244 | GCC_OPTIMIZATION_LEVEL = 0; 245 | GCC_PREPROCESSOR_DEFINITIONS = ( 246 | "DEBUG=1", 247 | "$(inherited)", 248 | ); 249 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 250 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 251 | GCC_WARN_UNDECLARED_SELECTOR = YES; 252 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 253 | GCC_WARN_UNUSED_FUNCTION = YES; 254 | GCC_WARN_UNUSED_VARIABLE = YES; 255 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 256 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 257 | MTL_FAST_MATH = YES; 258 | ONLY_ACTIVE_ARCH = YES; 259 | SDKROOT = iphoneos; 260 | }; 261 | name = Debug; 262 | }; 263 | 87BA7279215B3DC300A6911D /* Release */ = { 264 | isa = XCBuildConfiguration; 265 | buildSettings = { 266 | ALWAYS_SEARCH_USER_PATHS = NO; 267 | CLANG_ANALYZER_NONNULL = YES; 268 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 269 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 270 | CLANG_CXX_LIBRARY = "libc++"; 271 | CLANG_ENABLE_MODULES = YES; 272 | CLANG_ENABLE_OBJC_ARC = YES; 273 | CLANG_ENABLE_OBJC_WEAK = YES; 274 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 275 | CLANG_WARN_BOOL_CONVERSION = YES; 276 | CLANG_WARN_COMMA = YES; 277 | CLANG_WARN_CONSTANT_CONVERSION = YES; 278 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 279 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 280 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 281 | CLANG_WARN_EMPTY_BODY = YES; 282 | CLANG_WARN_ENUM_CONVERSION = YES; 283 | CLANG_WARN_INFINITE_RECURSION = YES; 284 | CLANG_WARN_INT_CONVERSION = YES; 285 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 286 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 287 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 288 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 289 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 290 | CLANG_WARN_STRICT_PROTOTYPES = YES; 291 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 292 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 293 | CLANG_WARN_UNREACHABLE_CODE = YES; 294 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 295 | CODE_SIGN_IDENTITY = "iPhone Developer"; 296 | COPY_PHASE_STRIP = NO; 297 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 298 | ENABLE_NS_ASSERTIONS = NO; 299 | ENABLE_STRICT_OBJC_MSGSEND = YES; 300 | GCC_C_LANGUAGE_STANDARD = gnu11; 301 | GCC_NO_COMMON_BLOCKS = YES; 302 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 303 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 304 | GCC_WARN_UNDECLARED_SELECTOR = YES; 305 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 306 | GCC_WARN_UNUSED_FUNCTION = YES; 307 | GCC_WARN_UNUSED_VARIABLE = YES; 308 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 309 | MTL_ENABLE_DEBUG_INFO = NO; 310 | MTL_FAST_MATH = YES; 311 | SDKROOT = iphoneos; 312 | VALIDATE_PRODUCT = YES; 313 | }; 314 | name = Release; 315 | }; 316 | 87BA727B215B3DC300A6911D /* Debug */ = { 317 | isa = XCBuildConfiguration; 318 | buildSettings = { 319 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 320 | CODE_SIGN_STYLE = Automatic; 321 | INFOPLIST_FILE = CodeTextDemo/Info.plist; 322 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 323 | LD_RUNPATH_SEARCH_PATHS = ( 324 | "$(inherited)", 325 | "@executable_path/Frameworks", 326 | ); 327 | PRODUCT_BUNDLE_IDENTIFIER = de.CodeTextDemo; 328 | PRODUCT_NAME = "$(TARGET_NAME)"; 329 | TARGETED_DEVICE_FAMILY = "1,2"; 330 | }; 331 | name = Debug; 332 | }; 333 | 87BA727C215B3DC300A6911D /* Release */ = { 334 | isa = XCBuildConfiguration; 335 | buildSettings = { 336 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 337 | CODE_SIGN_STYLE = Automatic; 338 | INFOPLIST_FILE = CodeTextDemo/Info.plist; 339 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 340 | LD_RUNPATH_SEARCH_PATHS = ( 341 | "$(inherited)", 342 | "@executable_path/Frameworks", 343 | ); 344 | PRODUCT_BUNDLE_IDENTIFIER = de.CodeTextDemo; 345 | PRODUCT_NAME = "$(TARGET_NAME)"; 346 | TARGETED_DEVICE_FAMILY = "1,2"; 347 | }; 348 | name = Release; 349 | }; 350 | /* End XCBuildConfiguration section */ 351 | 352 | /* Begin XCConfigurationList section */ 353 | 87BA725F215B3DC200A6911D /* Build configuration list for PBXProject "CodeTextDemo" */ = { 354 | isa = XCConfigurationList; 355 | buildConfigurations = ( 356 | 87BA7278215B3DC300A6911D /* Debug */, 357 | 87BA7279215B3DC300A6911D /* Release */, 358 | ); 359 | defaultConfigurationIsVisible = 0; 360 | defaultConfigurationName = Release; 361 | }; 362 | 87BA727A215B3DC300A6911D /* Build configuration list for PBXNativeTarget "CodeTextDemo" */ = { 363 | isa = XCConfigurationList; 364 | buildConfigurations = ( 365 | 87BA727B215B3DC300A6911D /* Debug */, 366 | 87BA727C215B3DC300A6911D /* Release */, 367 | ); 368 | defaultConfigurationIsVisible = 0; 369 | defaultConfigurationName = Release; 370 | }; 371 | /* End XCConfigurationList section */ 372 | }; 373 | rootObject = 87BA725C215B3DC200A6911D /* Project object */; 374 | } 375 | -------------------------------------------------------------------------------- /CodeTextDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CodeTextDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CodeTextDemo.xcodeproj/project.xcworkspace/xcuserdata/houwan.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HouWan/CodeTextDemo/f00a31ecf30457d8d890c4bafaec911422b6d98b/CodeTextDemo.xcodeproj/project.xcworkspace/xcuserdata/houwan.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /CodeTextDemo.xcodeproj/xcuserdata/houwan.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | CodeTextDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /CodeTextDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // CodeTextDemo 3 | // 4 | // Created by 小侯爷 on 2018/9/20. 5 | // Copyright © 2018年 小侯爷. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface AppDelegate : UIResponder 11 | 12 | @property (strong, nonatomic) UIWindow *window; 13 | 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /CodeTextDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // CodeTextDemo 3 | // 4 | // Created by 小侯爷 on 2018/9/20. 5 | // Copyright © 2018年 小侯爷. All rights reserved. 6 | // 7 | 8 | #import "AppDelegate.h" 9 | 10 | @interface AppDelegate () 11 | 12 | @end 13 | 14 | @implementation AppDelegate 15 | 16 | 17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 18 | // Override point for customization after application launch. 19 | return YES; 20 | } 21 | 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // 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. 25 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 26 | } 27 | 28 | 29 | - (void)applicationDidEnterBackground:(UIApplication *)application { 30 | // 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. 31 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 32 | } 33 | 34 | 35 | - (void)applicationWillEnterForeground:(UIApplication *)application { 36 | // 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. 37 | } 38 | 39 | 40 | - (void)applicationDidBecomeActive:(UIApplication *)application { 41 | // 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. 42 | } 43 | 44 | 45 | - (void)applicationWillTerminate:(UIApplication *)application { 46 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 47 | } 48 | 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /CodeTextDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /CodeTextDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CodeTextDemo/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 | -------------------------------------------------------------------------------- /CodeTextDemo/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 | -------------------------------------------------------------------------------- /CodeTextDemo/CodeTextView/HWTFCodeBView.h: -------------------------------------------------------------------------------- 1 | // 2 | // CodeTextDemo 3 | // 4 | // Created by 小侯爷 on 2018/9/20. 5 | // Copyright © 2018年 小侯爷. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | /** 13 | * 基础版 - 方块 14 | */ 15 | @interface HWTFCodeBView : UIView 16 | 17 | /// 当前输入的内容 18 | @property (nonatomic, copy, readonly) NSString *code; 19 | 20 | - (instancetype)initWithCount:(NSInteger)count margin:(CGFloat)margin; 21 | 22 | - (instancetype)init UNAVAILABLE_ATTRIBUTE; 23 | + (instancetype)new UNAVAILABLE_ATTRIBUTE; 24 | 25 | @end 26 | 27 | NS_ASSUME_NONNULL_END 28 | -------------------------------------------------------------------------------- /CodeTextDemo/CodeTextView/HWTFCodeBView.m: -------------------------------------------------------------------------------- 1 | // 2 | // CodeTextDemo 3 | // 4 | // Created by 小侯爷 on 2018/9/20. 5 | // Copyright © 2018年 小侯爷. All rights reserved. 6 | // 7 | 8 | #import "HWTFCodeBView.h" 9 | 10 | @interface HWTFCodeBView () 11 | 12 | @property (nonatomic, assign) NSInteger itemCount; 13 | 14 | @property (nonatomic, assign) CGFloat itemMargin; 15 | 16 | @property (nonatomic, weak) UITextField *textField; 17 | 18 | @property (nonatomic, weak) UIControl *maskView; 19 | 20 | @property (nonatomic, strong) NSMutableArray *labels; 21 | 22 | @end 23 | 24 | @implementation HWTFCodeBView 25 | 26 | #pragma mark - 初始化 27 | - (instancetype)initWithCount:(NSInteger)count margin:(CGFloat)margin 28 | { 29 | if (self = [super init]) { 30 | 31 | self.itemCount = count; 32 | self.itemMargin = margin; 33 | 34 | [self configTextField]; 35 | } 36 | return self; 37 | } 38 | 39 | - (void)configTextField 40 | { 41 | self.backgroundColor = [UIColor whiteColor]; 42 | 43 | self.labels = @[].mutableCopy; 44 | 45 | UITextField *textField = [[UITextField alloc] init]; 46 | textField.autocapitalizationType = UITextAutocapitalizationTypeNone; 47 | textField.keyboardType = UIKeyboardTypeNumberPad; 48 | [textField addTarget:self action:@selector(tfEditingChanged:) forControlEvents:(UIControlEventEditingChanged)]; 49 | 50 | [self addSubview:textField]; 51 | self.textField = textField; 52 | 53 | UIButton *maskView = [UIButton new]; 54 | maskView.backgroundColor = [UIColor whiteColor]; 55 | [maskView addTarget:self action:@selector(clickMaskView) forControlEvents:(UIControlEventTouchUpInside)]; 56 | [self addSubview:maskView]; 57 | self.maskView = maskView; 58 | 59 | for (NSInteger i = 0; i < self.itemCount; i++) 60 | { 61 | UILabel *label = [UILabel new]; 62 | label.textAlignment = NSTextAlignmentCenter; 63 | label.textColor = [UIColor darkTextColor]; 64 | label.font = [UIFont fontWithName:@"PingFangSC-Regular" size:36]; 65 | label.layer.borderColor = [[UIColor redColor] CGColor]; 66 | label.layer.borderWidth = 1; 67 | [self addSubview:label]; 68 | [self.labels addObject:label]; 69 | } 70 | } 71 | 72 | - (void)layoutSubviews 73 | { 74 | [super layoutSubviews]; 75 | 76 | if (self.labels.count != self.itemCount) return; 77 | 78 | CGFloat temp = self.bounds.size.width - (self.itemMargin * (self.itemCount - 1)); 79 | CGFloat w = temp / self.itemCount; 80 | CGFloat x = 0; 81 | 82 | for (NSInteger i = 0; i < self.labels.count; i++) 83 | { 84 | x = i * (w + self.itemMargin); 85 | 86 | UILabel *label = self.labels[i]; 87 | label.frame = CGRectMake(x, 0, w, self.bounds.size.height); 88 | } 89 | 90 | self.textField.frame = self.bounds; 91 | self.maskView.frame = self.bounds; 92 | } 93 | 94 | #pragma mark - 编辑改变 95 | - (void)tfEditingChanged:(UITextField *)textField 96 | { 97 | if (textField.text.length > self.itemCount) { 98 | textField.text = [textField.text substringWithRange:NSMakeRange(0, self.itemCount)]; 99 | } 100 | 101 | for (int i = 0; i < self.itemCount; i++) 102 | { 103 | UILabel *label = [self.labels objectAtIndex:i]; 104 | 105 | if (i < textField.text.length) { 106 | label.text = [textField.text substringWithRange:NSMakeRange(i, 1)]; 107 | } else { 108 | label.text = nil; 109 | } 110 | } 111 | 112 | // 输入完毕后,自动隐藏键盘 113 | if (textField.text.length >= self.itemCount) { 114 | [textField resignFirstResponder]; 115 | } 116 | } 117 | 118 | - (void)clickMaskView 119 | { 120 | [self.textField becomeFirstResponder]; 121 | } 122 | 123 | - (BOOL)endEditing:(BOOL)force 124 | { 125 | [self.textField endEditing:force]; 126 | return [super endEditing:force]; 127 | } 128 | 129 | - (NSString *)code 130 | { 131 | return self.textField.text; 132 | } 133 | 134 | @end 135 | -------------------------------------------------------------------------------- /CodeTextDemo/CodeTextView/HWTFCodeView.h: -------------------------------------------------------------------------------- 1 | // 2 | // CodeTextDemo 3 | // 4 | // Created by 小侯爷 on 2018/9/20. 5 | // Copyright © 2018年 小侯爷. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | 13 | /** 14 | * 基础版 - 下划线 15 | */ 16 | @interface HWTFCodeView : UIView 17 | 18 | /// 当前输入的内容 19 | @property (nonatomic, copy, readonly) NSString *code; 20 | 21 | - (instancetype)initWithCount:(NSInteger)count margin:(CGFloat)margin; 22 | 23 | - (instancetype)init UNAVAILABLE_ATTRIBUTE; 24 | + (instancetype)new UNAVAILABLE_ATTRIBUTE; 25 | 26 | @end 27 | 28 | 29 | 30 | NS_ASSUME_NONNULL_END 31 | -------------------------------------------------------------------------------- /CodeTextDemo/CodeTextView/HWTFCodeView.m: -------------------------------------------------------------------------------- 1 | // 2 | // CodeTextDemo 3 | // 4 | // Created by 小侯爷 on 2018/9/20. 5 | // Copyright © 2018年 小侯爷. All rights reserved. 6 | // 7 | 8 | #import "HWTFCodeView.h" 9 | 10 | @interface HWTFCodeView () 11 | 12 | @property (nonatomic, assign) NSInteger itemCount; 13 | 14 | @property (nonatomic, assign) CGFloat itemMargin; 15 | 16 | @property (nonatomic, weak) UITextField *textField; 17 | 18 | @property (nonatomic, weak) UIControl *maskView; 19 | 20 | @property (nonatomic, strong) NSMutableArray *labels; 21 | 22 | @property (nonatomic, strong) NSMutableArray *lines; 23 | 24 | @end 25 | 26 | @implementation HWTFCodeView 27 | 28 | #pragma mark - 初始化 29 | - (instancetype)initWithCount:(NSInteger)count margin:(CGFloat)margin 30 | { 31 | if (self = [super init]) { 32 | 33 | self.itemCount = count; 34 | self.itemMargin = margin; 35 | 36 | [self configTextField]; 37 | } 38 | return self; 39 | } 40 | 41 | - (void)configTextField 42 | { 43 | self.backgroundColor = [UIColor whiteColor]; 44 | 45 | self.labels = @[].mutableCopy; 46 | self.lines = @[].mutableCopy; 47 | 48 | UITextField *textField = [[UITextField alloc] init]; 49 | textField.autocapitalizationType = UITextAutocapitalizationTypeNone; 50 | textField.keyboardType = UIKeyboardTypeNumberPad; 51 | [textField addTarget:self action:@selector(tfEditingChanged:) forControlEvents:(UIControlEventEditingChanged)]; 52 | 53 | // 小技巧:这个属性为YES,可以强制使用系统的数字键盘,缺点是重新输入时,会清空之前的内容 54 | // clearsOnBeginEditing 属性并不适用于 secureTextEntry = YES 时 55 | // textField.secureTextEntry = YES; 56 | 57 | [self addSubview:textField]; 58 | self.textField = textField; 59 | 60 | // 小技巧:通过textField上层覆盖一个maskView,可以去掉textField的长按事件 61 | UIButton *maskView = [UIButton new]; 62 | maskView.backgroundColor = [UIColor whiteColor]; 63 | [maskView addTarget:self action:@selector(clickMaskView) forControlEvents:(UIControlEventTouchUpInside)]; 64 | [self addSubview:maskView]; 65 | self.maskView = maskView; 66 | 67 | for (NSInteger i = 0; i < self.itemCount; i++) 68 | { 69 | UILabel *label = [UILabel new]; 70 | label.textAlignment = NSTextAlignmentCenter; 71 | label.textColor = [UIColor darkTextColor]; 72 | label.font = [UIFont fontWithName:@"PingFangSC-Regular" size:41.5]; 73 | [self addSubview:label]; 74 | [self.labels addObject:label]; 75 | } 76 | 77 | for (NSInteger i = 0; i < self.itemCount; i++) 78 | { 79 | UIView *line = [UIView new]; 80 | line.backgroundColor = [UIColor purpleColor]; 81 | [self addSubview:line]; 82 | [self.lines addObject:line]; 83 | } 84 | } 85 | 86 | - (void)layoutSubviews 87 | { 88 | [super layoutSubviews]; 89 | 90 | if (self.labels.count != self.itemCount) return; 91 | 92 | CGFloat temp = self.bounds.size.width - (self.itemMargin * (self.itemCount - 1)); 93 | CGFloat w = temp / self.itemCount; 94 | CGFloat x = 0; 95 | 96 | for (NSInteger i = 0; i < self.labels.count; i++) 97 | { 98 | x = i * (w + self.itemMargin); 99 | 100 | UILabel *label = self.labels[i]; 101 | label.frame = CGRectMake(x, 0, w, self.bounds.size.height); 102 | 103 | UIView *line = self.lines[i]; 104 | line.frame = CGRectMake(x, self.bounds.size.height - 1, w, 1); 105 | } 106 | 107 | self.textField.frame = self.bounds; 108 | self.maskView.frame = self.bounds; 109 | } 110 | 111 | #pragma mark - 编辑改变 112 | - (void)tfEditingChanged:(UITextField *)textField 113 | { 114 | if (textField.text.length > self.itemCount) { 115 | textField.text = [textField.text substringWithRange:NSMakeRange(0, self.itemCount)]; 116 | } 117 | 118 | for (int i = 0; i < self.itemCount; i++) 119 | { 120 | UILabel *label = [self.labels objectAtIndex:i]; 121 | 122 | if (i < textField.text.length) { 123 | label.text = [textField.text substringWithRange:NSMakeRange(i, 1)]; 124 | } else { 125 | label.text = nil; 126 | } 127 | } 128 | 129 | // 输入完毕后,自动隐藏键盘 130 | if (textField.text.length >= self.itemCount) { 131 | [textField resignFirstResponder]; 132 | } 133 | } 134 | 135 | - (void)clickMaskView 136 | { 137 | [self.textField becomeFirstResponder]; 138 | } 139 | 140 | - (BOOL)endEditing:(BOOL)force 141 | { 142 | [self.textField endEditing:force]; 143 | return [super endEditing:force]; 144 | } 145 | 146 | - (NSString *)code 147 | { 148 | return self.textField.text; 149 | } 150 | 151 | @end 152 | 153 | -------------------------------------------------------------------------------- /CodeTextDemo/CodeTextView/HWTFCursorView.h: -------------------------------------------------------------------------------- 1 | // 2 | // HWTFCursorView.h 3 | // CodeTextDemo 4 | // 5 | // Created by 侯万 on 2018/12/13. 6 | // Copyright © 2018 小侯爷. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | 14 | /** 15 | 基础版 - 下划线 - 有光标 16 | */ 17 | @interface HWTFCursorView : UIView 18 | 19 | 20 | // ----------------------------Data---------------------------- 21 | /// 当前输入的内容 22 | @property (nonatomic, copy, readonly) NSString *code; 23 | 24 | 25 | // ----------------------------Method---------------------------- 26 | 27 | - (instancetype)initWithCount:(NSInteger)count margin:(CGFloat)margin; 28 | 29 | - (instancetype)init UNAVAILABLE_ATTRIBUTE; 30 | + (instancetype)new UNAVAILABLE_ATTRIBUTE; 31 | 32 | @end 33 | 34 | 35 | 36 | 37 | // ------------------------------------------------------------------------ 38 | // -----------------------------HWCursorLabel------------------------------ 39 | // ------------------------------------------------------------------------ 40 | 41 | 42 | @interface HWCursorLabel : UILabel 43 | 44 | @property (nonatomic, weak, readonly) UIView *cursorView; 45 | 46 | - (void)startAnimating; 47 | - (void)stopAnimating; 48 | 49 | @end 50 | 51 | NS_ASSUME_NONNULL_END 52 | -------------------------------------------------------------------------------- /CodeTextDemo/CodeTextView/HWTFCursorView.m: -------------------------------------------------------------------------------- 1 | // 2 | // HWTFCursorView.m 3 | // CodeTextDemo 4 | // 5 | // Created by 侯万 on 2018/12/13. 6 | // Copyright © 2018 小侯爷. All rights reserved. 7 | // 8 | 9 | #import "HWTFCursorView.h" 10 | 11 | @interface HWTFCursorView () 12 | 13 | @property (nonatomic, assign) NSInteger itemCount; 14 | 15 | @property (nonatomic, assign) CGFloat itemMargin; 16 | 17 | @property (nonatomic, weak) UITextField *textField; 18 | 19 | @property (nonatomic, weak) UIControl *maskView; 20 | 21 | @property (nonatomic, strong) NSMutableArray *labels; 22 | 23 | @property (nonatomic, strong) NSMutableArray *lines; 24 | 25 | @property (nonatomic, weak) HWCursorLabel *currentLabel; 26 | 27 | @end 28 | 29 | 30 | 31 | @implementation HWTFCursorView 32 | 33 | #pragma mark - 初始化 34 | - (instancetype)initWithCount:(NSInteger)count margin:(CGFloat)margin 35 | { 36 | if (self = [super init]) { 37 | 38 | self.itemCount = count; 39 | self.itemMargin = margin; 40 | 41 | [self configTextField]; 42 | } 43 | return self; 44 | } 45 | 46 | - (void)configTextField 47 | { 48 | self.backgroundColor = [UIColor whiteColor]; 49 | 50 | self.labels = @[].mutableCopy; 51 | self.lines = @[].mutableCopy; 52 | 53 | UITextField *textField = [[UITextField alloc] init]; 54 | textField.autocapitalizationType = UITextAutocapitalizationTypeNone; 55 | textField.keyboardType = UIKeyboardTypeNumberPad; 56 | [textField addTarget:self action:@selector(tfEditingChanged:) forControlEvents:(UIControlEventEditingChanged)]; 57 | 58 | // 小技巧:这个属性为YES,可以强制使用系统的数字键盘,缺点是重新输入时,会清空之前的内容 59 | // clearsOnBeginEditing 属性并不适用于 secureTextEntry = YES 时 60 | // textField.secureTextEntry = YES; 61 | 62 | [self addSubview:textField]; 63 | self.textField = textField; 64 | 65 | // 小技巧:通过textField上层覆盖一个maskView,可以去掉textField的长按事件 66 | UIButton *maskView = [UIButton new]; 67 | maskView.backgroundColor = [UIColor whiteColor]; 68 | [maskView addTarget:self action:@selector(clickMaskView) forControlEvents:(UIControlEventTouchUpInside)]; 69 | [self addSubview:maskView]; 70 | self.maskView = maskView; 71 | 72 | for (NSInteger i = 0; i < self.itemCount; i++) 73 | { 74 | HWCursorLabel *label = [HWCursorLabel new]; 75 | label.textAlignment = NSTextAlignmentCenter; 76 | label.textColor = [UIColor darkTextColor]; 77 | label.font = [UIFont fontWithName:@"PingFangSC-Regular" size:41.5]; 78 | [self addSubview:label]; 79 | [self.labels addObject:label]; 80 | } 81 | 82 | for (NSInteger i = 0; i < self.itemCount; i++) 83 | { 84 | UIView *line = [UIView new]; 85 | line.backgroundColor = [UIColor purpleColor]; 86 | [self addSubview:line]; 87 | [self.lines addObject:line]; 88 | } 89 | } 90 | 91 | 92 | - (void)layoutSubviews 93 | { 94 | [super layoutSubviews]; 95 | 96 | if (self.labels.count != self.itemCount) return; 97 | 98 | CGFloat temp = self.bounds.size.width - (self.itemMargin * (self.itemCount - 1)); 99 | CGFloat w = temp / self.itemCount; 100 | CGFloat x = 0; 101 | 102 | for (NSInteger i = 0; i < self.labels.count; i++) 103 | { 104 | x = i * (w + self.itemMargin); 105 | 106 | UILabel *label = self.labels[i]; 107 | label.frame = CGRectMake(x, 0, w, self.bounds.size.height); 108 | 109 | UIView *line = self.lines[i]; 110 | line.frame = CGRectMake(x, self.bounds.size.height - 1, w, 1); 111 | } 112 | 113 | self.textField.frame = self.bounds; 114 | self.maskView.frame = self.bounds; 115 | } 116 | 117 | #pragma mark - 编辑改变 118 | - (void)tfEditingChanged:(UITextField *)textField 119 | { 120 | if (textField.text.length > self.itemCount) { 121 | textField.text = [textField.text substringWithRange:NSMakeRange(0, self.itemCount)]; 122 | } 123 | 124 | for (int i = 0; i < self.itemCount; i++) 125 | { 126 | UILabel *label = [self.labels objectAtIndex:i]; 127 | 128 | if (i < textField.text.length) { 129 | label.text = [textField.text substringWithRange:NSMakeRange(i, 1)]; 130 | } else { 131 | label.text = nil; 132 | } 133 | } 134 | 135 | [self cursor]; 136 | 137 | // 输入完毕后,自动隐藏键盘 138 | if (textField.text.length >= self.itemCount) { 139 | [self.currentLabel stopAnimating]; 140 | [textField resignFirstResponder]; 141 | } 142 | } 143 | 144 | - (void)clickMaskView 145 | { 146 | [self.textField becomeFirstResponder]; 147 | [self cursor]; 148 | } 149 | 150 | - (BOOL)endEditing:(BOOL)force 151 | { 152 | [self.textField endEditing:force]; 153 | [self.currentLabel stopAnimating]; 154 | return [super endEditing:force]; 155 | } 156 | 157 | #pragma mark - 处理光标 158 | - (void)cursor 159 | { 160 | [self.currentLabel stopAnimating]; 161 | 162 | NSInteger index = self.code.length; 163 | if (index < 0) index = 0; 164 | if (index >= self.labels.count) index = self.labels.count - 1; 165 | 166 | HWCursorLabel *label = [self.labels objectAtIndex:index]; 167 | 168 | [label startAnimating]; 169 | self.currentLabel = label; 170 | } 171 | 172 | - (NSString *)code 173 | { 174 | return self.textField.text; 175 | } 176 | 177 | @end 178 | 179 | 180 | 181 | // ------------------------------------------------------------------------ 182 | // -----------------------------HWCursorLabel------------------------------ 183 | // ------------------------------------------------------------------------ 184 | 185 | 186 | @implementation HWCursorLabel 187 | 188 | - (instancetype)initWithFrame:(CGRect)frame 189 | { 190 | self = [super initWithFrame:frame]; 191 | if (self) { 192 | [self setupView]; 193 | } 194 | return self; 195 | } 196 | 197 | - (instancetype)initWithCoder:(NSCoder *)coder 198 | { 199 | self = [super initWithCoder:coder]; 200 | if (self) { 201 | [self setupView]; 202 | } 203 | return self; 204 | } 205 | 206 | #pragma mark - 初始化View 207 | - (void)setupView 208 | { 209 | UIView *cursorView = [[UIView alloc] init]; 210 | cursorView.backgroundColor = [UIColor blueColor]; 211 | cursorView.alpha = 0; 212 | [self addSubview:cursorView]; 213 | _cursorView = cursorView; 214 | } 215 | 216 | - (void)layoutSubviews 217 | { 218 | [super layoutSubviews]; 219 | 220 | CGFloat h = 30; 221 | CGFloat w = 2; 222 | CGFloat x = self.bounds.size.width * 0.5; 223 | CGFloat y = self.bounds.size.height * 0.5; 224 | self.cursorView.frame = CGRectMake(0, 0, w, h); 225 | self.cursorView.center = CGPointMake(x, y); 226 | } 227 | 228 | - (void)startAnimating 229 | { 230 | if (self.text.length > 0) return; 231 | 232 | CABasicAnimation *oa = [CABasicAnimation animationWithKeyPath:@"opacity"]; 233 | oa.fromValue = [NSNumber numberWithFloat:0]; 234 | oa.toValue = [NSNumber numberWithFloat:1]; 235 | oa.duration = 1; 236 | oa.repeatCount = MAXFLOAT; 237 | oa.removedOnCompletion = NO; 238 | oa.fillMode = kCAFillModeForwards; 239 | oa.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]; 240 | [self.cursorView.layer addAnimation:oa forKey:@"opacity"]; 241 | } 242 | 243 | - (void)stopAnimating 244 | { 245 | [self.cursorView.layer removeAnimationForKey:@"opacity"]; 246 | } 247 | 248 | @end 249 | -------------------------------------------------------------------------------- /CodeTextDemo/CodeTextView/HWTextCodeView.h: -------------------------------------------------------------------------------- 1 | // 2 | // CodeTextDemo 3 | // 4 | // Created by 小侯爷 on 2018/9/20. 5 | // Copyright © 2018年 小侯爷. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | /** 13 | * 完善版 - 加入动画 - 下划线 14 | */ 15 | @interface HWTextCodeView : UIView 16 | 17 | /// 当前输入的内容 18 | @property (nonatomic, copy, readonly) NSString *code; 19 | 20 | - (instancetype)initWithCount:(NSInteger)count margin:(CGFloat)margin; 21 | 22 | - (instancetype)init UNAVAILABLE_ATTRIBUTE; 23 | + (instancetype)new UNAVAILABLE_ATTRIBUTE; 24 | 25 | @end 26 | 27 | 28 | 29 | // ------------------------------------------------------------------------ 30 | // -----------------------------HWTC_lineView------------------------------ 31 | // ------------------------------------------------------------------------ 32 | 33 | 34 | @interface HWTC_lineView : UIView 35 | 36 | @property (nonatomic, weak) UIView *colorView; 37 | 38 | - (void)animation; 39 | 40 | @end 41 | 42 | NS_ASSUME_NONNULL_END 43 | -------------------------------------------------------------------------------- /CodeTextDemo/CodeTextView/HWTextCodeView.m: -------------------------------------------------------------------------------- 1 | // 2 | // CodeTextDemo 3 | // 4 | // Created by 小侯爷 on 2018/9/20. 5 | // Copyright © 2018年 小侯爷. All rights reserved. 6 | // 7 | 8 | #import "HWTextCodeView.h" 9 | 10 | @interface HWTextCodeView () 11 | 12 | @property (nonatomic, assign) NSInteger itemCount; 13 | 14 | @property (nonatomic, assign) CGFloat itemMargin; 15 | 16 | @property (nonatomic, weak) UITextField *textField; 17 | 18 | @property (nonatomic, weak) UIControl *maskView; 19 | 20 | @property (nonatomic, strong) NSMutableArray *labels; 21 | 22 | @property (nonatomic, strong) NSMutableArray *lines; 23 | 24 | /// 临时保存上次输入的内容(用于判断 删除 还是 输入) 25 | @property (nonatomic, copy) NSString *tempStr; 26 | 27 | @end 28 | 29 | @implementation HWTextCodeView 30 | 31 | - (instancetype)initWithCount:(NSInteger)count margin:(CGFloat)margin 32 | { 33 | if (self = [super init]) { 34 | 35 | self.itemCount = count; 36 | self.itemMargin = margin; 37 | 38 | [self configTextField]; 39 | } 40 | return self; 41 | } 42 | 43 | - (void)configTextField 44 | { 45 | self.backgroundColor = [UIColor whiteColor]; 46 | 47 | self.labels = @[].mutableCopy; 48 | self.lines = @[].mutableCopy; 49 | 50 | UITextField *textField = [[UITextField alloc] init]; 51 | textField.autocapitalizationType = UITextAutocapitalizationTypeNone; 52 | textField.keyboardType = UIKeyboardTypeNumberPad; 53 | [textField addTarget:self action:@selector(tfEditingChanged:) forControlEvents:(UIControlEventEditingChanged)]; 54 | [self addSubview:textField]; 55 | self.textField = textField; 56 | 57 | UIButton *maskView = [UIButton new]; 58 | maskView.backgroundColor = [UIColor whiteColor]; 59 | [maskView addTarget:self action:@selector(clickMaskView) forControlEvents:(UIControlEventTouchUpInside)]; 60 | [self addSubview:maskView]; 61 | self.maskView = maskView; 62 | 63 | for (NSInteger i = 0; i < self.itemCount; i++) 64 | { 65 | UILabel *label = [UILabel new]; 66 | label.textAlignment = NSTextAlignmentCenter; 67 | label.textColor = [UIColor purpleColor]; 68 | label.font = [UIFont fontWithName:@"PingFangSC-Regular" size:40]; 69 | [self addSubview:label]; 70 | [self.labels addObject:label]; 71 | } 72 | 73 | for (NSInteger i = 0; i < self.itemCount; i++) 74 | { 75 | HWTC_lineView *line = [HWTC_lineView new]; 76 | line.backgroundColor = [UIColor redColor]; 77 | [self addSubview:line]; 78 | [self.lines addObject:line]; 79 | } 80 | } 81 | 82 | - (void)layoutSubviews 83 | { 84 | [super layoutSubviews]; 85 | 86 | if (self.labels.count != self.itemCount) return; 87 | 88 | CGFloat temp = self.bounds.size.width - (self.itemMargin * (self.itemCount - 1)); 89 | CGFloat w = temp / self.itemCount; 90 | CGFloat x = 0; 91 | 92 | for (NSInteger i = 0; i < self.labels.count; i++) 93 | { 94 | x = i * (w + self.itemMargin); 95 | 96 | UILabel *label = self.labels[i]; 97 | label.frame = CGRectMake(x, 0, w, self.bounds.size.height); 98 | 99 | UIView *line = self.lines[i]; 100 | line.frame = CGRectMake(x, self.bounds.size.height - 1, w, 1); 101 | } 102 | 103 | self.textField.frame = self.bounds; 104 | self.maskView.frame = self.bounds; 105 | } 106 | 107 | #pragma mark - 编辑改变 108 | - (void)tfEditingChanged:(UITextField *)textField 109 | { 110 | if (textField.text.length > self.itemCount) { 111 | textField.text = [textField.text substringWithRange:NSMakeRange(0, self.itemCount)]; 112 | } 113 | 114 | for (int i = 0; i < self.itemCount; i++) 115 | { 116 | UILabel *label = [self.labels objectAtIndex:i]; 117 | UIView *line = [self.lines objectAtIndex:i]; 118 | 119 | if (i < textField.text.length) { 120 | label.text = [textField.text substringWithRange:NSMakeRange(i, 1)]; 121 | line.backgroundColor = [UIColor greenColor]; 122 | } else { 123 | label.text = nil; 124 | line.backgroundColor = [UIColor redColor]; 125 | } 126 | } 127 | 128 | // 动画效果,这里是删除时,不要动画,输入时显示动画 129 | if (self.tempStr.length < textField.text.length) { 130 | if (textField.text == nil || textField.text.length <= 0) { 131 | [self.lines.firstObject animation]; 132 | 133 | } else if (textField.text.length >= self.itemCount) { 134 | [self.lines.lastObject animation]; 135 | 136 | [self animation:self.labels.lastObject]; 137 | 138 | } else { 139 | [self.lines[self.textField.text.length - 1] animation]; 140 | 141 | UILabel *ff = self.labels[self.textField.text.length - 1]; 142 | [self animation:ff]; 143 | } 144 | } 145 | 146 | self.tempStr = textField.text; 147 | 148 | if (textField.text.length >= self.itemCount) { 149 | [textField resignFirstResponder]; 150 | } 151 | } 152 | 153 | - (void)animation:(UILabel *)label 154 | { 155 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"]; 156 | animation.duration = 0.15; 157 | animation.repeatCount = 1; 158 | animation.fromValue = @(0.1); 159 | animation.toValue = @(1); 160 | [label.layer addAnimation:animation forKey:@"zoom"]; 161 | } 162 | 163 | - (void)clickMaskView 164 | { 165 | [self.textField becomeFirstResponder]; 166 | } 167 | 168 | - (BOOL)endEditing:(BOOL)force 169 | { 170 | [self.textField endEditing:force]; 171 | return [super endEditing:force]; 172 | } 173 | 174 | - (NSString *)code 175 | { 176 | return self.textField.text; 177 | } 178 | 179 | @end 180 | 181 | 182 | // ------------------------------------------------------------------------ 183 | // -----------------------------HWTC_lineView------------------------------ 184 | // ------------------------------------------------------------------------ 185 | 186 | 187 | @implementation HWTC_lineView 188 | 189 | - (instancetype)initWithFrame:(CGRect)frame 190 | { 191 | self = [super initWithFrame:frame]; 192 | if (self) { 193 | [self setupView]; 194 | } 195 | return self; 196 | } 197 | 198 | - (instancetype)initWithCoder:(NSCoder *)coder 199 | { 200 | self = [super initWithCoder:coder]; 201 | if (self) { 202 | [self setupView]; 203 | } 204 | return self; 205 | } 206 | 207 | #pragma mark - 初始化View 208 | - (void)setupView 209 | { 210 | UIView *colorView = [UIView new]; 211 | [self addSubview:colorView]; 212 | self.colorView = colorView; 213 | } 214 | 215 | - (void)layoutSubviews 216 | { 217 | [super layoutSubviews]; 218 | self.colorView.frame = self.bounds; 219 | } 220 | 221 | - (void)setBackgroundColor:(UIColor *)backgroundColor 222 | { 223 | [super setBackgroundColor:[UIColor clearColor]]; 224 | self.colorView.backgroundColor = backgroundColor; 225 | } 226 | 227 | - (void)animation 228 | { 229 | [self.colorView.layer removeAllAnimations]; 230 | 231 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.scale.x"]; 232 | animation.duration = 0.18; 233 | animation.repeatCount = 1; 234 | animation.fromValue = @(1.0); 235 | animation.toValue = @(0.1); 236 | animation.autoreverses = YES; 237 | 238 | [self.colorView.layer addAnimation:animation forKey:@"zoom.scale.x"]; 239 | } 240 | 241 | @end 242 | -------------------------------------------------------------------------------- /CodeTextDemo/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 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /CodeTextDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CodeTextDemo 3 | // 4 | // Created by 小侯爷 on 2018/9/20. 5 | // Copyright © 2018年 小侯爷. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | // 屏幕宽度高度 11 | #define SW ([UIScreen mainScreen].bounds.size.width) 12 | #define SH ([UIScreen mainScreen].bounds.size.height) 13 | 14 | // 随机色 15 | #define HWRandomColor [UIColor colorWithRed:arc4random_uniform(256)/255.0 green:arc4random_uniform(256)/255.0 blue:arc4random_uniform(256)/255.0 alpha:1.0] 16 | 17 | @interface ViewController : UIViewController 18 | 19 | 20 | @end 21 | 22 | -------------------------------------------------------------------------------- /CodeTextDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // CodeTextDemo 3 | // 4 | // Created by 小侯爷 on 2018/9/20. 5 | // Copyright © 2018年 小侯爷. All rights reserved. 6 | // 7 | 8 | #import "ViewController.h" 9 | 10 | // ----------------------------View---------------------------- 11 | #import "HWTFCodeView.h" // 基础版 - 下划线 12 | #import "HWTFCodeBView.h" // 基础版 - 方块 13 | #import "HWTextCodeView.h" // 完善版 - 加入动画 - 下划线 14 | #import "HWTFCursorView.h" // 基础版 - 下划线 - 有光标 15 | 16 | @interface ViewController () 17 | 18 | @property (nonatomic, weak) HWTFCodeView *code1View; 19 | @property (nonatomic, weak) HWTFCodeBView *code2View; 20 | @property (nonatomic, weak) HWTextCodeView *code3View; 21 | @property (nonatomic, weak) HWTFCursorView *code4View; 22 | 23 | @end 24 | 25 | @implementation ViewController 26 | 27 | - (void)viewDidLoad 28 | { 29 | [super viewDidLoad]; 30 | 31 | UIScrollView *scrollView = [[UIScrollView alloc] init]; 32 | scrollView.contentSize = CGSizeMake(SW, SH * 1.5); 33 | scrollView.layer.borderColor = [HWRandomColor CGColor]; 34 | scrollView.layer.borderWidth = 0.5; 35 | scrollView.frame = CGRectMake(0, 0, SW, SH); 36 | [self.view addSubview:scrollView]; 37 | 38 | UILabel *labx = [UILabel new]; 39 | labx.textColor = [UIColor grayColor]; 40 | labx.font = [UIFont systemFontOfSize:16]; 41 | labx.text = @"😘页面可滑动,防止键盘挡住效果😘"; 42 | labx.frame = CGRectMake(30, 45, 320, 15); 43 | [scrollView addSubview:labx]; 44 | 45 | 46 | 47 | CGFloat x = 30; 48 | CGFloat w = SW - x * 2; 49 | CGFloat h = 50; 50 | CGFloat y = 100; 51 | 52 | // -------------------------------------------------------------------- 53 | 54 | UILabel *labA = [UILabel new]; 55 | labA.textColor = [UIColor orangeColor]; 56 | labA.font = [UIFont systemFontOfSize:13]; 57 | labA.text = @"基本实现原理 - 下划线"; 58 | labA.frame = CGRectMake(x, y, 200, 15); 59 | [scrollView addSubview:labA]; 60 | 61 | y = CGRectGetMaxY(labA.frame) + 10; 62 | 63 | HWTFCodeView *code1View = [[HWTFCodeView alloc] initWithCount:6 margin:20]; 64 | code1View.frame = CGRectMake(x, y, w, h); 65 | [scrollView addSubview:code1View]; 66 | self.code1View = code1View; 67 | 68 | 69 | // -------------------------------------------------------------------- 70 | 71 | y = CGRectGetMaxY(code1View.frame) + 60; 72 | 73 | UILabel *labB = [UILabel new]; 74 | labB.textColor = [UIColor orangeColor]; 75 | labB.font = [UIFont systemFontOfSize:13]; 76 | labB.text = @"基本实现原理 - 方块"; 77 | labB.frame = CGRectMake(x, y, 200, 15); 78 | [scrollView addSubview:labB]; 79 | 80 | y = CGRectGetMaxY(labB.frame) + 30; 81 | 82 | HWTFCodeBView *code2View = [[HWTFCodeBView alloc] initWithCount:6 margin:20]; 83 | code2View.frame = CGRectMake(x, y, w, h); 84 | [scrollView addSubview:code2View]; 85 | self.code2View = code2View; 86 | 87 | 88 | // -------------------------------------------------------------------- 89 | 90 | y = CGRectGetMaxY(code2View.frame) + 60; 91 | 92 | UILabel *labC = [UILabel new]; 93 | labC.textColor = [UIColor orangeColor]; 94 | labC.font = [UIFont systemFontOfSize:13]; 95 | labC.text = @"完善版 - 加入动画 - 下划线"; 96 | labC.frame = CGRectMake(x, y, 200, 15); 97 | [scrollView addSubview:labC]; 98 | 99 | y = CGRectGetMaxY(labC.frame) + 30; 100 | 101 | HWTextCodeView *code3View = [[HWTextCodeView alloc] initWithCount:6 margin:20]; 102 | code3View.frame = CGRectMake(x, y, w, h); 103 | [scrollView addSubview:code3View]; 104 | self.code3View = code3View; 105 | 106 | // -------------------------------------------------------------------- 107 | 108 | y = CGRectGetMaxY(code3View.frame) + 60; 109 | 110 | UILabel *labD = [UILabel new]; 111 | labD.textColor = [UIColor blueColor]; 112 | labD.font = [UIFont systemFontOfSize:13]; 113 | labD.text = @"基础版 - 下划线 - 有光标"; 114 | labD.frame = CGRectMake(x, y, 200, 15); 115 | [scrollView addSubview:labD]; 116 | 117 | y = CGRectGetMaxY(labD.frame) + 30; 118 | 119 | HWTFCursorView *code4View = [[HWTFCursorView alloc] initWithCount:6 margin:20]; 120 | code4View.frame = CGRectMake(x, y, w, h); 121 | [scrollView addSubview:code4View]; 122 | self.code4View = code4View; 123 | 124 | // -------------------------------------------------------------------- 125 | 126 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap)]; 127 | [scrollView addGestureRecognizer:tap]; 128 | } 129 | 130 | 131 | - (void)tap 132 | { 133 | [self.code1View endEditing:YES]; 134 | [self.code2View endEditing:YES]; 135 | [self.code3View endEditing:YES]; 136 | [self.code4View endEditing:YES]; 137 | } 138 | 139 | 140 | @end 141 | -------------------------------------------------------------------------------- /CodeTextDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // CodeTextDemo 4 | // 5 | // Created by 侯万 on 2018/9/26. 6 | // Copyright © 2018年 小侯爷. 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CodeTextDemo 2 | 3 | ## iOS验证码输入 4 | 5 | > * Xcode 10.0 (10A255) 新建项目并编辑 6 | > * 代码日期:2018年12月14日15:00 7 | > * 代码语言:Objective-C 8 | > * 加入光标小Demo(2018年12月14日) 9 | 10 | ![效果图](https://github.com/HouWan/CodeTextDemo/blob/master/Screen.png) 11 | 12 | 简书地址:[点我](https://www.jianshu.com/p/23f7be3677be) 13 | 简书地址:[点我](https://www.jianshu.com/p/23f7be3677be) 14 | 15 | 16 | ---- 17 | 18 | ## 博客内容 19 | 20 | ![img_2299-qibot.cn.png](https://upload-images.jianshu.io/upload_images/855108-f5c9f906ea6467dd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 21 | 22 | 如图所示,现在很多App采用了类似下划线、方块等方式的验证码输入,直观美观!对于这种效果的实现方式,大概有以下几种方式: 23 | ##### 1.多个`UITextField`组成 24 | 这种方式好处是有光标闪烁、但是在处理删除和动画效果时,就会显得有点笨拙,OFO应该是这样实现的,要严格处理好每个`UITextField`的`FirstResponder`。 25 | ##### 2.一个`UITextField`组成,使用富文本 26 | 这个方式是可行的, 使用富文本设置每个字符的间距,允许编辑富文本,有光标闪烁,缺点应该也是不好处理动画效果。 27 | ##### 3.使用`UIView`绘制 28 | 这个是我在`GitHub`上看到的一个半成品Demo,利用一个`UIView`,使用`Quartz 2D`和`UIBezierPath`进行绘制文本和下划线,并处理输入事件和键盘事件,其实整体下来代码也不多,300行以内,但是需要较好的iOS绘制功底。 29 | ##### 4.一个`UITextField`和多个`UILabel` 30 | 这个是我接下来介绍的思路,这个思路的缺点应该是没有光标闪烁,其实也能伪实现,看是否必须要有这个需要了。这个思路比较简单,方便加入动画,纯粹下来就100多行,接下来看结构和代码: 31 | ![wx20180926_114423_2x-qibot.cn.png](https://upload-images.jianshu.io/upload_images/855108-1a2a12e3e3c0df88.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 32 | 33 | 新建一个`UIView`,初始化方法 34 | ``` 35 | - (instancetype)initWithCount:(NSInteger)count margin:(CGFloat)margin 36 | { 37 | if (self = [super init]) { 38 | self.itemCount = count; // itemCount是验证码的个数,比如6个 39 | self.itemMargin = margin; // itemMargin是每个Label之间的间距 40 | [self configTextField]; 41 | } 42 | return self; 43 | } 44 | ``` 45 | 添加内部子控件(演示) 46 | ``` 47 | - (void)configTextField 48 | { 49 | UITextField *textField = [[UITextField alloc] init]; 50 | textField.autocapitalizationType = UITextAutocapitalizationTypeNone; 51 | textField.keyboardType = UIKeyboardTypeNumberPad; 52 | [textField addTarget:self action:@selector(tfEditingChanged:) forControlEvents:(UIControlEventEditingChanged)]; 53 | 54 | // 小技巧:这个属性为YES,可以强制使用系统的数字键盘,缺点是重新输入时,会清空之前的内容 55 | // clearsOnBeginEditing 属性并不适用于 secureTextEntry = YES 时 56 | // textField.secureTextEntry = YES; 57 | 58 | [self addSubview:textField]; 59 | self.textField = textField; 60 | 61 | // 小技巧:通过textField上层覆盖一个maskView,可以去掉textField的长按事件 62 | UIButton *maskView = [UIButton new]; 63 | maskView.backgroundColor = [UIColor whiteColor]; 64 | [maskView addTarget:self action:@selector(clickMaskView) forControlEvents:(UIControlEventTouchUpInside)]; 65 | [self addSubview:maskView]; 66 | self.maskView = maskView; 67 | 68 | for (NSInteger i = 0; i < self.itemCount; i++) 69 | { 70 | UILabel *label = [UILabel new]; 71 | [self addSubview:label]; 72 | [self.labels addObject:label]; 73 | } 74 | 75 | for (NSInteger i = 0; i < self.itemCount; i++) 76 | { 77 | UIView *line = [UIView new]; 78 | [self.lines addObject:line]; 79 | } 80 | } 81 | ``` 82 | 这里可能对`maskView `有点费解,`maskView`主要是为了挡住下面的`UITextField `,使用类`UIButton `是为了挡住事件,因为如果使用类`UIView `,会将事件传递到`self`,进而影响到外面隐藏键盘的代码,你可以试试就知道了。 83 | 84 | ##### 主要处理业务逻辑的代码 85 | 86 | ``` 87 | #pragma mark - 编辑改变 88 | - (void)tfEditingChanged:(UITextField *)textField 89 | { 90 | if (textField.text.length > self.itemCount) { 91 | textField.text = [textField.text substringWithRange:NSMakeRange(0, self.itemCount)]; 92 | } 93 | 94 | for (int i = 0; i < self.itemCount; i++) 95 | { 96 | UILabel *label = [self.labels objectAtIndex:i]; 97 | 98 | if (i < textField.text.length) { 99 | label.text = [textField.text substringWithRange:NSMakeRange(i, 1)]; 100 | } else { 101 | label.text = nil; 102 | } 103 | } 104 | 105 | // 输入完毕后,自动隐藏键盘 106 | if (textField.text.length >= self.itemCount) { 107 | [textField resignFirstResponder]; 108 | } 109 | } 110 | ``` 111 | 这里没有使用`UITextField `的`delegate`,使用`UIControlEventEditingChanged`足以,但是如果你的需求能输入英文等其他字符,就需要实现`delegate`去限制。 112 | 113 | 至此,验证码输入的核心代码就***没有了***,是不是感觉这么少!!在此基础上,我用Demo实现了3个基本效果,如图所示: 114 | 115 | ![Screen.png](https://upload-images.jianshu.io/upload_images/855108-da661909e6280eb2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 116 | 117 | #### Github代码地址:[点我](https://github.com/HouWan/CodeTextDemo) 118 | 119 | #####小技巧Tip 120 | 当你不想让别人使用某个方法时,除了私有方法办法之外,还可以这么做: 121 | ``` 122 | - (instancetype)init UNAVAILABLE_ATTRIBUTE; 123 | + (instancetype)new UNAVAILABLE_ATTRIBUTE; 124 | ``` 125 | 126 | 127 | ## 有问题反馈 128 | 如果大家在使用过程中遇到其他问题,可以留言,我们共同解决。 129 | 130 | **PS**:最近我有跳槽的想法,有工作机会的老板,欢迎骚扰哦!北京呦! 131 | 132 | **END。** 133 | *我是小侯爷。* 134 | *在魔都艰苦奋斗,白天是上班族,晚上是知识服务工作者。* 135 | *如果读完觉得有收获的话,记得关注和点赞哦。* 136 | *非要打赏的话,我也是不会拒绝的。* 137 | -------------------------------------------------------------------------------- /Screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HouWan/CodeTextDemo/f00a31ecf30457d8d890c4bafaec911422b6d98b/Screen.png --------------------------------------------------------------------------------