├── .gitignore ├── Example ├── SecureTextFieldTest.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ │ └── shunkele.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── SecureTextFieldTest.xcscheme │ └── xcuserdata │ │ └── shunkele.xcuserdatad │ │ └── xcschemes │ │ ├── SecureTextFieldTest.xcscheme │ │ └── xcschememanagement.plist ├── SecureTextFieldTest │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj │ │ └── MainMenu.xib │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── SecureTextFieldTest-Info.plist │ ├── SecureTextFieldTest-Prefix.pch │ ├── en.lproj │ │ ├── Credits.rtf │ │ └── InfoPlist.strings │ └── main.m └── SecureTextFieldTestTests │ ├── SecureTextFieldTestTests-Info.plist │ ├── SecureTextFieldTestTests.m │ └── en.lproj │ └── InfoPlist.strings ├── KSPasswordField.h ├── KSPasswordField.m ├── LICENSE.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /.DS_Store 2 | # Xcode 3 | build/* 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | *.xcworkspace 13 | !default.xcworkspace 14 | xcuserdata 15 | profile 16 | *.moved-aside 17 | -------------------------------------------------------------------------------- /Example/SecureTextFieldTest.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 27FD664518911CE300A0D8BF /* KSPasswordField.m in Sources */ = {isa = PBXBuildFile; fileRef = 27FD664418911CE300A0D8BF /* KSPasswordField.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 11 | 6EC2F5A8188ECD4E00D87A69 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EC2F5A7188ECD4E00D87A69 /* Cocoa.framework */; }; 12 | 6EC2F5B2188ECD4E00D87A69 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6EC2F5B0188ECD4E00D87A69 /* InfoPlist.strings */; }; 13 | 6EC2F5B4188ECD4E00D87A69 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6EC2F5B3188ECD4E00D87A69 /* main.m */; }; 14 | 6EC2F5B8188ECD4E00D87A69 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 6EC2F5B6188ECD4E00D87A69 /* Credits.rtf */; }; 15 | 6EC2F5BB188ECD4E00D87A69 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6EC2F5BA188ECD4E00D87A69 /* AppDelegate.m */; }; 16 | 6EC2F5BE188ECD4E00D87A69 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6EC2F5BC188ECD4E00D87A69 /* MainMenu.xib */; }; 17 | 6EC2F5C0188ECD4E00D87A69 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6EC2F5BF188ECD4E00D87A69 /* Images.xcassets */; }; 18 | 6EC2F5C7188ECD4E00D87A69 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EC2F5C6188ECD4E00D87A69 /* XCTest.framework */; }; 19 | 6EC2F5C8188ECD4E00D87A69 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EC2F5A7188ECD4E00D87A69 /* Cocoa.framework */; }; 20 | 6EC2F5D0188ECD4E00D87A69 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6EC2F5CE188ECD4E00D87A69 /* InfoPlist.strings */; }; 21 | 6EC2F5D2188ECD4E00D87A69 /* SecureTextFieldTestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6EC2F5D1188ECD4E00D87A69 /* SecureTextFieldTestTests.m */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXContainerItemProxy section */ 25 | 6EC2F5C9188ECD4E00D87A69 /* PBXContainerItemProxy */ = { 26 | isa = PBXContainerItemProxy; 27 | containerPortal = 6EC2F59C188ECD4E00D87A69 /* Project object */; 28 | proxyType = 1; 29 | remoteGlobalIDString = 6EC2F5A3188ECD4E00D87A69; 30 | remoteInfo = SecureTextFieldTest; 31 | }; 32 | /* End PBXContainerItemProxy section */ 33 | 34 | /* Begin PBXFileReference section */ 35 | 27FD664318911CE300A0D8BF /* KSPasswordField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = KSPasswordField.h; path = ../../KSPasswordField.h; sourceTree = ""; }; 36 | 27FD664418911CE300A0D8BF /* KSPasswordField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = KSPasswordField.m; path = ../../KSPasswordField.m; sourceTree = ""; }; 37 | 6EC2F5A4188ECD4E00D87A69 /* SecureTextFieldTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SecureTextFieldTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | 6EC2F5A7188ECD4E00D87A69 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 39 | 6EC2F5AA188ECD4E00D87A69 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 40 | 6EC2F5AB188ECD4E00D87A69 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 41 | 6EC2F5AC188ECD4E00D87A69 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 42 | 6EC2F5AF188ECD4E00D87A69 /* SecureTextFieldTest-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SecureTextFieldTest-Info.plist"; sourceTree = ""; }; 43 | 6EC2F5B1188ECD4E00D87A69 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 44 | 6EC2F5B3188ECD4E00D87A69 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 45 | 6EC2F5B5188ECD4E00D87A69 /* SecureTextFieldTest-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SecureTextFieldTest-Prefix.pch"; sourceTree = ""; }; 46 | 6EC2F5B7188ECD4E00D87A69 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = ""; }; 47 | 6EC2F5B9188ECD4E00D87A69 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 48 | 6EC2F5BA188ECD4E00D87A69 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 49 | 6EC2F5BD188ECD4E00D87A69 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 50 | 6EC2F5BF188ECD4E00D87A69 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 51 | 6EC2F5C5188ECD4E00D87A69 /* SecureTextFieldTestTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SecureTextFieldTestTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | 6EC2F5C6188ECD4E00D87A69 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 53 | 6EC2F5CD188ECD4E00D87A69 /* SecureTextFieldTestTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SecureTextFieldTestTests-Info.plist"; sourceTree = ""; }; 54 | 6EC2F5CF188ECD4E00D87A69 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 55 | 6EC2F5D1188ECD4E00D87A69 /* SecureTextFieldTestTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SecureTextFieldTestTests.m; sourceTree = ""; }; 56 | /* End PBXFileReference section */ 57 | 58 | /* Begin PBXFrameworksBuildPhase section */ 59 | 6EC2F5A1188ECD4E00D87A69 /* Frameworks */ = { 60 | isa = PBXFrameworksBuildPhase; 61 | buildActionMask = 2147483647; 62 | files = ( 63 | 6EC2F5A8188ECD4E00D87A69 /* Cocoa.framework in Frameworks */, 64 | ); 65 | runOnlyForDeploymentPostprocessing = 0; 66 | }; 67 | 6EC2F5C2188ECD4E00D87A69 /* Frameworks */ = { 68 | isa = PBXFrameworksBuildPhase; 69 | buildActionMask = 2147483647; 70 | files = ( 71 | 6EC2F5C8188ECD4E00D87A69 /* Cocoa.framework in Frameworks */, 72 | 6EC2F5C7188ECD4E00D87A69 /* XCTest.framework in Frameworks */, 73 | ); 74 | runOnlyForDeploymentPostprocessing = 0; 75 | }; 76 | /* End PBXFrameworksBuildPhase section */ 77 | 78 | /* Begin PBXGroup section */ 79 | 6EC2F59B188ECD4E00D87A69 = { 80 | isa = PBXGroup; 81 | children = ( 82 | 6EC2F5AD188ECD4E00D87A69 /* SecureTextFieldTest */, 83 | 6EC2F5CB188ECD4E00D87A69 /* SecureTextFieldTestTests */, 84 | 6EC2F5A6188ECD4E00D87A69 /* Frameworks */, 85 | 6EC2F5A5188ECD4E00D87A69 /* Products */, 86 | ); 87 | sourceTree = ""; 88 | }; 89 | 6EC2F5A5188ECD4E00D87A69 /* Products */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 6EC2F5A4188ECD4E00D87A69 /* SecureTextFieldTest.app */, 93 | 6EC2F5C5188ECD4E00D87A69 /* SecureTextFieldTestTests.xctest */, 94 | ); 95 | name = Products; 96 | sourceTree = ""; 97 | }; 98 | 6EC2F5A6188ECD4E00D87A69 /* Frameworks */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | 6EC2F5A7188ECD4E00D87A69 /* Cocoa.framework */, 102 | 6EC2F5C6188ECD4E00D87A69 /* XCTest.framework */, 103 | 6EC2F5A9188ECD4E00D87A69 /* Other Frameworks */, 104 | ); 105 | name = Frameworks; 106 | sourceTree = ""; 107 | }; 108 | 6EC2F5A9188ECD4E00D87A69 /* Other Frameworks */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 6EC2F5AA188ECD4E00D87A69 /* AppKit.framework */, 112 | 6EC2F5AB188ECD4E00D87A69 /* CoreData.framework */, 113 | 6EC2F5AC188ECD4E00D87A69 /* Foundation.framework */, 114 | ); 115 | name = "Other Frameworks"; 116 | sourceTree = ""; 117 | }; 118 | 6EC2F5AD188ECD4E00D87A69 /* SecureTextFieldTest */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 6EC2F5B9188ECD4E00D87A69 /* AppDelegate.h */, 122 | 6EC2F5BA188ECD4E00D87A69 /* AppDelegate.m */, 123 | 27FD664318911CE300A0D8BF /* KSPasswordField.h */, 124 | 27FD664418911CE300A0D8BF /* KSPasswordField.m */, 125 | 6EC2F5BC188ECD4E00D87A69 /* MainMenu.xib */, 126 | 6EC2F5BF188ECD4E00D87A69 /* Images.xcassets */, 127 | 6EC2F5AE188ECD4E00D87A69 /* Supporting Files */, 128 | ); 129 | path = SecureTextFieldTest; 130 | sourceTree = ""; 131 | }; 132 | 6EC2F5AE188ECD4E00D87A69 /* Supporting Files */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | 6EC2F5AF188ECD4E00D87A69 /* SecureTextFieldTest-Info.plist */, 136 | 6EC2F5B0188ECD4E00D87A69 /* InfoPlist.strings */, 137 | 6EC2F5B3188ECD4E00D87A69 /* main.m */, 138 | 6EC2F5B5188ECD4E00D87A69 /* SecureTextFieldTest-Prefix.pch */, 139 | 6EC2F5B6188ECD4E00D87A69 /* Credits.rtf */, 140 | ); 141 | name = "Supporting Files"; 142 | sourceTree = ""; 143 | }; 144 | 6EC2F5CB188ECD4E00D87A69 /* SecureTextFieldTestTests */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | 6EC2F5D1188ECD4E00D87A69 /* SecureTextFieldTestTests.m */, 148 | 6EC2F5CC188ECD4E00D87A69 /* Supporting Files */, 149 | ); 150 | path = SecureTextFieldTestTests; 151 | sourceTree = ""; 152 | }; 153 | 6EC2F5CC188ECD4E00D87A69 /* Supporting Files */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | 6EC2F5CD188ECD4E00D87A69 /* SecureTextFieldTestTests-Info.plist */, 157 | 6EC2F5CE188ECD4E00D87A69 /* InfoPlist.strings */, 158 | ); 159 | name = "Supporting Files"; 160 | sourceTree = ""; 161 | }; 162 | /* End PBXGroup section */ 163 | 164 | /* Begin PBXNativeTarget section */ 165 | 6EC2F5A3188ECD4E00D87A69 /* SecureTextFieldTest */ = { 166 | isa = PBXNativeTarget; 167 | buildConfigurationList = 6EC2F5D5188ECD4E00D87A69 /* Build configuration list for PBXNativeTarget "SecureTextFieldTest" */; 168 | buildPhases = ( 169 | 6EC2F5A0188ECD4E00D87A69 /* Sources */, 170 | 6EC2F5A1188ECD4E00D87A69 /* Frameworks */, 171 | 6EC2F5A2188ECD4E00D87A69 /* Resources */, 172 | ); 173 | buildRules = ( 174 | ); 175 | dependencies = ( 176 | ); 177 | name = SecureTextFieldTest; 178 | productName = SecureTextFieldTest; 179 | productReference = 6EC2F5A4188ECD4E00D87A69 /* SecureTextFieldTest.app */; 180 | productType = "com.apple.product-type.application"; 181 | }; 182 | 6EC2F5C4188ECD4E00D87A69 /* SecureTextFieldTestTests */ = { 183 | isa = PBXNativeTarget; 184 | buildConfigurationList = 6EC2F5D8188ECD4E00D87A69 /* Build configuration list for PBXNativeTarget "SecureTextFieldTestTests" */; 185 | buildPhases = ( 186 | 6EC2F5C1188ECD4E00D87A69 /* Sources */, 187 | 6EC2F5C2188ECD4E00D87A69 /* Frameworks */, 188 | 6EC2F5C3188ECD4E00D87A69 /* Resources */, 189 | ); 190 | buildRules = ( 191 | ); 192 | dependencies = ( 193 | 6EC2F5CA188ECD4E00D87A69 /* PBXTargetDependency */, 194 | ); 195 | name = SecureTextFieldTestTests; 196 | productName = SecureTextFieldTestTests; 197 | productReference = 6EC2F5C5188ECD4E00D87A69 /* SecureTextFieldTestTests.xctest */; 198 | productType = "com.apple.product-type.bundle.unit-test"; 199 | }; 200 | /* End PBXNativeTarget section */ 201 | 202 | /* Begin PBXProject section */ 203 | 6EC2F59C188ECD4E00D87A69 /* Project object */ = { 204 | isa = PBXProject; 205 | attributes = { 206 | LastUpgradeCheck = 0500; 207 | ORGANIZATIONNAME = HSR; 208 | TargetAttributes = { 209 | 6EC2F5C4188ECD4E00D87A69 = { 210 | TestTargetID = 6EC2F5A3188ECD4E00D87A69; 211 | }; 212 | }; 213 | }; 214 | buildConfigurationList = 6EC2F59F188ECD4E00D87A69 /* Build configuration list for PBXProject "SecureTextFieldTest" */; 215 | compatibilityVersion = "Xcode 3.2"; 216 | developmentRegion = English; 217 | hasScannedForEncodings = 0; 218 | knownRegions = ( 219 | en, 220 | Base, 221 | ); 222 | mainGroup = 6EC2F59B188ECD4E00D87A69; 223 | productRefGroup = 6EC2F5A5188ECD4E00D87A69 /* Products */; 224 | projectDirPath = ""; 225 | projectRoot = ""; 226 | targets = ( 227 | 6EC2F5A3188ECD4E00D87A69 /* SecureTextFieldTest */, 228 | 6EC2F5C4188ECD4E00D87A69 /* SecureTextFieldTestTests */, 229 | ); 230 | }; 231 | /* End PBXProject section */ 232 | 233 | /* Begin PBXResourcesBuildPhase section */ 234 | 6EC2F5A2188ECD4E00D87A69 /* Resources */ = { 235 | isa = PBXResourcesBuildPhase; 236 | buildActionMask = 2147483647; 237 | files = ( 238 | 6EC2F5B2188ECD4E00D87A69 /* InfoPlist.strings in Resources */, 239 | 6EC2F5C0188ECD4E00D87A69 /* Images.xcassets in Resources */, 240 | 6EC2F5B8188ECD4E00D87A69 /* Credits.rtf in Resources */, 241 | 6EC2F5BE188ECD4E00D87A69 /* MainMenu.xib in Resources */, 242 | ); 243 | runOnlyForDeploymentPostprocessing = 0; 244 | }; 245 | 6EC2F5C3188ECD4E00D87A69 /* Resources */ = { 246 | isa = PBXResourcesBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | 6EC2F5D0188ECD4E00D87A69 /* InfoPlist.strings in Resources */, 250 | ); 251 | runOnlyForDeploymentPostprocessing = 0; 252 | }; 253 | /* End PBXResourcesBuildPhase section */ 254 | 255 | /* Begin PBXSourcesBuildPhase section */ 256 | 6EC2F5A0188ECD4E00D87A69 /* Sources */ = { 257 | isa = PBXSourcesBuildPhase; 258 | buildActionMask = 2147483647; 259 | files = ( 260 | 6EC2F5BB188ECD4E00D87A69 /* AppDelegate.m in Sources */, 261 | 6EC2F5B4188ECD4E00D87A69 /* main.m in Sources */, 262 | 27FD664518911CE300A0D8BF /* KSPasswordField.m in Sources */, 263 | ); 264 | runOnlyForDeploymentPostprocessing = 0; 265 | }; 266 | 6EC2F5C1188ECD4E00D87A69 /* Sources */ = { 267 | isa = PBXSourcesBuildPhase; 268 | buildActionMask = 2147483647; 269 | files = ( 270 | 6EC2F5D2188ECD4E00D87A69 /* SecureTextFieldTestTests.m in Sources */, 271 | ); 272 | runOnlyForDeploymentPostprocessing = 0; 273 | }; 274 | /* End PBXSourcesBuildPhase section */ 275 | 276 | /* Begin PBXTargetDependency section */ 277 | 6EC2F5CA188ECD4E00D87A69 /* PBXTargetDependency */ = { 278 | isa = PBXTargetDependency; 279 | target = 6EC2F5A3188ECD4E00D87A69 /* SecureTextFieldTest */; 280 | targetProxy = 6EC2F5C9188ECD4E00D87A69 /* PBXContainerItemProxy */; 281 | }; 282 | /* End PBXTargetDependency section */ 283 | 284 | /* Begin PBXVariantGroup section */ 285 | 6EC2F5B0188ECD4E00D87A69 /* InfoPlist.strings */ = { 286 | isa = PBXVariantGroup; 287 | children = ( 288 | 6EC2F5B1188ECD4E00D87A69 /* en */, 289 | ); 290 | name = InfoPlist.strings; 291 | sourceTree = ""; 292 | }; 293 | 6EC2F5B6188ECD4E00D87A69 /* Credits.rtf */ = { 294 | isa = PBXVariantGroup; 295 | children = ( 296 | 6EC2F5B7188ECD4E00D87A69 /* en */, 297 | ); 298 | name = Credits.rtf; 299 | sourceTree = ""; 300 | }; 301 | 6EC2F5BC188ECD4E00D87A69 /* MainMenu.xib */ = { 302 | isa = PBXVariantGroup; 303 | children = ( 304 | 6EC2F5BD188ECD4E00D87A69 /* Base */, 305 | ); 306 | name = MainMenu.xib; 307 | sourceTree = ""; 308 | }; 309 | 6EC2F5CE188ECD4E00D87A69 /* InfoPlist.strings */ = { 310 | isa = PBXVariantGroup; 311 | children = ( 312 | 6EC2F5CF188ECD4E00D87A69 /* en */, 313 | ); 314 | name = InfoPlist.strings; 315 | sourceTree = ""; 316 | }; 317 | /* End PBXVariantGroup section */ 318 | 319 | /* Begin XCBuildConfiguration section */ 320 | 6EC2F5D3188ECD4E00D87A69 /* Debug */ = { 321 | isa = XCBuildConfiguration; 322 | buildSettings = { 323 | ALWAYS_SEARCH_USER_PATHS = NO; 324 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 325 | CLANG_CXX_LIBRARY = "libc++"; 326 | CLANG_ENABLE_OBJC_ARC = YES; 327 | CLANG_WARN_BOOL_CONVERSION = YES; 328 | CLANG_WARN_CONSTANT_CONVERSION = YES; 329 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 330 | CLANG_WARN_EMPTY_BODY = YES; 331 | CLANG_WARN_ENUM_CONVERSION = YES; 332 | CLANG_WARN_INT_CONVERSION = YES; 333 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 334 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 335 | COPY_PHASE_STRIP = NO; 336 | GCC_C_LANGUAGE_STANDARD = gnu99; 337 | GCC_DYNAMIC_NO_PIC = NO; 338 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 339 | GCC_OPTIMIZATION_LEVEL = 0; 340 | GCC_PREPROCESSOR_DEFINITIONS = ( 341 | "DEBUG=1", 342 | "$(inherited)", 343 | ); 344 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 345 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 346 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 347 | GCC_WARN_UNDECLARED_SELECTOR = YES; 348 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 349 | GCC_WARN_UNUSED_FUNCTION = YES; 350 | GCC_WARN_UNUSED_VARIABLE = YES; 351 | MACOSX_DEPLOYMENT_TARGET = 10.9; 352 | ONLY_ACTIVE_ARCH = YES; 353 | SDKROOT = macosx; 354 | }; 355 | name = Debug; 356 | }; 357 | 6EC2F5D4188ECD4E00D87A69 /* Release */ = { 358 | isa = XCBuildConfiguration; 359 | buildSettings = { 360 | ALWAYS_SEARCH_USER_PATHS = NO; 361 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 362 | CLANG_CXX_LIBRARY = "libc++"; 363 | CLANG_ENABLE_OBJC_ARC = YES; 364 | CLANG_WARN_BOOL_CONVERSION = YES; 365 | CLANG_WARN_CONSTANT_CONVERSION = YES; 366 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 367 | CLANG_WARN_EMPTY_BODY = YES; 368 | CLANG_WARN_ENUM_CONVERSION = YES; 369 | CLANG_WARN_INT_CONVERSION = YES; 370 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 371 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 372 | COPY_PHASE_STRIP = YES; 373 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 374 | ENABLE_NS_ASSERTIONS = NO; 375 | GCC_C_LANGUAGE_STANDARD = gnu99; 376 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 377 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 378 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 379 | GCC_WARN_UNDECLARED_SELECTOR = YES; 380 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 381 | GCC_WARN_UNUSED_FUNCTION = YES; 382 | GCC_WARN_UNUSED_VARIABLE = YES; 383 | MACOSX_DEPLOYMENT_TARGET = 10.9; 384 | SDKROOT = macosx; 385 | }; 386 | name = Release; 387 | }; 388 | 6EC2F5D6188ECD4E00D87A69 /* Debug */ = { 389 | isa = XCBuildConfiguration; 390 | buildSettings = { 391 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 392 | COMBINE_HIDPI_IMAGES = YES; 393 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 394 | GCC_PREFIX_HEADER = "SecureTextFieldTest/SecureTextFieldTest-Prefix.pch"; 395 | INFOPLIST_FILE = "SecureTextFieldTest/SecureTextFieldTest-Info.plist"; 396 | PRODUCT_NAME = "$(TARGET_NAME)"; 397 | WRAPPER_EXTENSION = app; 398 | }; 399 | name = Debug; 400 | }; 401 | 6EC2F5D7188ECD4E00D87A69 /* Release */ = { 402 | isa = XCBuildConfiguration; 403 | buildSettings = { 404 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 405 | COMBINE_HIDPI_IMAGES = YES; 406 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 407 | GCC_PREFIX_HEADER = "SecureTextFieldTest/SecureTextFieldTest-Prefix.pch"; 408 | INFOPLIST_FILE = "SecureTextFieldTest/SecureTextFieldTest-Info.plist"; 409 | PRODUCT_NAME = "$(TARGET_NAME)"; 410 | WRAPPER_EXTENSION = app; 411 | }; 412 | name = Release; 413 | }; 414 | 6EC2F5D9188ECD4E00D87A69 /* Debug */ = { 415 | isa = XCBuildConfiguration; 416 | buildSettings = { 417 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/SecureTextFieldTest.app/Contents/MacOS/SecureTextFieldTest"; 418 | COMBINE_HIDPI_IMAGES = YES; 419 | FRAMEWORK_SEARCH_PATHS = ( 420 | "$(DEVELOPER_FRAMEWORKS_DIR)", 421 | "$(inherited)", 422 | ); 423 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 424 | GCC_PREFIX_HEADER = "SecureTextFieldTest/SecureTextFieldTest-Prefix.pch"; 425 | GCC_PREPROCESSOR_DEFINITIONS = ( 426 | "DEBUG=1", 427 | "$(inherited)", 428 | ); 429 | INFOPLIST_FILE = "SecureTextFieldTestTests/SecureTextFieldTestTests-Info.plist"; 430 | PRODUCT_NAME = "$(TARGET_NAME)"; 431 | TEST_HOST = "$(BUNDLE_LOADER)"; 432 | WRAPPER_EXTENSION = xctest; 433 | }; 434 | name = Debug; 435 | }; 436 | 6EC2F5DA188ECD4E00D87A69 /* Release */ = { 437 | isa = XCBuildConfiguration; 438 | buildSettings = { 439 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/SecureTextFieldTest.app/Contents/MacOS/SecureTextFieldTest"; 440 | COMBINE_HIDPI_IMAGES = YES; 441 | FRAMEWORK_SEARCH_PATHS = ( 442 | "$(DEVELOPER_FRAMEWORKS_DIR)", 443 | "$(inherited)", 444 | ); 445 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 446 | GCC_PREFIX_HEADER = "SecureTextFieldTest/SecureTextFieldTest-Prefix.pch"; 447 | INFOPLIST_FILE = "SecureTextFieldTestTests/SecureTextFieldTestTests-Info.plist"; 448 | PRODUCT_NAME = "$(TARGET_NAME)"; 449 | TEST_HOST = "$(BUNDLE_LOADER)"; 450 | WRAPPER_EXTENSION = xctest; 451 | }; 452 | name = Release; 453 | }; 454 | /* End XCBuildConfiguration section */ 455 | 456 | /* Begin XCConfigurationList section */ 457 | 6EC2F59F188ECD4E00D87A69 /* Build configuration list for PBXProject "SecureTextFieldTest" */ = { 458 | isa = XCConfigurationList; 459 | buildConfigurations = ( 460 | 6EC2F5D3188ECD4E00D87A69 /* Debug */, 461 | 6EC2F5D4188ECD4E00D87A69 /* Release */, 462 | ); 463 | defaultConfigurationIsVisible = 0; 464 | defaultConfigurationName = Release; 465 | }; 466 | 6EC2F5D5188ECD4E00D87A69 /* Build configuration list for PBXNativeTarget "SecureTextFieldTest" */ = { 467 | isa = XCConfigurationList; 468 | buildConfigurations = ( 469 | 6EC2F5D6188ECD4E00D87A69 /* Debug */, 470 | 6EC2F5D7188ECD4E00D87A69 /* Release */, 471 | ); 472 | defaultConfigurationIsVisible = 0; 473 | defaultConfigurationName = Release; 474 | }; 475 | 6EC2F5D8188ECD4E00D87A69 /* Build configuration list for PBXNativeTarget "SecureTextFieldTestTests" */ = { 476 | isa = XCConfigurationList; 477 | buildConfigurations = ( 478 | 6EC2F5D9188ECD4E00D87A69 /* Debug */, 479 | 6EC2F5DA188ECD4E00D87A69 /* Release */, 480 | ); 481 | defaultConfigurationIsVisible = 0; 482 | defaultConfigurationName = Release; 483 | }; 484 | /* End XCConfigurationList section */ 485 | }; 486 | rootObject = 6EC2F59C188ECD4E00D87A69 /* Project object */; 487 | } 488 | -------------------------------------------------------------------------------- /Example/SecureTextFieldTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/SecureTextFieldTest.xcodeproj/project.xcworkspace/xcuserdata/shunkele.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karelia/SecurityInterface/62bef42a6727f38c9d0a6edb5edee8a2d818a321/Example/SecureTextFieldTest.xcodeproj/project.xcworkspace/xcuserdata/shunkele.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Example/SecureTextFieldTest.xcodeproj/xcshareddata/xcschemes/SecureTextFieldTest.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /Example/SecureTextFieldTest.xcodeproj/xcuserdata/shunkele.xcuserdatad/xcschemes/SecureTextFieldTest.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /Example/SecureTextFieldTest.xcodeproj/xcuserdata/shunkele.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SecureTextFieldTest.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 6EC2F5A3188ECD4E00D87A69 16 | 17 | primary 18 | 19 | 20 | 6EC2F5C4188ECD4E00D87A69 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Example/SecureTextFieldTest/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // SecureTextFieldTest 4 | // 5 | // Created by Sebastian Hunkeler on 21/01/14. 6 | // Copyright (c) 2014 HSR. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "KSPasswordField.h" 11 | 12 | @interface AppDelegate : NSObject 13 | 14 | @property (assign) IBOutlet NSWindow *window; 15 | @property (copy) NSString *password1; 16 | @property (copy) NSString *password2; 17 | @property (nonatomic, assign) BOOL showPassword; 18 | 19 | @property (weak) IBOutlet KSPasswordField *oPassword1Field; 20 | @property (weak) IBOutlet KSPasswordField *oPassword2Field; 21 | @property (weak) IBOutlet NSTextField *oPassword1Prompt; 22 | @property (weak) IBOutlet NSTextField *oPassword2Prompt; 23 | @property (weak) IBOutlet NSButton *oShowPasswordButton; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /Example/SecureTextFieldTest/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // SecureTextFieldTest 4 | // 5 | // Created by Sebastian Hunkeler on 21/01/14. 6 | // Copyright (c) 2014 HSR. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "KSPasswordField.h" 11 | 12 | @implementation AppDelegate 13 | 14 | - (void)setShowPassword:(BOOL)showPassword 15 | { 16 | _showPassword = showPassword; 17 | 18 | if (!self.showPassword) // we don't want first responder of second field 19 | { 20 | [_oPassword2Field setStringValue:@""]; // clear out, so they don't have to match 21 | [_oPassword2Field setMatching:HIDE_MATCH]; // don't show any indication of matching, either 22 | [self.window makeFirstResponder:_oPassword1Field]; 23 | } 24 | 25 | [_oPassword1Field setShowsText:self.showPassword]; 26 | 27 | [_oPassword2Field setHidden:self.showPassword]; 28 | [_oPassword2Prompt setHidden:self.showPassword]; 29 | 30 | // Calculate the height difference of the window with and without the second password field 31 | NSTextField *fieldAbove = self.showPassword ? _oPassword1Field : _oPassword2Field ; 32 | NSRect checkboxFrame = [_oShowPasswordButton frame]; 33 | checkboxFrame.origin.y = NSMinY([fieldAbove frame]) - 8 - NSHeight(checkboxFrame); 34 | [[_oShowPasswordButton animator] setFrame:checkboxFrame]; 35 | } 36 | 37 | - (void)controlTextDidChange:(NSNotification *)aNotification 38 | { 39 | KSPasswordField *obj = (KSPasswordField *)[aNotification object]; 40 | // Notification is synthesized for the password fields. Now synthesize a binding update! 41 | if (obj == _oPassword1Field) 42 | { 43 | // Simulate binding to password1 44 | NSText *fieldEditor = [[aNotification userInfo] objectForKey:@"NSFieldEditor"]; 45 | NSString *string = [fieldEditor string]; 46 | self.password1 = string; 47 | [_oPassword2Field setStringValue:@""]; // clear out second password whenever password 1 changes 48 | [_oPassword2Field setMatching:HIDE_MATCH]; // don't show any indication of matching, either 49 | } 50 | else if (obj == _oPassword2Field) 51 | { 52 | // Simulate binding to password1 53 | NSText *fieldEditor = [[aNotification userInfo] objectForKey:@"NSFieldEditor"]; 54 | NSString *string = [fieldEditor string]; 55 | self.password2 = string; 56 | 57 | NSString *password1 = [_oPassword1Field stringValue]; 58 | 59 | if ([@"" isEqualToString:string]) 60 | { 61 | [obj setMatching:HIDE_MATCH]; 62 | } 63 | else if ([password1 isEqualToString:string] && [string length] >= 8) 64 | { 65 | [obj setMatching:FULL_MATCH]; 66 | } 67 | else if ([password1 hasPrefix:string] && [string length] >= 8) 68 | { 69 | [obj setMatching:PARTIAL_MATCH]; 70 | } 71 | else 72 | { 73 | [obj setMatching:DOESNT_MATCH]; 74 | } 75 | [obj setNeedsDisplay]; 76 | } 77 | } 78 | 79 | @end 80 | -------------------------------------------------------------------------------- /Example/SecureTextFieldTest/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 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 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 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 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | Default 522 | 523 | 524 | 525 | 526 | 527 | 528 | Left to Right 529 | 530 | 531 | 532 | 533 | 534 | 535 | Right to Left 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | Default 547 | 548 | 549 | 550 | 551 | 552 | 553 | Left to Right 554 | 555 | 556 | 557 | 558 | 559 | 560 | Right to Left 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | NSAllRomanInputSourcesLocaleIdentifier 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | NSAllRomanInputSourcesLocaleIdentifier 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | -------------------------------------------------------------------------------- /Example/SecureTextFieldTest/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Example/SecureTextFieldTest/SecureTextFieldTest-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | ch.hsr.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | ${MACOSX_DEPLOYMENT_TARGET} 27 | NSHumanReadableCopyright 28 | Copyright © 2014 HSR. All rights reserved. 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /Example/SecureTextFieldTest/SecureTextFieldTest-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #endif 10 | -------------------------------------------------------------------------------- /Example/SecureTextFieldTest/en.lproj/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;} 2 | {\colortbl;\red255\green255\blue255;} 3 | \paperw9840\paperh8400 4 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural 5 | 6 | \f0\b\fs24 \cf0 Engineering: 7 | \b0 \ 8 | Some people\ 9 | \ 10 | 11 | \b Human Interface Design: 12 | \b0 \ 13 | Some other people\ 14 | \ 15 | 16 | \b Testing: 17 | \b0 \ 18 | Hopefully not nobody\ 19 | \ 20 | 21 | \b Documentation: 22 | \b0 \ 23 | Whoever\ 24 | \ 25 | 26 | \b With special thanks to: 27 | \b0 \ 28 | Mom\ 29 | } 30 | -------------------------------------------------------------------------------- /Example/SecureTextFieldTest/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Example/SecureTextFieldTest/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // SecureTextFieldTest 4 | // 5 | // Created by Sebastian Hunkeler on 21/01/14. 6 | // Copyright (c) 2014 HSR. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, const char * argv[]) 12 | { 13 | return NSApplicationMain(argc, argv); 14 | } 15 | -------------------------------------------------------------------------------- /Example/SecureTextFieldTestTests/SecureTextFieldTestTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ch.hsr.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/SecureTextFieldTestTests/SecureTextFieldTestTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // SecureTextFieldTestTests.m 3 | // SecureTextFieldTestTests 4 | // 5 | // Created by Sebastian Hunkeler on 21/01/14. 6 | // Copyright (c) 2014 HSR. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SecureTextFieldTestTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation SecureTextFieldTestTests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown 24 | { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | [super tearDown]; 27 | } 28 | 29 | - (void)testExample 30 | { 31 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /Example/SecureTextFieldTestTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /KSPasswordField.h: -------------------------------------------------------------------------------- 1 | // 2 | // KSPasswordField.h 3 | // Sandvox 4 | // 5 | // Created by Mike Abdullah on 28/04/2012. 6 | // Copyright (c) 2012-2014 Karelia Software. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import 28 | 29 | typedef NS_ENUM(NSInteger, MATCHING) { HIDE_MATCH = 0, DOESNT_MATCH, PARTIAL_MATCH, FULL_MATCH }; 30 | 31 | typedef NS_ENUM(NSInteger, PasswordMeter) { 32 | MeterEmpty = 0, // Disallow, very short; don't show strength yet 33 | MeterCommon, // Disallow, too common 34 | MeterShort, // Disallow, too short 35 | MeterInsecure, // Allow, but warn it's insecure (a.k.a. very weak) 36 | MeterWeak, // Allow, but weak 37 | MeterFair, 38 | MeterStrong 39 | }; 40 | 41 | @interface KSPasswordField : NSSecureTextField 42 | { 43 | @private 44 | BOOL _showsText; 45 | BOOL _becomesFirstResponderWhenToggled; 46 | BOOL _showStrength; 47 | BOOL _showMatchIndicator; 48 | float _strength; 49 | NSUInteger _length; 50 | MATCHING _matching; 51 | PasswordMeter _passwordMeter; 52 | NSString *_descriptionOfStrength; 53 | 54 | } 55 | 56 | 57 | @property(nonatomic) BOOL showStrength; 58 | @property(nonatomic) BOOL showMatchIndicator; 59 | @property(nonatomic) PasswordMeter passwordMeter; 60 | 61 | /** 62 | Whether to display the password as plain text or not. 63 | */ 64 | @property(nonatomic) BOOL showsText; 65 | 66 | @property (nonatomic, assign) float strength; 67 | @property (nonatomic, assign) NSUInteger length; 68 | @property (nonatomic, assign) MATCHING matching; 69 | @property (nonatomic, copy) NSString *descriptionOfStrength; 70 | 71 | /** 72 | Whether the receiver becomes first responder whenever `.showsText` changes. 73 | 74 | The default is `YES`, which means that whenever the password is shown or hidden, 75 | the field will try to become the first responder, ready for the user to type 76 | into it. Set to `NO` if you want to perform your own management of the first 77 | responder instead. 78 | */ 79 | @property(nonatomic) BOOL becomesFirstResponderWhenToggled; 80 | 81 | /** 82 | Sets `.showsText` to `YES`. 83 | 84 | Convenient for connecting up a "Show Password" button. 85 | */ 86 | - (IBAction)showText:(id)sender; 87 | 88 | /** 89 | Sets `.showsText` to `NO`. 90 | 91 | Convenient for connecting up a "Hide Password" button. 92 | */ 93 | - (IBAction)secureText:(id)sender; 94 | 95 | /** 96 | Toggles the value of `.showsText`. 97 | 98 | Generally connected up as the action of a "Show Password" checkbox, or some 99 | kind of toggle button. 100 | */ 101 | - (IBAction)toggleTextShown:(id)sender; 102 | 103 | /** 104 | Performs cleanup of a potential password by limiting to a single line, and trimming whitespace. 105 | 106 | Called by `KSPasswordField` when the user pastes or drags in a string. 107 | 108 | @param string The password string to be cleaned up. 109 | */ 110 | - (NSString*)cleanedPasswordForString:(NSString*)string; 111 | 112 | 113 | - (void) setStrength:(float)strength length:(NSUInteger)length; 114 | 115 | - (BOOL) strongEnough; 116 | 117 | @end 118 | 119 | 120 | @interface KSPasswordTextFieldCell : NSTextFieldCell 121 | @end 122 | 123 | @interface KSPasswordSecureTextFieldCell : NSSecureTextFieldCell 124 | @end 125 | -------------------------------------------------------------------------------- /KSPasswordField.m: -------------------------------------------------------------------------------- 1 | // 2 | // KSPasswordField.m 3 | // Sandvox 4 | // 5 | // Created by Mike Abdullah on 28/04/2012. 6 | // Copyright (c) 2012-2014 Karelia Software. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import "KSPasswordField.h" 28 | #import "NSData+Karelia.h" 29 | 30 | #define YOFFSET 2 31 | #define STRENGTH_INSET 4 32 | 33 | 34 | static NSArray *sStrengthDescriptions = nil; 35 | static NSSet *sPasswordBlacklist = nil; // Worst ones 36 | static NSUInteger sMaxStrengthDescriptionWidth = 0; 37 | 38 | NSString *MyControlDidBecomeFirstResponderNotification = @"MyControlDidBecomeFirstResponderNotification"; 39 | 40 | void drawMeter(NSRect bounds, float strength, NSUInteger width) 41 | { 42 | NSColor *red = [NSColor colorWithCalibratedHue:1.0 saturation:1.000 brightness:0.9 alpha:1.000]; 43 | NSColor *yellow = [NSColor colorWithCalibratedHue:0.130 saturation:1.0 brightness:1.0 alpha:1.000]; 44 | NSColor *green = [NSColor colorWithCalibratedHue:0.283 saturation:0.7 brightness:0.9 alpha:1.000]; 45 | NSColor *gray = [NSColor colorWithCalibratedHue:0.672 saturation:0.06 brightness:0.9 alpha:1.000]; // slight blue tinge 46 | 47 | CGFloat endRed, startYellow, endYellow, startGreen; 48 | 49 | // https://www.desmos.com/calculator/xqjscbu76v 50 | 51 | // We want all red up to 1.0 until about strength 0.3, then start bringing in yellow. 52 | // At strength 1.0, red is just until 0.05 53 | endRed = MAX(0.0, MIN(1.0, 0.05 - 1.4 * (strength-1))); 54 | startYellow = MIN(1.0, endRed + 0.05); // next color comes 5% after color change 55 | 56 | // We want to have yellow until about 0.7, then start bringing in green. 57 | // At strength 1.0, yellow is just until 0.15 58 | endYellow = MAX(0.0, MIN(1.0, 0.15 - 2.7 * (strength-1))); 59 | startGreen = MIN(1.0, endYellow + 0.05); // next color comes 5% after color change 60 | 61 | // NSLog(@"Strength %.2f gives us colors at %.2f and %.2f", strength, endRed, endYellow); 62 | NSGradient *gradient = [[NSGradient alloc] initWithColorsAndLocations: 63 | red, 0.0, 64 | red, endRed, 65 | yellow, startYellow, 66 | yellow, endYellow, 67 | green, startGreen, 68 | green, 1.0, nil]; 69 | 70 | NSRect rectToUse = bounds; 71 | rectToUse = NSInsetRect(rectToUse, 1.0, 1.0); 72 | rectToUse.size.height = 4; 73 | 74 | [NSGraphicsContext saveGraphicsState]; 75 | [[NSGraphicsContext currentContext] setCompositingOperation:NSCompositePlusDarker]; 76 | 77 | // Gray background 78 | [gray set]; 79 | [NSBezierPath fillRect:rectToUse]; 80 | 81 | // Now the show the meter 82 | CGFloat cappedWidth = MIN(rectToUse.size.width, width); 83 | rectToUse.size.width = cappedWidth; 84 | [gradient drawInRect:rectToUse angle:0.0]; // not enough room for rounded rect 85 | [gradient release]; 86 | 87 | [NSGraphicsContext restoreGraphicsState]; 88 | } 89 | 90 | void drawDescriptionOfStrength(NSRect cellFrame, float strength, NSString *descriptionOfStrength) 91 | { 92 | NSColor *red = [NSColor colorWithCalibratedHue:1.0 saturation:1.000 brightness:0.7 alpha:1.000]; 93 | NSColor *yellow = [NSColor colorWithCalibratedHue:0.130 saturation:1.0 brightness:0.7 alpha:1.000]; // Less bright than meter color! 94 | NSColor *green = [NSColor colorWithCalibratedHue:0.283 saturation:0.7 brightness:0.8 alpha:1.000]; 95 | 96 | NSColor *textColor = strength < 0.4 ? red : (strength > 0.70 ? green : yellow); 97 | [textColor set]; 98 | 99 | NSMutableParagraphStyle* rightStyle = [[[NSMutableParagraphStyle alloc] init] autorelease]; 100 | [rightStyle setAlignment:NSRightTextAlignment]; 101 | NSDictionary *attr = [NSDictionary dictionaryWithObjectsAndKeys: 102 | [NSFont systemFontOfSize:[NSFont smallSystemFontSize]], NSFontAttributeName, 103 | textColor, NSForegroundColorAttributeName, 104 | rightStyle,NSParagraphStyleAttributeName, 105 | nil]; 106 | 107 | NSRect descRect = cellFrame; 108 | 109 | descRect = NSInsetRect(descRect, STRENGTH_INSET, 0.0); 110 | descRect.origin.y += 8; 111 | 112 | [descriptionOfStrength drawInRect:descRect withAttributes:attr]; 113 | } 114 | 115 | void drawMatch(NSRect cellFrame, MATCHING matching) 116 | { 117 | if (matching == HIDE_MATCH) return; 118 | 119 | NSColor *fillColor = nil; 120 | 121 | CGFloat w = 8.0; 122 | NSUInteger y = NSMaxY(cellFrame) - 15; 123 | NSRect drawFrame = NSMakeRect(NSMaxX(cellFrame)-w-6, y, w, w); 124 | 125 | switch (matching) { 126 | case PARTIAL_MATCH: fillColor = [NSColor yellowColor]; break; 127 | case DOESNT_MATCH: fillColor = [NSColor redColor]; break; 128 | default: break; 129 | } 130 | 131 | if (fillColor) 132 | { 133 | NSBezierPath *fillPath = [NSBezierPath bezierPathWithOvalInRect:drawFrame]; 134 | NSGradient *fillGradient = [[[NSGradient alloc] initWithStartingColor:[fillColor highlightWithLevel:0.15] 135 | endingColor:[fillColor shadowWithLevel:0.15]] autorelease]; 136 | [fillGradient drawInBezierPath:fillPath angle:90.0]; 137 | 138 | NSBezierPath *strokePath = [NSBezierPath bezierPathWithOvalInRect:NSInsetRect(drawFrame, -0.5, -0.5)]; 139 | [[fillColor shadowWithLevel:0.25] set]; 140 | [strokePath stroke]; 141 | } 142 | if (matching == FULL_MATCH) 143 | { 144 | NSImage *checkmark = [NSImage imageNamed:@"checkmark"]; // This image resource should be in the app 145 | NSRect checkmarkFrame = NSMakeRect(NSMaxX(cellFrame)-16-4,3,16,16); 146 | [checkmark drawInRect:checkmarkFrame fromRect:NSZeroRect operation:NSCompositeSourceAtop fraction:1.0 respectFlipped:YES hints:nil]; 147 | } 148 | } 149 | 150 | NSRect drawAdornments(NSRect cellFrame, NSView *controlView) 151 | { 152 | // When editing, use field editor, not string accessors, to avoid rending problems 153 | NSTextView *fieldEditor = (NSTextView *)[controlView.window fieldEditor:NO forObject:controlView]; // actuall NSSecureTextView (undocumented) 154 | if ([controlView.window firstResponder] != fieldEditor || fieldEditor.delegate != (id)controlView) fieldEditor = nil; 155 | 156 | KSPasswordField *passwordField = (KSPasswordField *)controlView; 157 | NSString *stringValue = fieldEditor ? [fieldEditor string] : [passwordField stringValue]; 158 | NSAttributedString *attribStringValue = fieldEditor ? [fieldEditor textStorage] : [passwordField attributedStringValue]; 159 | 160 | NSRect result = cellFrame; 161 | NSUInteger strlength = [stringValue length]; 162 | if (passwordField.showStrength) 163 | { 164 | NSRect r = NSZeroRect; 165 | if (passwordField.showsText) 166 | { 167 | r = [attribStringValue boundingRectWithSize:[controlView bounds].size options:0]; 168 | } 169 | else // get width of bullets 170 | { 171 | if (strlength) 172 | { 173 | NSDictionary *attr = [attribStringValue attributesAtIndex:0 effectiveRange:nil]; 174 | NSAttributedString *oneBullet = [[[NSAttributedString alloc] initWithString:@"•" attributes:attr] autorelease]; 175 | r = [oneBullet boundingRectWithSize:[controlView bounds].size options:0]; 176 | r.size.width *= strlength; 177 | } 178 | } 179 | if (r.size.width) r.size.width += 5; // extra to compensate for margin starting to left of actual text 180 | 181 | drawMeter(cellFrame, passwordField.strength, r.size.width); 182 | drawDescriptionOfStrength(cellFrame, passwordField.strength, passwordField.descriptionOfStrength); 183 | 184 | result.origin.y += YOFFSET; 185 | result.size.height -= YOFFSET; 186 | 187 | } 188 | // For the main field, if there is a string not long enough, also show the not-matching indicator to indicate a problem to fix 189 | MATCHING matchingToShow = passwordField.matching; 190 | if (!passwordField.showMatchIndicator) { 191 | matchingToShow = HIDE_MATCH; 192 | } else if (strlength > 0 && strlength < 8) { 193 | matchingToShow = DOESNT_MATCH; 194 | } 195 | drawMatch(result, matchingToShow); 196 | 197 | return result; 198 | } 199 | 200 | @interface NSObject(controlDidBecomeFirstResponder) 201 | - (void) controlDidBecomeFirstResponder:(NSNotification *)aNotification; 202 | @end 203 | 204 | 205 | @implementation KSPasswordTextFieldCell 206 | 207 | - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView 208 | { 209 | cellFrame = drawAdornments(cellFrame, controlView); 210 | cellFrame.origin.y -= 1; 211 | [super drawInteriorWithFrame:cellFrame inView:controlView]; 212 | } 213 | 214 | - (NSRect)drawingRectForBounds:(NSRect)aRect 215 | { 216 | NSRect result = [super drawingRectForBounds:aRect]; 217 | KSPasswordField *field = (KSPasswordField *)[self controlView]; 218 | 219 | if ([field showStrength]) 220 | { 221 | result.origin.y += YOFFSET; 222 | result.size.height -= YOFFSET; 223 | result.size.width -= (sMaxStrengthDescriptionWidth + STRENGTH_INSET); // leave room for drawing strength description 224 | } 225 | else if ([field showMatchIndicator]) 226 | { 227 | result.size.width -= (16 + 2); // leave room for drawing indicator 228 | } 229 | 230 | return result; 231 | } 232 | 233 | - (void)selectWithFrame:(NSRect)aRect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)anObject start:(NSInteger)selStart length:(NSInteger)selLength 234 | { 235 | NSRect result = aRect; 236 | if ([((KSPasswordField *)[self controlView]) showStrength]) 237 | { 238 | result.origin.y += YOFFSET - 1; 239 | result.size.height -= YOFFSET; 240 | } 241 | [super selectWithFrame:result inView: controlView editor:textObj delegate:anObject start:selStart length:selLength]; 242 | } 243 | 244 | - (void)editWithFrame:(NSRect)aRect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)anObject event:(NSEvent *)theEvent 245 | { 246 | NSRect result = aRect; 247 | if ([((KSPasswordField *)[self controlView]) showStrength]) 248 | { 249 | result.origin.y += YOFFSET - 1; 250 | result.size.height -= YOFFSET; 251 | } 252 | [super editWithFrame:result inView: controlView editor:textObj delegate:anObject event: theEvent]; 253 | } 254 | 255 | @end 256 | 257 | @implementation KSPasswordSecureTextFieldCell 258 | 259 | - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView 260 | { 261 | cellFrame = drawAdornments(cellFrame, controlView); 262 | [super drawInteriorWithFrame:cellFrame inView:controlView]; 263 | } 264 | 265 | - (NSRect)drawingRectForBounds:(NSRect)aRect 266 | { 267 | NSRect result = [super drawingRectForBounds:aRect]; 268 | KSPasswordField *field = (KSPasswordField *)[self controlView]; 269 | 270 | if ([field showStrength]) 271 | { 272 | result.origin.y += YOFFSET; 273 | result.size.height -= YOFFSET; 274 | result.size.width -= (sMaxStrengthDescriptionWidth + STRENGTH_INSET); // leave room for drawing strength description 275 | } 276 | else if ([field showMatchIndicator]) 277 | { 278 | result.size.width -= (16 + 2); // leave room for drawing indicator 279 | } 280 | 281 | return result; 282 | } 283 | 284 | - (void)selectWithFrame:(NSRect)aRect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)anObject start:(NSInteger)selStart length:(NSInteger)selLength 285 | { 286 | NSRect result = aRect; 287 | if ([((KSPasswordField *)[self controlView]) showStrength]) 288 | { 289 | result.origin.y += YOFFSET; 290 | result.size.height -= YOFFSET; 291 | } 292 | [super selectWithFrame:result inView: controlView editor:textObj delegate:anObject start:selStart length:selLength]; 293 | } 294 | 295 | - (void)editWithFrame:(NSRect)aRect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)anObject event:(NSEvent *)theEvent 296 | { 297 | NSRect result = aRect; 298 | if ([((KSPasswordField *)[self controlView]) showStrength]) 299 | { 300 | result.origin.y += YOFFSET; 301 | result.size.height -= YOFFSET; 302 | } 303 | [super editWithFrame:result inView: controlView editor:textObj delegate:anObject event: theEvent]; 304 | } 305 | 306 | @end 307 | 308 | 309 | @implementation KSPasswordField 310 | 311 | 312 | -(BOOL)textView:(NSTextView *)aTextView doCommandBySelector: (SEL)aSelector 313 | { 314 | if (aSelector == @selector(moveDown:)) 315 | { 316 | if ([self delegate] && [[self delegate] respondsToSelector:aSelector]) 317 | { 318 | [((NSResponder *)[self delegate]) moveDown:self]; 319 | return YES; 320 | } 321 | } 322 | return NO; 323 | } 324 | 325 | - (id)initWithFrame:(NSRect)frameRect; 326 | { 327 | if (self = [super initWithFrame:frameRect]) 328 | { 329 | _becomesFirstResponderWhenToggled = YES; 330 | 331 | // Don't show text by default. This needs to be called to replace the standard cell with our custom one. 332 | [self setShowsText:NO]; 333 | } 334 | return self; 335 | } 336 | 337 | - (id)initWithCoder:(NSCoder *)aDecoder; 338 | { 339 | if (self = [super initWithCoder:aDecoder]) 340 | { 341 | _becomesFirstResponderWhenToggled = YES; 342 | 343 | // Don't show text by default. This needs to be called to replace the standard cell with our custom one. 344 | [self setShowsText:NO]; 345 | } 346 | return self; 347 | } 348 | 349 | @synthesize showStrength = _showStrength; 350 | @synthesize showMatchIndicator = _showMatchIndicator; 351 | @synthesize passwordMeter = _passwordMeter;; 352 | @synthesize strength = _strength; 353 | @synthesize length = _length; 354 | @synthesize matching = _matching; 355 | @synthesize descriptionOfStrength = _descriptionOfStrength; 356 | 357 | + (Class)cellClass; 358 | { 359 | return [NSSecureTextFieldCell class]; // Really just a guess; set to the right subclass from code later. 360 | } 361 | 362 | + (void)initialize 363 | { 364 | NSArray *strengthDescriptions = @[ 365 | @"", // don't show any status yet 366 | NSLocalizedString(@"too common", @"description of (strength of) password. SHOULD BE A VERY SHORT WORD/PHRASE!"), 367 | NSLocalizedString(@"too short", @"description of (strength of) password. SHOULD BE A VERY SHORT WORD/PHRASE!"), 368 | NSLocalizedString(@"insecure", @"description of (strength of) password. SHOULD BE A VERY SHORT WORD!"), 369 | NSLocalizedString(@"weak", @"description of (strength of) password. SHOULD BE A VERY SHORT WORD!"), 370 | NSLocalizedString(@"fair", @"description of (strength of) password. OK but not great. SHOULD BE A VERY SHORT WORD!"), 371 | NSLocalizedString(@"strong", @"description of (strength of) password. SHOULD BE A VERY SHORT WORD!")]; 372 | sStrengthDescriptions = [strengthDescriptions retain]; 373 | 374 | // Load in the password blacklist file 375 | NSURL *blacklistURL = [[NSBundle mainBundle] URLForResource:@"blacklist" withExtension:@"plist"]; 376 | NSArray *blacklistArray = [[[NSArray alloc] initWithContentsOfURL:blacklistURL] autorelease]; 377 | sPasswordBlacklist = [[NSSet alloc] initWithArray:blacklistArray]; 378 | 379 | 380 | NSDictionary *attr = [NSDictionary dictionaryWithObjectsAndKeys: 381 | [NSFont systemFontOfSize:[NSFont smallSystemFontSize]], NSFontAttributeName, 382 | nil]; 383 | for ( NSString *desc in sStrengthDescriptions) 384 | { 385 | NSAttributedString *a = [[[NSMutableAttributedString alloc] initWithString:desc attributes:attr] autorelease]; 386 | NSRect r = [a boundingRectWithSize:NSZeroSize options:0]; 387 | if (r.size.width > sMaxStrengthDescriptionWidth) sMaxStrengthDescriptionWidth = ceilf(r.size.width); 388 | } 389 | } 390 | 391 | 392 | - (BOOL)becomeFirstResponder; 393 | { 394 | // If the control's delegate responds to controlDidBecomeFirstResponder, invoke it. Also post a notification. 395 | BOOL didBecomeFirstResponder = [super becomeFirstResponder]; 396 | NSNotification *notification = [NSNotification notificationWithName:MyControlDidBecomeFirstResponderNotification object:self]; 397 | if ( [self delegate] && [[self delegate] respondsToSelector:@selector(controlDidBecomeFirstResponder:)] ) { 398 | [((NSObject *)[self delegate]) controlDidBecomeFirstResponder:notification]; 399 | } 400 | [[NSNotificationCenter defaultCenter] postNotification:notification]; 401 | return didBecomeFirstResponder; 402 | } 403 | 404 | 405 | #pragma mark strength-o-meter 406 | 407 | 408 | 409 | - (void) setStrength:(float)strength length:(NSUInteger)length; 410 | { 411 | self.strength = strength; 412 | self.length = length; 413 | [self setNeedsDisplay:YES]; 414 | } 415 | 416 | 417 | #pragma mark Showing Password 418 | 419 | @synthesize showsText = _showsText; 420 | - (void)setShowsText:(BOOL)showsText; 421 | { 422 | _showsText = showsText; 423 | 424 | [self swapCellForOneOfClass:(showsText ? [KSPasswordTextFieldCell class] : [KSPasswordSecureTextFieldCell class])]; 425 | } 426 | 427 | @synthesize becomesFirstResponderWhenToggled = _becomesFirstResponderWhenToggled; 428 | 429 | - (void)showText:(id)sender; 430 | { 431 | [self setShowsText:YES]; 432 | } 433 | 434 | - (void)secureText:(id)sender; 435 | { 436 | [self setShowsText:NO]; 437 | } 438 | 439 | - (IBAction)toggleTextShown:(id)sender; 440 | { 441 | [self setShowsText:![self showsText]]; 442 | } 443 | 444 | - (void)swapCellForOneOfClass:(Class)cellClass; 445 | { 446 | // Rememeber current selection for restoration after the swap 447 | // -valueForKey: neatly gives nil if no currently selected 448 | NSValue *selection = [[self currentEditor] valueForKey:@"selectedRange"]; 449 | 450 | // Seems to me the best way to ensure all properties come along for the ride (e.g. border/editability) is to archive the existing cell 451 | NSMutableData *data = [[NSMutableData alloc] init]; 452 | NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; 453 | [[self cell] encodeWithCoder:archiver]; 454 | [archiver finishEncoding]; 455 | [archiver release]; 456 | 457 | NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; 458 | NSTextFieldCell *cell = [[cellClass alloc] initWithCoder:unarchiver]; 459 | cell.stringValue = [self.cell stringValue]; // restore value; secure text fields wisely don't encode it 460 | [unarchiver finishDecoding]; 461 | [unarchiver release]; 462 | [data release]; 463 | 464 | [self setCell:cell]; 465 | [self setNeedsDisplay:YES]; 466 | [cell release]; 467 | 468 | // Restore selection 469 | if (selection) 470 | { 471 | [self.window makeFirstResponder:self]; 472 | [[self currentEditor] setSelectedRange:[selection rangeValue]]; 473 | } 474 | else if (self.becomesFirstResponderWhenToggled) 475 | { 476 | NSTextView *fieldEditor = (NSTextView *)[self.window firstResponder]; 477 | BOOL isEditingPassword = [fieldEditor isKindOfClass:[NSTextView class]] && [fieldEditor.delegate isKindOfClass:[KSPasswordField class]]; 478 | 479 | if (isEditingPassword) 480 | { 481 | [self.window makeFirstResponder:self]; 482 | } 483 | } 484 | } 485 | 486 | #pragma mark - Password Cleaning Logic 487 | 488 | //! Returns the same string if nothing needs changing. 489 | //! Otherwise, returns the password with whitespace trimmed off 490 | 491 | - (NSString*)cleanedPasswordForString:(NSString*)string 492 | { 493 | NSString* result; 494 | 495 | NSCharacterSet *whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet]; 496 | NSCharacterSet *nonWhitespaceChars = [whitespace invertedSet]; 497 | BOOL containsNonWhitespace = [string rangeOfCharacterFromSet:nonWhitespaceChars].location != NSNotFound; 498 | if (containsNonWhitespace) 499 | { 500 | result = [string stringByTrimmingCharactersInSet:whitespace]; 501 | } 502 | else 503 | { 504 | result = string; 505 | } 506 | 507 | return result; 508 | } 509 | 510 | #pragma mark - Smart Paste 511 | 512 | 513 | - (BOOL)textView:(NSTextView *)textView shouldChangeTextInRange:(NSRange)affectedCharRange replacementString:(NSString *)replacementString; 514 | { 515 | BOOL shouldChange = YES; 516 | BOOL changingEntireContents = (affectedCharRange.location == 0) && (affectedCharRange.length == [textView.string length]); 517 | if (changingEntireContents) 518 | { 519 | // we check for text containing non-whitespace characters but starting with or ending with whitespace 520 | // if we find it, we assume that it's being pasted in, and we trim the whitespace off first 521 | NSString* cleaned = [self cleanedPasswordForString:replacementString]; 522 | BOOL wasTrimmed = ![cleaned isEqualToString:replacementString]; 523 | if (wasTrimmed) 524 | { 525 | // Store the trimmed value back into the model, which should bubble up to replace what got pasted in 526 | NSDictionary *binding = [self infoForBinding:NSValueBinding]; 527 | [[binding objectForKey:NSObservedObjectKey] setValue:cleaned forKeyPath:[binding objectForKey:NSObservedKeyPathKey]]; 528 | shouldChange = NO; 529 | } 530 | } 531 | 532 | return shouldChange; 533 | } 534 | 535 | #pragma mark Password strength 536 | 537 | // Returns zero (awful) to one (awesome). We could reject low values, warn if medium values 538 | - (float) strengthOfPassword:(NSString *)proposedPassword; 539 | { 540 | NSUInteger length = [proposedPassword length]; // the longer the better 541 | // Not going to use enumerateSubstringsInRange with NSStringEnumerationByComposedCharacterSequences 542 | // since I just want a quick and dirty measurement, and it's easier to check character-by-character 543 | 544 | NSUInteger numberOfLowercase = 0; 545 | NSUInteger numberOfUppercase = 0; 546 | NSUInteger numberOfDecimalDigit = 0; 547 | NSUInteger numberOfOther = 0; 548 | for (NSUInteger i = 0 ; i < length ; i++) 549 | { 550 | unichar aChar = [proposedPassword characterAtIndex:i]; 551 | if ([[NSCharacterSet lowercaseLetterCharacterSet] characterIsMember:aChar]) 552 | { 553 | numberOfLowercase++; 554 | } 555 | else if ([[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember:aChar]) 556 | { 557 | numberOfUppercase++; 558 | } 559 | else if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:aChar]) 560 | { 561 | numberOfDecimalDigit++; 562 | } 563 | else 564 | { 565 | numberOfOther++; 566 | } 567 | } 568 | NSUInteger diversity = 0; // We'll find if it has lower-alpha, upper alpha, numeric, and other 569 | if (numberOfLowercase) diversity++; 570 | if (numberOfUppercase) diversity++; 571 | if (numberOfDecimalDigit) diversity++; 572 | if (numberOfOther) diversity++; 573 | 574 | // Give another point if we have a good number of unique characters in the whole (longish) string 575 | 576 | NSMutableSet *allChars = [NSMutableSet set]; 577 | [proposedPassword enumerateSubstringsInRange:NSMakeRange(0, [proposedPassword length]) 578 | options:NSStringEnumerationByComposedCharacterSequences 579 | usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop){ 580 | [allChars addObject:substring]; 581 | }]; 582 | if ( [proposedPassword length] >= 12 && (float)[allChars count] / (float)[proposedPassword length] > 0.4 ) 583 | { 584 | diversity++; // Give it an extra point if there is a variety of characters 585 | } 586 | if (NSClassFromString(@"NSRegularExpression")) // 10.7 up, so ignore if not on 10.7) 587 | { 588 | // Look for repetition: 4 or more characters, repeated - if so, reduce the diversity 589 | 590 | NSError *error = NULL; 591 | NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(.{4,}).*\\1" // 4 or more characters, then anything, then that same group of characters 592 | options:NSRegularExpressionCaseInsensitive 593 | error:&error]; 594 | 595 | if (!error) 596 | { 597 | NSUInteger numberOfMatches = [regex numberOfMatchesInString:proposedPassword 598 | options:0 599 | range:NSMakeRange(0, [proposedPassword length])]; 600 | if (numberOfMatches > 0) 601 | { 602 | diversity--; // Might go as low as zero. 603 | } 604 | } 605 | } 606 | 607 | // Possible enhancement: Penalize for ordered sequences like ABCDEFGHI…, 123456789, QWERTY, etc. 608 | 609 | 610 | // Now here's the big emperical formula, devised so that the more diverse and the longer, 611 | // the better the rating. An 8-character password with diversity of 4 is right in the middle. 612 | // It's possible to have a good strength with low diversity ONLY if the password is hecka long. 613 | 614 | float strength = MIN(1.0f, 0.015625f * diversity * length); 615 | return strength; 616 | } 617 | 618 | - (BOOL) strongEnough; 619 | { 620 | return self.passwordMeter >= MeterInsecure; 621 | } 622 | 623 | - (void)textDidChange:(NSNotification *)aNotification 624 | { 625 | if (self.showStrength) 626 | { 627 | NSString *string = [self stringValue]; 628 | float strength = [self strengthOfPassword:string]; // zero to one 629 | 630 | BOOL visible = string.length > 0; 631 | 632 | PasswordMeter passwordMeter = MeterEmpty; 633 | if (visible) 634 | { 635 | if ([string length] < 8) 636 | { 637 | passwordMeter = MeterShort; 638 | } 639 | else if ([sPasswordBlacklist containsObject:string]) 640 | { 641 | passwordMeter = MeterCommon; // Don't let super-common password be chosen 642 | } 643 | else 644 | { 645 | if (strength < 0.25) { passwordMeter = MeterInsecure; } 646 | else if (strength < 0.4) { passwordMeter = MeterWeak; } 647 | else if (strength < 0.70) { passwordMeter = MeterFair; } 648 | else { passwordMeter = MeterStrong; } 649 | } 650 | } 651 | self.passwordMeter = passwordMeter; 652 | self.descriptionOfStrength = [sStrengthDescriptions objectAtIndex:passwordMeter]; 653 | 654 | [self setStrength:strength length:[string length]]; 655 | } 656 | 657 | // Password fields don't seem to send out continuous binding updates, nor NSControlTextDidChangeNotification. 658 | // https://developer.apple.com/library/mac/documentation/cocoa/reference/applicationkit/classes/NSControl_Class/Reference/Reference.html#//apple_ref/c/data/NSControlTextDidChangeNotification 659 | // So we're doing that manually. 660 | 661 | NSNotification *newNotif = [NSNotification notificationWithName:NSControlTextDidChangeNotification 662 | object:self 663 | userInfo: @{ @"NSFieldEditor" : [self.window fieldEditor:NO forObject:self] } 664 | ]; 665 | [[NSNotificationCenter defaultCenter] postNotification:newNotif]; 666 | } 667 | 668 | 669 | @end 670 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Created by Mike Abdullah on 28/04/2012. 2 | Copyright © 2012 Karelia Software 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Features 2 | ======== 3 | 4 | Presently, SecurityInterface has a whopping one class: `KSPasswordField`. It: 5 | 6 | * Shows the password in plain text on-demand 7 | * On the basis that password visibility is likely being toggled for editing, makes the field the first responder 8 | * Automatically cleans up likely unwanted whitespace when pasting or dragging in passwords 9 | 10 | Contributors and Contact Info 11 | ============================= 12 | 13 | I'm Mike Abdullah, of [Karelia Software](http://karelia.com). [@mikeabdullah](http://twitter.com/mikeabdullah) on Twitter. 14 | 15 | Questions about the code should be left as issues at https://github.com/karelia/SecurityInterface or message me on Twitter. 16 | 17 | Thanks to [Sebastian](https://github.com/lightforce) for the example project. 18 | 19 | Dependencies 20 | ============ 21 | 22 | None beyond AppKit. Probably works back to OS X v10.2 if you were so inclined 23 | 24 | License 25 | ======= 26 | 27 | Copyright © 2012 Karelia Software 28 | 29 | Permission is hereby granted, free of charge, to any person obtaining a copy 30 | of this software and associated documentation files (the "Software"), to deal 31 | in the Software without restriction, including without limitation the rights 32 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 33 | copies of the Software, and to permit persons to whom the Software is 34 | furnished to do so, subject to the following conditions: 35 | 36 | The above copyright notice and this permission notice shall be included in 37 | all copies or substantial portions of the Software. 38 | 39 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 40 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 41 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 42 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 43 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 44 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 45 | THE SOFTWARE. 46 | 47 | Usage 48 | ===== 49 | 50 | 1. Add `KSPasswordField.h` and `KSPasswordField.m` to your project. Ideally, make this repo a submodule, but hey, it's your codebase, do whatever you feel like. 51 | 2. In Interface Builder, create a regular `NSSecureTextField` and then set its custom class to be `KSPasswordField`. You can also instantiate `KSPasswordField` directly like any other control 52 | 3. Whitespace-cleanup comes for free; no extra work required 53 | 4. To control showing the password, `KSPasswordField` has the following methods: 54 | * `@property(nonatomic) BOOL showsText;` 55 | * `- (IBAction)showText:(id)sender;` 56 | * `- (IBAction)secureText:(id)sender;` 57 | * `- (IBAction)toggleTextShown:(id)sender;` 58 | 59 | I generally prefer to hook up a checkbox (`NSButton`) straight to the `toggleTextShown:` action --------------------------------------------------------------------------------