├── KUDemo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── jonny.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── KUDemo ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ └── LaunchScreen.storyboard ├── Info.plist └── ViewController.swift ├── KeyboardUtility ├── Info.plist ├── KeyboardFrameObserver.swift ├── KeyboardFrameObserversManager.swift ├── KeyboardLayoutGuide.swift └── KeyboardUtility.h ├── LICENSE └── README.md /KUDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | FB08C2C31F779D8C0092A8F5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB08C2C21F779D8C0092A8F5 /* AppDelegate.swift */; }; 11 | FB08C2C51F779D8C0092A8F5 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB08C2C41F779D8C0092A8F5 /* ViewController.swift */; }; 12 | FB08C2CA1F779D8C0092A8F5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FB08C2C91F779D8C0092A8F5 /* Assets.xcassets */; }; 13 | FB08C2CD1F779D8C0092A8F5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FB08C2CB1F779D8C0092A8F5 /* LaunchScreen.storyboard */; }; 14 | FB08C2DD1F779DA70092A8F5 /* KeyboardUtility.h in Headers */ = {isa = PBXBuildFile; fileRef = FB08C2DB1F779DA70092A8F5 /* KeyboardUtility.h */; settings = {ATTRIBUTES = (Public, ); }; }; 15 | FB08C2E01F779DA70092A8F5 /* KeyboardUtility.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB08C2D91F779DA70092A8F5 /* KeyboardUtility.framework */; }; 16 | FB08C2E11F779DA70092A8F5 /* KeyboardUtility.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = FB08C2D91F779DA70092A8F5 /* KeyboardUtility.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 17 | FB08C2E91F779DC00092A8F5 /* KeyboardFrameObserversManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB08C2E61F779DC00092A8F5 /* KeyboardFrameObserversManager.swift */; }; 18 | FB08C2EA1F779DC00092A8F5 /* KeyboardLayoutGuide.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB08C2E71F779DC00092A8F5 /* KeyboardLayoutGuide.swift */; }; 19 | FB08C2EB1F779DC00092A8F5 /* KeyboardFrameObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB08C2E81F779DC00092A8F5 /* KeyboardFrameObserver.swift */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | FB08C2DE1F779DA70092A8F5 /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = FB08C2B71F779D8C0092A8F5 /* Project object */; 26 | proxyType = 1; 27 | remoteGlobalIDString = FB08C2D81F779DA70092A8F5; 28 | remoteInfo = KeyboardUtility; 29 | }; 30 | /* End PBXContainerItemProxy section */ 31 | 32 | /* Begin PBXCopyFilesBuildPhase section */ 33 | FB08C2E51F779DA70092A8F5 /* Embed Frameworks */ = { 34 | isa = PBXCopyFilesBuildPhase; 35 | buildActionMask = 2147483647; 36 | dstPath = ""; 37 | dstSubfolderSpec = 10; 38 | files = ( 39 | FB08C2E11F779DA70092A8F5 /* KeyboardUtility.framework in Embed Frameworks */, 40 | ); 41 | name = "Embed Frameworks"; 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXCopyFilesBuildPhase section */ 45 | 46 | /* Begin PBXFileReference section */ 47 | FB08C2BF1F779D8C0092A8F5 /* KUDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KUDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | FB08C2C21F779D8C0092A8F5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 49 | FB08C2C41F779D8C0092A8F5 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 50 | FB08C2C91F779D8C0092A8F5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 51 | FB08C2CC1F779D8C0092A8F5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 52 | FB08C2CE1F779D8C0092A8F5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53 | FB08C2D91F779DA70092A8F5 /* KeyboardUtility.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = KeyboardUtility.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | FB08C2DB1F779DA70092A8F5 /* KeyboardUtility.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyboardUtility.h; sourceTree = ""; }; 55 | FB08C2DC1F779DA70092A8F5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 56 | FB08C2E61F779DC00092A8F5 /* KeyboardFrameObserversManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardFrameObserversManager.swift; sourceTree = ""; }; 57 | FB08C2E71F779DC00092A8F5 /* KeyboardLayoutGuide.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardLayoutGuide.swift; sourceTree = ""; }; 58 | FB08C2E81F779DC00092A8F5 /* KeyboardFrameObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardFrameObserver.swift; sourceTree = ""; }; 59 | /* End PBXFileReference section */ 60 | 61 | /* Begin PBXFrameworksBuildPhase section */ 62 | FB08C2BC1F779D8C0092A8F5 /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 2147483647; 65 | files = ( 66 | FB08C2E01F779DA70092A8F5 /* KeyboardUtility.framework in Frameworks */, 67 | ); 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | FB08C2D51F779DA70092A8F5 /* Frameworks */ = { 71 | isa = PBXFrameworksBuildPhase; 72 | buildActionMask = 2147483647; 73 | files = ( 74 | ); 75 | runOnlyForDeploymentPostprocessing = 0; 76 | }; 77 | /* End PBXFrameworksBuildPhase section */ 78 | 79 | /* Begin PBXGroup section */ 80 | FB08C2B61F779D8C0092A8F5 = { 81 | isa = PBXGroup; 82 | children = ( 83 | FB08C2C11F779D8C0092A8F5 /* KUDemo */, 84 | FB08C2DA1F779DA70092A8F5 /* KeyboardUtility */, 85 | FB08C2C01F779D8C0092A8F5 /* Products */, 86 | ); 87 | sourceTree = ""; 88 | }; 89 | FB08C2C01F779D8C0092A8F5 /* Products */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | FB08C2BF1F779D8C0092A8F5 /* KUDemo.app */, 93 | FB08C2D91F779DA70092A8F5 /* KeyboardUtility.framework */, 94 | ); 95 | name = Products; 96 | sourceTree = ""; 97 | }; 98 | FB08C2C11F779D8C0092A8F5 /* KUDemo */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | FB08C2C21F779D8C0092A8F5 /* AppDelegate.swift */, 102 | FB08C2C41F779D8C0092A8F5 /* ViewController.swift */, 103 | FB08C2C91F779D8C0092A8F5 /* Assets.xcassets */, 104 | FB08C2CB1F779D8C0092A8F5 /* LaunchScreen.storyboard */, 105 | FB08C2CE1F779D8C0092A8F5 /* Info.plist */, 106 | ); 107 | path = KUDemo; 108 | sourceTree = ""; 109 | }; 110 | FB08C2DA1F779DA70092A8F5 /* KeyboardUtility */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | FB08C2DB1F779DA70092A8F5 /* KeyboardUtility.h */, 114 | FB08C2E81F779DC00092A8F5 /* KeyboardFrameObserver.swift */, 115 | FB08C2E61F779DC00092A8F5 /* KeyboardFrameObserversManager.swift */, 116 | FB08C2E71F779DC00092A8F5 /* KeyboardLayoutGuide.swift */, 117 | FB08C2DC1F779DA70092A8F5 /* Info.plist */, 118 | ); 119 | path = KeyboardUtility; 120 | sourceTree = ""; 121 | }; 122 | /* End PBXGroup section */ 123 | 124 | /* Begin PBXHeadersBuildPhase section */ 125 | FB08C2D61F779DA70092A8F5 /* Headers */ = { 126 | isa = PBXHeadersBuildPhase; 127 | buildActionMask = 2147483647; 128 | files = ( 129 | FB08C2DD1F779DA70092A8F5 /* KeyboardUtility.h in Headers */, 130 | ); 131 | runOnlyForDeploymentPostprocessing = 0; 132 | }; 133 | /* End PBXHeadersBuildPhase section */ 134 | 135 | /* Begin PBXNativeTarget section */ 136 | FB08C2BE1F779D8C0092A8F5 /* KUDemo */ = { 137 | isa = PBXNativeTarget; 138 | buildConfigurationList = FB08C2D11F779D8C0092A8F5 /* Build configuration list for PBXNativeTarget "KUDemo" */; 139 | buildPhases = ( 140 | FB08C2BB1F779D8C0092A8F5 /* Sources */, 141 | FB08C2BC1F779D8C0092A8F5 /* Frameworks */, 142 | FB08C2BD1F779D8C0092A8F5 /* Resources */, 143 | FB08C2E51F779DA70092A8F5 /* Embed Frameworks */, 144 | ); 145 | buildRules = ( 146 | ); 147 | dependencies = ( 148 | FB08C2DF1F779DA70092A8F5 /* PBXTargetDependency */, 149 | ); 150 | name = KUDemo; 151 | productName = KUDemo; 152 | productReference = FB08C2BF1F779D8C0092A8F5 /* KUDemo.app */; 153 | productType = "com.apple.product-type.application"; 154 | }; 155 | FB08C2D81F779DA70092A8F5 /* KeyboardUtility */ = { 156 | isa = PBXNativeTarget; 157 | buildConfigurationList = FB08C2E21F779DA70092A8F5 /* Build configuration list for PBXNativeTarget "KeyboardUtility" */; 158 | buildPhases = ( 159 | FB08C2D41F779DA70092A8F5 /* Sources */, 160 | FB08C2D51F779DA70092A8F5 /* Frameworks */, 161 | FB08C2D61F779DA70092A8F5 /* Headers */, 162 | FB08C2D71F779DA70092A8F5 /* Resources */, 163 | ); 164 | buildRules = ( 165 | ); 166 | dependencies = ( 167 | ); 168 | name = KeyboardUtility; 169 | productName = KeyboardUtility; 170 | productReference = FB08C2D91F779DA70092A8F5 /* KeyboardUtility.framework */; 171 | productType = "com.apple.product-type.framework"; 172 | }; 173 | /* End PBXNativeTarget section */ 174 | 175 | /* Begin PBXProject section */ 176 | FB08C2B71F779D8C0092A8F5 /* Project object */ = { 177 | isa = PBXProject; 178 | attributes = { 179 | LastSwiftUpdateCheck = 0900; 180 | LastUpgradeCheck = 0900; 181 | ORGANIZATIONNAME = Jonny; 182 | TargetAttributes = { 183 | FB08C2BE1F779D8C0092A8F5 = { 184 | CreatedOnToolsVersion = 9.0; 185 | ProvisioningStyle = Automatic; 186 | }; 187 | FB08C2D81F779DA70092A8F5 = { 188 | CreatedOnToolsVersion = 9.0; 189 | LastSwiftMigration = 0900; 190 | ProvisioningStyle = Automatic; 191 | }; 192 | }; 193 | }; 194 | buildConfigurationList = FB08C2BA1F779D8C0092A8F5 /* Build configuration list for PBXProject "KUDemo" */; 195 | compatibilityVersion = "Xcode 8.0"; 196 | developmentRegion = en; 197 | hasScannedForEncodings = 0; 198 | knownRegions = ( 199 | en, 200 | Base, 201 | ); 202 | mainGroup = FB08C2B61F779D8C0092A8F5; 203 | productRefGroup = FB08C2C01F779D8C0092A8F5 /* Products */; 204 | projectDirPath = ""; 205 | projectRoot = ""; 206 | targets = ( 207 | FB08C2BE1F779D8C0092A8F5 /* KUDemo */, 208 | FB08C2D81F779DA70092A8F5 /* KeyboardUtility */, 209 | ); 210 | }; 211 | /* End PBXProject section */ 212 | 213 | /* Begin PBXResourcesBuildPhase section */ 214 | FB08C2BD1F779D8C0092A8F5 /* Resources */ = { 215 | isa = PBXResourcesBuildPhase; 216 | buildActionMask = 2147483647; 217 | files = ( 218 | FB08C2CD1F779D8C0092A8F5 /* LaunchScreen.storyboard in Resources */, 219 | FB08C2CA1F779D8C0092A8F5 /* Assets.xcassets in Resources */, 220 | ); 221 | runOnlyForDeploymentPostprocessing = 0; 222 | }; 223 | FB08C2D71F779DA70092A8F5 /* Resources */ = { 224 | isa = PBXResourcesBuildPhase; 225 | buildActionMask = 2147483647; 226 | files = ( 227 | ); 228 | runOnlyForDeploymentPostprocessing = 0; 229 | }; 230 | /* End PBXResourcesBuildPhase section */ 231 | 232 | /* Begin PBXSourcesBuildPhase section */ 233 | FB08C2BB1F779D8C0092A8F5 /* Sources */ = { 234 | isa = PBXSourcesBuildPhase; 235 | buildActionMask = 2147483647; 236 | files = ( 237 | FB08C2C51F779D8C0092A8F5 /* ViewController.swift in Sources */, 238 | FB08C2C31F779D8C0092A8F5 /* AppDelegate.swift in Sources */, 239 | ); 240 | runOnlyForDeploymentPostprocessing = 0; 241 | }; 242 | FB08C2D41F779DA70092A8F5 /* Sources */ = { 243 | isa = PBXSourcesBuildPhase; 244 | buildActionMask = 2147483647; 245 | files = ( 246 | FB08C2E91F779DC00092A8F5 /* KeyboardFrameObserversManager.swift in Sources */, 247 | FB08C2EB1F779DC00092A8F5 /* KeyboardFrameObserver.swift in Sources */, 248 | FB08C2EA1F779DC00092A8F5 /* KeyboardLayoutGuide.swift in Sources */, 249 | ); 250 | runOnlyForDeploymentPostprocessing = 0; 251 | }; 252 | /* End PBXSourcesBuildPhase section */ 253 | 254 | /* Begin PBXTargetDependency section */ 255 | FB08C2DF1F779DA70092A8F5 /* PBXTargetDependency */ = { 256 | isa = PBXTargetDependency; 257 | target = FB08C2D81F779DA70092A8F5 /* KeyboardUtility */; 258 | targetProxy = FB08C2DE1F779DA70092A8F5 /* PBXContainerItemProxy */; 259 | }; 260 | /* End PBXTargetDependency section */ 261 | 262 | /* Begin PBXVariantGroup section */ 263 | FB08C2CB1F779D8C0092A8F5 /* LaunchScreen.storyboard */ = { 264 | isa = PBXVariantGroup; 265 | children = ( 266 | FB08C2CC1F779D8C0092A8F5 /* Base */, 267 | ); 268 | name = LaunchScreen.storyboard; 269 | sourceTree = ""; 270 | }; 271 | /* End PBXVariantGroup section */ 272 | 273 | /* Begin XCBuildConfiguration section */ 274 | FB08C2CF1F779D8C0092A8F5 /* Debug */ = { 275 | isa = XCBuildConfiguration; 276 | buildSettings = { 277 | ALWAYS_SEARCH_USER_PATHS = NO; 278 | CLANG_ANALYZER_NONNULL = YES; 279 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 280 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 281 | CLANG_CXX_LIBRARY = "libc++"; 282 | CLANG_ENABLE_MODULES = YES; 283 | CLANG_ENABLE_OBJC_ARC = YES; 284 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 285 | CLANG_WARN_BOOL_CONVERSION = YES; 286 | CLANG_WARN_COMMA = YES; 287 | CLANG_WARN_CONSTANT_CONVERSION = YES; 288 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 289 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 290 | CLANG_WARN_EMPTY_BODY = YES; 291 | CLANG_WARN_ENUM_CONVERSION = YES; 292 | CLANG_WARN_INFINITE_RECURSION = YES; 293 | CLANG_WARN_INT_CONVERSION = YES; 294 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 295 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 296 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 297 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 298 | CLANG_WARN_STRICT_PROTOTYPES = YES; 299 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 300 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 301 | CLANG_WARN_UNREACHABLE_CODE = YES; 302 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 303 | CODE_SIGN_IDENTITY = "iPhone Developer"; 304 | COPY_PHASE_STRIP = NO; 305 | DEBUG_INFORMATION_FORMAT = dwarf; 306 | ENABLE_STRICT_OBJC_MSGSEND = YES; 307 | ENABLE_TESTABILITY = YES; 308 | GCC_C_LANGUAGE_STANDARD = gnu11; 309 | GCC_DYNAMIC_NO_PIC = NO; 310 | GCC_NO_COMMON_BLOCKS = YES; 311 | GCC_OPTIMIZATION_LEVEL = 0; 312 | GCC_PREPROCESSOR_DEFINITIONS = ( 313 | "DEBUG=1", 314 | "$(inherited)", 315 | ); 316 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 317 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 318 | GCC_WARN_UNDECLARED_SELECTOR = YES; 319 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 320 | GCC_WARN_UNUSED_FUNCTION = YES; 321 | GCC_WARN_UNUSED_VARIABLE = YES; 322 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 323 | MTL_ENABLE_DEBUG_INFO = YES; 324 | ONLY_ACTIVE_ARCH = YES; 325 | SDKROOT = iphoneos; 326 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 327 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 328 | }; 329 | name = Debug; 330 | }; 331 | FB08C2D01F779D8C0092A8F5 /* Release */ = { 332 | isa = XCBuildConfiguration; 333 | buildSettings = { 334 | ALWAYS_SEARCH_USER_PATHS = NO; 335 | CLANG_ANALYZER_NONNULL = YES; 336 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 337 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 338 | CLANG_CXX_LIBRARY = "libc++"; 339 | CLANG_ENABLE_MODULES = YES; 340 | CLANG_ENABLE_OBJC_ARC = YES; 341 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 342 | CLANG_WARN_BOOL_CONVERSION = YES; 343 | CLANG_WARN_COMMA = YES; 344 | CLANG_WARN_CONSTANT_CONVERSION = YES; 345 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 346 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 347 | CLANG_WARN_EMPTY_BODY = YES; 348 | CLANG_WARN_ENUM_CONVERSION = YES; 349 | CLANG_WARN_INFINITE_RECURSION = YES; 350 | CLANG_WARN_INT_CONVERSION = YES; 351 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 352 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 353 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 354 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 355 | CLANG_WARN_STRICT_PROTOTYPES = YES; 356 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 357 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 358 | CLANG_WARN_UNREACHABLE_CODE = YES; 359 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 360 | CODE_SIGN_IDENTITY = "iPhone Developer"; 361 | COPY_PHASE_STRIP = NO; 362 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 363 | ENABLE_NS_ASSERTIONS = NO; 364 | ENABLE_STRICT_OBJC_MSGSEND = YES; 365 | GCC_C_LANGUAGE_STANDARD = gnu11; 366 | GCC_NO_COMMON_BLOCKS = YES; 367 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 368 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 369 | GCC_WARN_UNDECLARED_SELECTOR = YES; 370 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 371 | GCC_WARN_UNUSED_FUNCTION = YES; 372 | GCC_WARN_UNUSED_VARIABLE = YES; 373 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 374 | MTL_ENABLE_DEBUG_INFO = NO; 375 | SDKROOT = iphoneos; 376 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 377 | VALIDATE_PRODUCT = YES; 378 | }; 379 | name = Release; 380 | }; 381 | FB08C2D21F779D8C0092A8F5 /* Debug */ = { 382 | isa = XCBuildConfiguration; 383 | buildSettings = { 384 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 385 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 386 | CODE_SIGN_STYLE = Automatic; 387 | DEVELOPMENT_TEAM = FF973S8UP2; 388 | INFOPLIST_FILE = KUDemo/Info.plist; 389 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 390 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 391 | PRODUCT_BUNDLE_IDENTIFIER = com.jonny.KUDemo; 392 | PRODUCT_NAME = "$(TARGET_NAME)"; 393 | SWIFT_VERSION = 4.0; 394 | TARGETED_DEVICE_FAMILY = "1,2"; 395 | }; 396 | name = Debug; 397 | }; 398 | FB08C2D31F779D8C0092A8F5 /* Release */ = { 399 | isa = XCBuildConfiguration; 400 | buildSettings = { 401 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 402 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 403 | CODE_SIGN_STYLE = Automatic; 404 | DEVELOPMENT_TEAM = FF973S8UP2; 405 | INFOPLIST_FILE = KUDemo/Info.plist; 406 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 407 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 408 | PRODUCT_BUNDLE_IDENTIFIER = com.jonny.KUDemo; 409 | PRODUCT_NAME = "$(TARGET_NAME)"; 410 | SWIFT_VERSION = 4.0; 411 | TARGETED_DEVICE_FAMILY = "1,2"; 412 | }; 413 | name = Release; 414 | }; 415 | FB08C2E31F779DA70092A8F5 /* Debug */ = { 416 | isa = XCBuildConfiguration; 417 | buildSettings = { 418 | CLANG_ENABLE_MODULES = YES; 419 | CODE_SIGN_IDENTITY = ""; 420 | CODE_SIGN_STYLE = Automatic; 421 | CURRENT_PROJECT_VERSION = 1; 422 | DEFINES_MODULE = YES; 423 | DEVELOPMENT_TEAM = FF973S8UP2; 424 | DYLIB_COMPATIBILITY_VERSION = 1; 425 | DYLIB_CURRENT_VERSION = 1; 426 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 427 | INFOPLIST_FILE = KeyboardUtility/Info.plist; 428 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 429 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 430 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 431 | PRODUCT_BUNDLE_IDENTIFIER = com.jonny.KeyboardUtility; 432 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 433 | SKIP_INSTALL = YES; 434 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 435 | SWIFT_VERSION = 4.0; 436 | TARGETED_DEVICE_FAMILY = "1,2"; 437 | VERSIONING_SYSTEM = "apple-generic"; 438 | VERSION_INFO_PREFIX = ""; 439 | }; 440 | name = Debug; 441 | }; 442 | FB08C2E41F779DA70092A8F5 /* Release */ = { 443 | isa = XCBuildConfiguration; 444 | buildSettings = { 445 | CLANG_ENABLE_MODULES = YES; 446 | CODE_SIGN_IDENTITY = ""; 447 | CODE_SIGN_STYLE = Automatic; 448 | CURRENT_PROJECT_VERSION = 1; 449 | DEFINES_MODULE = YES; 450 | DEVELOPMENT_TEAM = FF973S8UP2; 451 | DYLIB_COMPATIBILITY_VERSION = 1; 452 | DYLIB_CURRENT_VERSION = 1; 453 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 454 | INFOPLIST_FILE = KeyboardUtility/Info.plist; 455 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 456 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 457 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 458 | PRODUCT_BUNDLE_IDENTIFIER = com.jonny.KeyboardUtility; 459 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 460 | SKIP_INSTALL = YES; 461 | SWIFT_VERSION = 4.0; 462 | TARGETED_DEVICE_FAMILY = "1,2"; 463 | VERSIONING_SYSTEM = "apple-generic"; 464 | VERSION_INFO_PREFIX = ""; 465 | }; 466 | name = Release; 467 | }; 468 | /* End XCBuildConfiguration section */ 469 | 470 | /* Begin XCConfigurationList section */ 471 | FB08C2BA1F779D8C0092A8F5 /* Build configuration list for PBXProject "KUDemo" */ = { 472 | isa = XCConfigurationList; 473 | buildConfigurations = ( 474 | FB08C2CF1F779D8C0092A8F5 /* Debug */, 475 | FB08C2D01F779D8C0092A8F5 /* Release */, 476 | ); 477 | defaultConfigurationIsVisible = 0; 478 | defaultConfigurationName = Release; 479 | }; 480 | FB08C2D11F779D8C0092A8F5 /* Build configuration list for PBXNativeTarget "KUDemo" */ = { 481 | isa = XCConfigurationList; 482 | buildConfigurations = ( 483 | FB08C2D21F779D8C0092A8F5 /* Debug */, 484 | FB08C2D31F779D8C0092A8F5 /* Release */, 485 | ); 486 | defaultConfigurationIsVisible = 0; 487 | defaultConfigurationName = Release; 488 | }; 489 | FB08C2E21F779DA70092A8F5 /* Build configuration list for PBXNativeTarget "KeyboardUtility" */ = { 490 | isa = XCConfigurationList; 491 | buildConfigurations = ( 492 | FB08C2E31F779DA70092A8F5 /* Debug */, 493 | FB08C2E41F779DA70092A8F5 /* Release */, 494 | ); 495 | defaultConfigurationIsVisible = 0; 496 | defaultConfigurationName = Release; 497 | }; 498 | /* End XCConfigurationList section */ 499 | }; 500 | rootObject = FB08C2B71F779D8C0092A8F5 /* Project object */; 501 | } 502 | -------------------------------------------------------------------------------- /KUDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /KUDemo.xcodeproj/xcuserdata/jonny.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | KUDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | KeyboardUtility.xcscheme 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /KUDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // KUDemo 4 | // 5 | // Created by Jonny on 9/24/17. 6 | // Copyright © 2017 Jonny. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | 19 | window = UIWindow() 20 | window?.rootViewController = ViewController() 21 | window?.makeKeyAndVisible() 22 | 23 | return true 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /KUDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /KUDemo/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 | -------------------------------------------------------------------------------- /KUDemo/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 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /KUDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // KUDemo 4 | // 5 | // Created by Jonny on 9/24/17. 6 | // Copyright © 2017 Jonny. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import KeyboardUtility 11 | 12 | class ViewController: UIViewController { 13 | 14 | private lazy var keyboardLayoutGuide: UILayoutGuide = { 15 | let guide = KeyboardLayoutGuide() 16 | view.addLayoutGuide(guide) 17 | return guide 18 | }() 19 | 20 | /// A text field attached on top of the keyboard. 21 | private lazy var textField: UITextField = { 22 | let textField = UITextField() 23 | textField.borderStyle = .roundedRect 24 | textField.returnKeyType = .done 25 | textField.placeholder = "Input something..." 26 | 27 | // tap return key to dismiss keyboard 28 | textField.addTarget(self, action: #selector(editingDidEndOnExit), for: .editingDidEndOnExit) 29 | 30 | view.addSubview(textField) 31 | 32 | textField.translatesAutoresizingMaskIntoConstraints = false 33 | NSLayoutConstraint.activate([ 34 | textField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8), 35 | view.trailingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 8), 36 | keyboardLayoutGuide.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 8), // attach on top the keyboard 37 | ]) 38 | 39 | return textField 40 | }() 41 | 42 | /// A scroll view for interactive keyboard dismissal. 43 | private lazy var scrollView: UIScrollView = { 44 | let scrollView = UIScrollView() 45 | view.addSubview(scrollView) 46 | 47 | scrollView.keyboardDismissMode = .interactive 48 | scrollView.alwaysBounceVertical = true 49 | 50 | scrollView.translatesAutoresizingMaskIntoConstraints = false 51 | NSLayoutConstraint.activate([ 52 | scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor), 53 | scrollView.topAnchor.constraint(equalTo: view.topAnchor), 54 | scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor), 55 | scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor), 56 | ]) 57 | 58 | return scrollView 59 | }() 60 | 61 | override func viewDidLoad() { 62 | super.viewDidLoad() 63 | 64 | view.backgroundColor = .lightGray 65 | 66 | _ = scrollView // for interactive keyboard dismissal. 67 | _ = textField // will be attached on top of the keyboard. 68 | setupKeyboardHighlightView() // make keyboard backgroud red. 69 | 70 | view.layoutIfNeeded() // force update frames for scroll view and text field based on Auto Layout constraints. 71 | 72 | textField.becomeFirstResponder() 73 | } 74 | 75 | private var keyboardFrameObserver: KeyboardFrameObserver? 76 | 77 | private func setupKeyboardHighlightView() { 78 | let frame = CGRect(x: 0, y: view.bounds.height, width: view.bounds.width, height: 0) // setup inital frame to avoid weird animation 79 | let keyboardHighlightView = UIView(frame: frame) 80 | 81 | keyboardHighlightView.backgroundColor = .red 82 | view.addSubview(keyboardHighlightView) 83 | 84 | keyboardFrameObserver = KeyboardFrameObserver(view: view) { [weak keyboardHighlightView] frame, animated in 85 | keyboardHighlightView?.frame = frame 86 | } 87 | } 88 | 89 | @objc private func editingDidEndOnExit() { 90 | print(#function) 91 | } 92 | } 93 | 94 | -------------------------------------------------------------------------------- /KeyboardUtility/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /KeyboardUtility/KeyboardFrameObserver.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyboardFrameObserver.swift 3 | // KeyboardUtility 4 | // 5 | // Created by Jonny on 9/10/17. 6 | // Copyright © 2017 Jonny. All rights reserved. 7 | // 8 | 9 | /// Init to start observation, deinit or call invalidate() to stop observation. 10 | public class KeyboardFrameObserver : NSObject { 11 | 12 | public private(set) weak var view: UIView? 13 | 14 | let updateHandler: (CGRect, Bool) -> Void 15 | 16 | public init(view: UIView, updateHandler: @escaping (_ frame: CGRect, _ animated: Bool) -> Void) { 17 | self.view = view 18 | self.updateHandler = updateHandler 19 | super.init() 20 | KeyboardFrameObserversManager.shared.addObserver(self) 21 | } 22 | 23 | deinit { 24 | print("Deinit:", type(of: self)) 25 | invalidate() 26 | } 27 | 28 | /// Stop observation. 29 | public func invalidate() { 30 | KeyboardFrameObserversManager.shared.removeObserver(self) 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /KeyboardUtility/KeyboardFrameObserversManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyboardFrameObserversManager.swift 3 | // KeyboardUtility 4 | // 5 | // Created by Jonny on 9/10/17. 6 | // Copyright © 2017 Jonny. All rights reserved. 7 | // 8 | 9 | class KeyboardFrameObserversManager : NSObject { 10 | 11 | static let shared = KeyboardFrameObserversManager() 12 | 13 | private override init() { 14 | super.init() 15 | 16 | let center = NotificationCenter.default 17 | center.addObserver(self, selector: #selector(keyboardWillChangeFrame), name: .UIKeyboardWillChangeFrame, object: nil) 18 | center.addObserver(self, selector: #selector(keyboardDidChangeFrame), name: .UIKeyboardDidChangeFrame, object: nil) 19 | } 20 | 21 | private let observers: NSHashTable = NSHashTable.weakObjects() 22 | 23 | private func runOnMainQueue(work: @escaping () -> Void) { 24 | if Thread.isMainThread { 25 | work() 26 | } else { 27 | DispatchQueue.main.async(execute: work) 28 | } 29 | } 30 | 31 | func addObserver(_ observer: KeyboardFrameObserver) { 32 | runOnMainQueue { 33 | self.observers.add(observer) 34 | self.isEnabled = true 35 | 36 | // immediately notify the new observer current keyboard frame. 37 | if let keyboardView = self.keyboardView, let targetView = observer.view { 38 | let frame = targetView.convert(keyboardView.bounds, from: keyboardView as UICoordinateSpace) 39 | observer.updateHandler(frame, false) 40 | } 41 | } 42 | } 43 | 44 | func removeObserver(_ observer: KeyboardFrameObserver) { 45 | runOnMainQueue { 46 | self.observers.remove(observer) 47 | if self.observers.count == 0 { 48 | self.isEnabled = false 49 | } 50 | } 51 | } 52 | 53 | /// Enabled keyboard frame observing only when at least one observer register. 54 | private var isEnabled = false { 55 | didSet { 56 | guard isEnabled != oldValue else { return } 57 | print("KeyboardFrameObserverManager isEnabled", isEnabled) 58 | 59 | if isEnabled { 60 | // KVO host view's frame sometimes can't get updated values. So instead, we use display link pulling. 61 | displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLink)) 62 | displayLink?.add(to: RunLoop.main, forMode: .commonModes) 63 | } else { 64 | displayLink = nil 65 | } 66 | } 67 | } 68 | 69 | private var displayLink: CADisplayLink? { 70 | didSet { 71 | oldValue?.invalidate() 72 | } 73 | } 74 | 75 | /// Previous recorded keyboard frame. 76 | private var keyboardFrame = CGRect.zero 77 | 78 | /// Retrieve new keyboard frame. If frame did change, notify all observers. 79 | /// 80 | /// - Parameters: 81 | /// - forced: A boolean indicated whether update keyboard frame no matter frame changed or not. The default value is false. 82 | /// - animated: A boolean indicated whether the update should be perform with animation. The default value is false. 83 | private func updateKeyboardFrameForObservers(forced: Bool = false, animated: Bool = false) { 84 | guard let keyboardView = keyboardView else { return } 85 | 86 | let newFrame = keyboardView.frame 87 | guard forced || newFrame != keyboardFrame else { return } 88 | 89 | keyboardFrame = newFrame 90 | let keyboardBounds = keyboardView.bounds 91 | 92 | for observer in observers.allObjects { 93 | if let view = observer.view { 94 | let frame = view.convert(keyboardBounds, from: keyboardView as UICoordinateSpace) // iPad Slide Over Undock mode not align 95 | // let frame = view.convert(keyboardFrame, from: UIScreen.main.coordinateSpace) // iPad Slide Over Dock mode not align 96 | observer.updateHandler(frame, animated) 97 | } 98 | } 99 | } 100 | 101 | /// The keyboardView aka UIInputSetHostView used to real-time calculate keyboard's position. 102 | private var keyboardView: UIView? { 103 | if _keyboardView == nil { 104 | struct Classes { 105 | // static let keyboardWindow = NSClassFromString("UIRemoteKeyboardWindow") as? NSObject.Type // for input view 106 | static let textEffectsWindow = NSClassFromString("UITextEffectsWindow") as? NSObject.Type // for input accessory view 107 | static let inputSetContainerView = NSClassFromString("UIInputSetContainerView") as? NSObject.Type 108 | static let inputSetHostView = NSClassFromString("UIInputSetHostView") as? NSObject.Type 109 | } 110 | 111 | // 1. UIRemoteKeyboardWindow > UIInputSetContainerView > UIInputSetHostView 112 | // 2. UITextEffectsWindow > UIInputSetContainerView > UIInputSetHostView 113 | // * UIRemoteKeyboardWindow will be removed and set to nil in iPad Split View when keyboard is hosting on another app, but UITextEffectsWindow won't 114 | 115 | for window in UIApplication.shared.windows.reversed() { 116 | let windowClass = type(of: window) 117 | if windowClass == Classes.textEffectsWindow { 118 | if let containerView = window.subviews.first(where: { type(of: $0) == Classes.inputSetContainerView }), 119 | let hostView = containerView.subviews.first(where: { type(of: $0) == Classes.inputSetHostView }) { 120 | _keyboardView = hostView 121 | break 122 | } 123 | } 124 | } 125 | } 126 | return _keyboardView 127 | } 128 | private weak var _keyboardView: UIView? 129 | 130 | @objc private func handleDisplayLink() { 131 | updateKeyboardFrameForObservers() 132 | } 133 | 134 | @objc private func keyboardWillChangeFrame(_ notification: Notification) { 135 | // endFrame == .zero, means the keyboard's frame is currently freely changed in iPad Undock mode. 136 | let endFrame = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? CGRect ?? .zero 137 | if endFrame != .zero { 138 | updateKeyboardFrameForObservers(animated: true) 139 | } 140 | } 141 | 142 | @objc private func keyboardDidChangeFrame() { 143 | updateKeyboardFrameForObservers(forced: true) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /KeyboardUtility/KeyboardLayoutGuide.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyboardLayoutGuide.swift 3 | // KeyboardUtility 4 | // 5 | // Created by Jonny on 9/10/17. 6 | // Copyright © 2017 Jonny. All rights reserved. 7 | // 8 | 9 | @available (iOS 9.0, *) 10 | public class KeyboardLayoutGuide : UILayoutGuide { 11 | 12 | private var observer: KeyboardFrameObserver? 13 | 14 | public override var owningView: UIView? { 15 | didSet { 16 | guard let view = owningView else { 17 | observer = nil 18 | return 19 | } 20 | 21 | let topConstraint = view.bottomAnchor.constraint(equalTo: topAnchor) 22 | topConstraint.priority = .defaultHigh 23 | 24 | let heightConstraint = heightAnchor.constraint(equalToConstant: 0) 25 | 26 | NSLayoutConstraint.activate([ 27 | leadingAnchor.constraint(equalTo: view.leadingAnchor), 28 | trailingAnchor.constraint(equalTo: view.trailingAnchor), 29 | topConstraint, 30 | heightConstraint, 31 | ]) 32 | 33 | // layoutGuide.layoutFrame.origin.y <= view.bounds.height 34 | if #available(iOS 11.0, *) { 35 | topAnchor.constraint(lessThanOrEqualTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true 36 | } else { 37 | topAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor).isActive = true 38 | } 39 | 40 | observer = KeyboardFrameObserver(view: view) { [weak view] keyboardFrame, animated in 41 | guard let view = view else { return } 42 | 43 | topConstraint.constant = view.bounds.height - keyboardFrame.origin.y 44 | heightConstraint.constant = keyboardFrame.height 45 | 46 | if animated { 47 | view.layoutIfNeeded() 48 | } 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /KeyboardUtility/KeyboardUtility.h: -------------------------------------------------------------------------------- 1 | // 2 | // KeyboardUtility.h 3 | // KeyboardUtility 4 | // 5 | // Created by Jonny on 9/10/17. 6 | // Copyright © 2017 Jonny. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Junyu Kuang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KeyboardUtility 2 | iOS keyboard frame real-time tracking. Support interactive keyboard dismissal and iPad undock keyboard. 3 | 4 | See demo video on YouTube: 5 | https://youtu.be/w3BKTFrxs4I 6 | 7 | # Requirement 8 | - Xcode 9.0 9 | - Swift 4.0 10 | - iOS 9.0+ for auto keyboard frame observation with [UILayoutGuide](https://developer.apple.com/documentation/uikit/uilayoutguide) 11 | - iOS 8.0+ for manual keyboard frame observation 12 | 13 | # Usage 14 | 15 | Auto keyboard frame observation with [UILayoutGuide](https://developer.apple.com/documentation/uikit/uilayoutguide): 16 | ``` swift 17 | let keyboardLayoutGuide = KeyboardLayoutGuide() 18 | view.addLayoutGuide(keyboardLayoutGuide) 19 | 20 | // place a text field on top of the keyboard using auto layout 21 | let textField = UITextField() 22 | textField.borderStyle = .roundedRect 23 | view.addSubview(textField) 24 | 25 | textField.translatesAutoresizingMaskIntoConstraints = false 26 | NSLayoutConstraint.activate([ 27 | textField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8), 28 | view.trailingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 8), 29 | keyboardLayoutGuide.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 8), 30 | ]) 31 | ``` 32 | 33 | Manual keyboard frame observation: 34 | ``` swift 35 | // Strong reference the observer to make sure keyboard frame keep update to date. 36 | // Will stop observation automatically when deinit. 37 | var keyboardFrameObserver: KeyboardFrameObserver? 38 | 39 | /// make keyboard backgroud red. 40 | func setupKeyboardHighlightView() { 41 | let frame = CGRect(x: 0, y: view.bounds.height, width: view.bounds.width, height: 0) // setup inital frame to avoid weird animation 42 | let keyboardHighlightView = UIView(frame: frame) 43 | 44 | keyboardHighlightView.backgroundColor = .red 45 | view.addSubview(keyboardHighlightView) 46 | 47 | keyboardFrameObserver = KeyboardFrameObserver(view: view) { [weak keyboardHighlightView] frame, animated in 48 | // the frame is related to view 49 | keyboardHighlightView?.frame = frame 50 | } 51 | } 52 | ``` 53 | 54 | **Limitation** 55 | 56 | `KeyboardLayoutGuide` is not supported on `UIScrollView`, which means you must add it to a non-scrollable view. 57 | E.g. If you want to track keyboard frame in a `UITableViewController`, do not add it to `tableView`, instead, try add it to `navigationController.view`. 58 | 59 | # Installation 60 | 1. Download KeyboardUtility 61 | 2. Create a new **Swift Cocoa Touch Framework** in your project and name it KeyboardUtility 62 | 3. Drag 4 files **KeyboardUtility.h**, **KeyboardFrameObserver.swift**, **KeyboardFrameObserversManager.swift** and **KeyboardLayoutGuide.swift** from KeyboardUtility into the new KeyboardUtility folder that Xcode create for you 63 | 4. Add the new framework as your other target's **Linked Frameworks and Libraries** 64 | 5. `import KeyboardUtility` 65 | 66 | # Apps use KeyboardUtility 67 | 68 | ### Aurora Dictionary 极光词典 69 | An elegant English-Chinese Chinese-English dictionary 70 | 71 | [App Store](https://itunes.apple.com/app/id1154746981?at=1001lmw6) 72 | 73 | 74 | ### MessageFilter 短信过滤 75 | Block and filter spam SMS messages for iOS 11 76 | 77 | [App Store](https://itunes.apple.com/app/id1262898709?at=1001lmw6) 78 | 79 | 80 | # License - MIT 81 | Copyright (c) 2017 Junyu Kuang 82 | 83 | Permission is hereby granted, free of charge, to any person obtaining a copy 84 | of this software and associated documentation files (the "Software"), to deal 85 | in the Software without restriction, including without limitation the rights 86 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 87 | copies of the Software, and to permit persons to whom the Software is 88 | furnished to do so, subject to the following conditions: 89 | 90 | The above copyright notice and this permission notice shall be included in all 91 | copies or substantial portions of the Software. 92 | 93 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 94 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 95 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 96 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 97 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 98 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 99 | SOFTWARE. 100 | --------------------------------------------------------------------------------