├── .gitignore ├── .swift-version ├── ALTextInputBar.podspec ├── ALTextInputBar.sketch ├── ALTextInputBar.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── Example.xcscheme ├── ALTextInputBar ├── ALKeyboardObservingView.swift ├── ALTextInputBar.swift ├── ALTextInputBarDelegate.swift ├── ALTextInputUtilities.swift └── ALTextView.swift ├── ALTextInputBarTests ├── ALTextInputBarTests.swift └── Supporting Files │ └── Info.plist ├── Example ├── AppDelegate.swift ├── Supporting Files │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── icon@120.png │ │ │ ├── icon@180.png │ │ │ ├── icon@58.png │ │ │ ├── icon@80.png │ │ │ └── icon@87.png │ │ ├── LaunchImage.launchimage │ │ │ ├── Contents.json │ │ │ ├── splash-3.5.png │ │ │ ├── splash-4.7.png │ │ │ ├── splash-4.png │ │ │ └── splash-5.5.png │ │ ├── leftIcon.imageset │ │ │ ├── Contents.json │ │ │ ├── leftIcon.png │ │ │ ├── leftIcon@2x.png │ │ │ └── leftIcon@3x.png │ │ └── rightIcon.imageset │ │ │ ├── Contents.json │ │ │ ├── rightIcon.png │ │ │ ├── rightIcon@2x.png │ │ │ └── rightIcon@3x.png │ └── Info.plist └── ViewController.swift ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | #Pods/ 27 | .DS_Store 28 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 4.0 2 | -------------------------------------------------------------------------------- /ALTextInputBar.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = "ALTextInputBar" 3 | spec.version = "1.1.8" 4 | spec.summary = "An auto growing text input bar for messaging apps" 5 | spec.source = { :git => "https://github.com/AlexLittlejohn/ALTextInputBar.git", :tag => spec.version.to_s } 6 | spec.requires_arc = true 7 | spec.platform = :ios, "8.0" 8 | spec.license = "MIT" 9 | spec.source_files = "ALTextInputBar/*.{swift}" 10 | spec.homepage = "https://github.com/AlexLittlejohn/ALTextInputBar" 11 | spec.author = { "Alex Littlejohn" => "alexlittlejohn@me.com" } 12 | end 13 | -------------------------------------------------------------------------------- /ALTextInputBar.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexLittlejohn/ALTextInputBar/0ceda5cbb3f3e633e750a90cc9418361cd776bfd/ALTextInputBar.sketch -------------------------------------------------------------------------------- /ALTextInputBar.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | EC203D832575158C00DE5607 /* ALTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA61C9671AEAEEC800920E37 /* ALTextView.swift */; }; 11 | EC203D862575158F00DE5607 /* ALTextInputBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA61C9691AEAF01D00920E37 /* ALTextInputBar.swift */; }; 12 | EC203D892575159600DE5607 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA61C9481AEAEC5F00920E37 /* ViewController.swift */; }; 13 | EC203D8C2575159800DE5607 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA61C9461AEAEC5F00920E37 /* AppDelegate.swift */; }; 14 | FA41B0B21AEBC57D00593A04 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FA41B0B01AEBC57D00593A04 /* Images.xcassets */; }; 15 | FA4AE6741B0494200086AEBF /* ALTextInputBarDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA4AE6731B0494200086AEBF /* ALTextInputBarDelegate.swift */; }; 16 | FA4AE6751B0494200086AEBF /* ALTextInputBarDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA4AE6731B0494200086AEBF /* ALTextInputBarDelegate.swift */; }; 17 | FA4AE6771B0494AE0086AEBF /* ALKeyboardObservingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA4AE6761B0494AE0086AEBF /* ALKeyboardObservingView.swift */; }; 18 | FA4AE6781B0494AE0086AEBF /* ALKeyboardObservingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA4AE6761B0494AE0086AEBF /* ALKeyboardObservingView.swift */; }; 19 | FA4AE67A1B0495F60086AEBF /* ALTextInputUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA4AE6791B0495F60086AEBF /* ALTextInputUtilities.swift */; }; 20 | FA4AE67B1B0495F60086AEBF /* ALTextInputUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA4AE6791B0495F60086AEBF /* ALTextInputUtilities.swift */; }; 21 | FA61C9471AEAEC5F00920E37 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA61C9461AEAEC5F00920E37 /* AppDelegate.swift */; }; 22 | FA61C9491AEAEC5F00920E37 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA61C9481AEAEC5F00920E37 /* ViewController.swift */; }; 23 | FA61C95D1AEAEC5F00920E37 /* ALTextInputBarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA61C95C1AEAEC5F00920E37 /* ALTextInputBarTests.swift */; }; 24 | FA61C9681AEAEEC800920E37 /* ALTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA61C9671AEAEEC800920E37 /* ALTextView.swift */; }; 25 | FA61C96A1AEAF01D00920E37 /* ALTextInputBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA61C9691AEAF01D00920E37 /* ALTextInputBar.swift */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXContainerItemProxy section */ 29 | FA61C9571AEAEC5F00920E37 /* PBXContainerItemProxy */ = { 30 | isa = PBXContainerItemProxy; 31 | containerPortal = FA61C9391AEAEC5F00920E37 /* Project object */; 32 | proxyType = 1; 33 | remoteGlobalIDString = FA61C9401AEAEC5F00920E37; 34 | remoteInfo = ALTextInputBar; 35 | }; 36 | /* End PBXContainerItemProxy section */ 37 | 38 | /* Begin PBXFileReference section */ 39 | FA41B0B01AEBC57D00593A04 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 40 | FA41B0B11AEBC57D00593A04 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 41 | FA4AE6731B0494200086AEBF /* ALTextInputBarDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ALTextInputBarDelegate.swift; sourceTree = ""; }; 42 | FA4AE6761B0494AE0086AEBF /* ALKeyboardObservingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ALKeyboardObservingView.swift; sourceTree = ""; }; 43 | FA4AE6791B0495F60086AEBF /* ALTextInputUtilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ALTextInputUtilities.swift; sourceTree = ""; }; 44 | FA61C9411AEAEC5F00920E37 /* ALTextInputBar.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ALTextInputBar.app; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | FA61C9461AEAEC5F00920E37 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 46 | FA61C9481AEAEC5F00920E37 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 47 | FA61C9561AEAEC5F00920E37 /* ALTextInputBarTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ALTextInputBarTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | FA61C95B1AEAEC5F00920E37 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49 | FA61C95C1AEAEC5F00920E37 /* ALTextInputBarTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ALTextInputBarTests.swift; sourceTree = ""; }; 50 | FA61C9671AEAEEC800920E37 /* ALTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ALTextView.swift; sourceTree = ""; }; 51 | FA61C9691AEAF01D00920E37 /* ALTextInputBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ALTextInputBar.swift; sourceTree = ""; }; 52 | /* End PBXFileReference section */ 53 | 54 | /* Begin PBXFrameworksBuildPhase section */ 55 | FA61C93E1AEAEC5F00920E37 /* Frameworks */ = { 56 | isa = PBXFrameworksBuildPhase; 57 | buildActionMask = 2147483647; 58 | files = ( 59 | ); 60 | runOnlyForDeploymentPostprocessing = 0; 61 | }; 62 | FA61C9531AEAEC5F00920E37 /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 2147483647; 65 | files = ( 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | /* End PBXFrameworksBuildPhase section */ 70 | 71 | /* Begin PBXGroup section */ 72 | FA61C9381AEAEC5F00920E37 = { 73 | isa = PBXGroup; 74 | children = ( 75 | FA61C9661AEAEC6F00920E37 /* Example */, 76 | FA61C9431AEAEC5F00920E37 /* ALTextInputBar */, 77 | FA61C9591AEAEC5F00920E37 /* ALTextInputBarTests */, 78 | FA61C9421AEAEC5F00920E37 /* Products */, 79 | ); 80 | sourceTree = ""; 81 | }; 82 | FA61C9421AEAEC5F00920E37 /* Products */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | FA61C9411AEAEC5F00920E37 /* ALTextInputBar.app */, 86 | FA61C9561AEAEC5F00920E37 /* ALTextInputBarTests.xctest */, 87 | ); 88 | name = Products; 89 | sourceTree = ""; 90 | }; 91 | FA61C9431AEAEC5F00920E37 /* ALTextInputBar */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | FA61C9671AEAEEC800920E37 /* ALTextView.swift */, 95 | FA61C9691AEAF01D00920E37 /* ALTextInputBar.swift */, 96 | FA4AE6731B0494200086AEBF /* ALTextInputBarDelegate.swift */, 97 | FA4AE6761B0494AE0086AEBF /* ALKeyboardObservingView.swift */, 98 | FA4AE6791B0495F60086AEBF /* ALTextInputUtilities.swift */, 99 | ); 100 | path = ALTextInputBar; 101 | sourceTree = ""; 102 | }; 103 | FA61C9441AEAEC5F00920E37 /* Supporting Files */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | FA41B0B01AEBC57D00593A04 /* Images.xcassets */, 107 | FA41B0B11AEBC57D00593A04 /* Info.plist */, 108 | ); 109 | path = "Supporting Files"; 110 | sourceTree = ""; 111 | }; 112 | FA61C9591AEAEC5F00920E37 /* ALTextInputBarTests */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | FA61C95C1AEAEC5F00920E37 /* ALTextInputBarTests.swift */, 116 | FA61C95A1AEAEC5F00920E37 /* Supporting Files */, 117 | ); 118 | path = ALTextInputBarTests; 119 | sourceTree = ""; 120 | }; 121 | FA61C95A1AEAEC5F00920E37 /* Supporting Files */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | FA61C95B1AEAEC5F00920E37 /* Info.plist */, 125 | ); 126 | path = "Supporting Files"; 127 | sourceTree = ""; 128 | }; 129 | FA61C9661AEAEC6F00920E37 /* Example */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | FA61C9461AEAEC5F00920E37 /* AppDelegate.swift */, 133 | FA61C9481AEAEC5F00920E37 /* ViewController.swift */, 134 | FA61C9441AEAEC5F00920E37 /* Supporting Files */, 135 | ); 136 | path = Example; 137 | sourceTree = ""; 138 | }; 139 | /* End PBXGroup section */ 140 | 141 | /* Begin PBXNativeTarget section */ 142 | FA61C9401AEAEC5F00920E37 /* ALTextInputBar */ = { 143 | isa = PBXNativeTarget; 144 | buildConfigurationList = FA61C9601AEAEC5F00920E37 /* Build configuration list for PBXNativeTarget "ALTextInputBar" */; 145 | buildPhases = ( 146 | FA61C93D1AEAEC5F00920E37 /* Sources */, 147 | FA61C93E1AEAEC5F00920E37 /* Frameworks */, 148 | FA61C93F1AEAEC5F00920E37 /* Resources */, 149 | ); 150 | buildRules = ( 151 | ); 152 | dependencies = ( 153 | ); 154 | name = ALTextInputBar; 155 | productName = ALTextInputBar; 156 | productReference = FA61C9411AEAEC5F00920E37 /* ALTextInputBar.app */; 157 | productType = "com.apple.product-type.application"; 158 | }; 159 | FA61C9551AEAEC5F00920E37 /* ALTextInputBarTests */ = { 160 | isa = PBXNativeTarget; 161 | buildConfigurationList = FA61C9631AEAEC5F00920E37 /* Build configuration list for PBXNativeTarget "ALTextInputBarTests" */; 162 | buildPhases = ( 163 | FA61C9521AEAEC5F00920E37 /* Sources */, 164 | FA61C9531AEAEC5F00920E37 /* Frameworks */, 165 | FA61C9541AEAEC5F00920E37 /* Resources */, 166 | ); 167 | buildRules = ( 168 | ); 169 | dependencies = ( 170 | FA61C9581AEAEC5F00920E37 /* PBXTargetDependency */, 171 | ); 172 | name = ALTextInputBarTests; 173 | productName = ALTextInputBarTests; 174 | productReference = FA61C9561AEAEC5F00920E37 /* ALTextInputBarTests.xctest */; 175 | productType = "com.apple.product-type.bundle.unit-test"; 176 | }; 177 | /* End PBXNativeTarget section */ 178 | 179 | /* Begin PBXProject section */ 180 | FA61C9391AEAEC5F00920E37 /* Project object */ = { 181 | isa = PBXProject; 182 | attributes = { 183 | LastSwiftMigration = 0700; 184 | LastSwiftUpdateCheck = 0700; 185 | LastUpgradeCheck = 1220; 186 | ORGANIZATIONNAME = zero; 187 | TargetAttributes = { 188 | FA61C9401AEAEC5F00920E37 = { 189 | CreatedOnToolsVersion = 6.3.1; 190 | DevelopmentTeam = GAPQH4AM76; 191 | LastSwiftMigration = 1220; 192 | }; 193 | FA61C9551AEAEC5F00920E37 = { 194 | CreatedOnToolsVersion = 6.3.1; 195 | LastSwiftMigration = 1220; 196 | TestTargetID = FA61C9401AEAEC5F00920E37; 197 | }; 198 | }; 199 | }; 200 | buildConfigurationList = FA61C93C1AEAEC5F00920E37 /* Build configuration list for PBXProject "ALTextInputBar" */; 201 | compatibilityVersion = "Xcode 3.2"; 202 | developmentRegion = en; 203 | hasScannedForEncodings = 0; 204 | knownRegions = ( 205 | en, 206 | Base, 207 | ); 208 | mainGroup = FA61C9381AEAEC5F00920E37; 209 | productRefGroup = FA61C9421AEAEC5F00920E37 /* Products */; 210 | projectDirPath = ""; 211 | projectRoot = ""; 212 | targets = ( 213 | FA61C9401AEAEC5F00920E37 /* ALTextInputBar */, 214 | FA61C9551AEAEC5F00920E37 /* ALTextInputBarTests */, 215 | ); 216 | }; 217 | /* End PBXProject section */ 218 | 219 | /* Begin PBXResourcesBuildPhase section */ 220 | FA61C93F1AEAEC5F00920E37 /* Resources */ = { 221 | isa = PBXResourcesBuildPhase; 222 | buildActionMask = 2147483647; 223 | files = ( 224 | FA41B0B21AEBC57D00593A04 /* Images.xcassets in Resources */, 225 | ); 226 | runOnlyForDeploymentPostprocessing = 0; 227 | }; 228 | FA61C9541AEAEC5F00920E37 /* Resources */ = { 229 | isa = PBXResourcesBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | ); 233 | runOnlyForDeploymentPostprocessing = 0; 234 | }; 235 | /* End PBXResourcesBuildPhase section */ 236 | 237 | /* Begin PBXSourcesBuildPhase section */ 238 | FA61C93D1AEAEC5F00920E37 /* Sources */ = { 239 | isa = PBXSourcesBuildPhase; 240 | buildActionMask = 2147483647; 241 | files = ( 242 | FA61C9491AEAEC5F00920E37 /* ViewController.swift in Sources */, 243 | FA4AE6741B0494200086AEBF /* ALTextInputBarDelegate.swift in Sources */, 244 | FA4AE6771B0494AE0086AEBF /* ALKeyboardObservingView.swift in Sources */, 245 | FA61C9681AEAEEC800920E37 /* ALTextView.swift in Sources */, 246 | FA61C96A1AEAF01D00920E37 /* ALTextInputBar.swift in Sources */, 247 | FA61C9471AEAEC5F00920E37 /* AppDelegate.swift in Sources */, 248 | FA4AE67A1B0495F60086AEBF /* ALTextInputUtilities.swift in Sources */, 249 | ); 250 | runOnlyForDeploymentPostprocessing = 0; 251 | }; 252 | FA61C9521AEAEC5F00920E37 /* Sources */ = { 253 | isa = PBXSourcesBuildPhase; 254 | buildActionMask = 2147483647; 255 | files = ( 256 | EC203D8C2575159800DE5607 /* AppDelegate.swift in Sources */, 257 | EC203D862575158F00DE5607 /* ALTextInputBar.swift in Sources */, 258 | EC203D832575158C00DE5607 /* ALTextView.swift in Sources */, 259 | FA61C95D1AEAEC5F00920E37 /* ALTextInputBarTests.swift in Sources */, 260 | FA4AE6751B0494200086AEBF /* ALTextInputBarDelegate.swift in Sources */, 261 | EC203D892575159600DE5607 /* ViewController.swift in Sources */, 262 | FA4AE6781B0494AE0086AEBF /* ALKeyboardObservingView.swift in Sources */, 263 | FA4AE67B1B0495F60086AEBF /* ALTextInputUtilities.swift in Sources */, 264 | ); 265 | runOnlyForDeploymentPostprocessing = 0; 266 | }; 267 | /* End PBXSourcesBuildPhase section */ 268 | 269 | /* Begin PBXTargetDependency section */ 270 | FA61C9581AEAEC5F00920E37 /* PBXTargetDependency */ = { 271 | isa = PBXTargetDependency; 272 | target = FA61C9401AEAEC5F00920E37 /* ALTextInputBar */; 273 | targetProxy = FA61C9571AEAEC5F00920E37 /* PBXContainerItemProxy */; 274 | }; 275 | /* End PBXTargetDependency section */ 276 | 277 | /* Begin XCBuildConfiguration section */ 278 | FA61C95E1AEAEC5F00920E37 /* Debug */ = { 279 | isa = XCBuildConfiguration; 280 | buildSettings = { 281 | ALWAYS_SEARCH_USER_PATHS = NO; 282 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 283 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 284 | CLANG_CXX_LIBRARY = "libc++"; 285 | CLANG_ENABLE_MODULES = YES; 286 | CLANG_ENABLE_OBJC_ARC = YES; 287 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 288 | CLANG_WARN_BOOL_CONVERSION = YES; 289 | CLANG_WARN_COMMA = YES; 290 | CLANG_WARN_CONSTANT_CONVERSION = YES; 291 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 292 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 293 | CLANG_WARN_EMPTY_BODY = YES; 294 | CLANG_WARN_ENUM_CONVERSION = YES; 295 | CLANG_WARN_INFINITE_RECURSION = YES; 296 | CLANG_WARN_INT_CONVERSION = YES; 297 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 298 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 299 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 300 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 301 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 302 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 303 | CLANG_WARN_STRICT_PROTOTYPES = YES; 304 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 305 | CLANG_WARN_UNREACHABLE_CODE = YES; 306 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 307 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 308 | COPY_PHASE_STRIP = NO; 309 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 310 | ENABLE_STRICT_OBJC_MSGSEND = YES; 311 | ENABLE_TESTABILITY = YES; 312 | GCC_C_LANGUAGE_STANDARD = gnu99; 313 | GCC_DYNAMIC_NO_PIC = NO; 314 | GCC_NO_COMMON_BLOCKS = YES; 315 | GCC_OPTIMIZATION_LEVEL = 0; 316 | GCC_PREPROCESSOR_DEFINITIONS = ( 317 | "DEBUG=1", 318 | "$(inherited)", 319 | ); 320 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 321 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 322 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 323 | GCC_WARN_UNDECLARED_SELECTOR = YES; 324 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 325 | GCC_WARN_UNUSED_FUNCTION = YES; 326 | GCC_WARN_UNUSED_VARIABLE = YES; 327 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 328 | MTL_ENABLE_DEBUG_INFO = YES; 329 | ONLY_ACTIVE_ARCH = YES; 330 | SDKROOT = iphoneos; 331 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 332 | TARGETED_DEVICE_FAMILY = "1,2"; 333 | }; 334 | name = Debug; 335 | }; 336 | FA61C95F1AEAEC5F00920E37 /* Release */ = { 337 | isa = XCBuildConfiguration; 338 | buildSettings = { 339 | ALWAYS_SEARCH_USER_PATHS = NO; 340 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 341 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 342 | CLANG_CXX_LIBRARY = "libc++"; 343 | CLANG_ENABLE_MODULES = YES; 344 | CLANG_ENABLE_OBJC_ARC = YES; 345 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 346 | CLANG_WARN_BOOL_CONVERSION = YES; 347 | CLANG_WARN_COMMA = YES; 348 | CLANG_WARN_CONSTANT_CONVERSION = YES; 349 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 350 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 351 | CLANG_WARN_EMPTY_BODY = YES; 352 | CLANG_WARN_ENUM_CONVERSION = YES; 353 | CLANG_WARN_INFINITE_RECURSION = YES; 354 | CLANG_WARN_INT_CONVERSION = YES; 355 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 356 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 357 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 358 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 359 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 360 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 361 | CLANG_WARN_STRICT_PROTOTYPES = YES; 362 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 363 | CLANG_WARN_UNREACHABLE_CODE = YES; 364 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 365 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 366 | COPY_PHASE_STRIP = NO; 367 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 368 | ENABLE_NS_ASSERTIONS = NO; 369 | ENABLE_STRICT_OBJC_MSGSEND = YES; 370 | GCC_C_LANGUAGE_STANDARD = gnu99; 371 | GCC_NO_COMMON_BLOCKS = YES; 372 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 373 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 374 | GCC_WARN_UNDECLARED_SELECTOR = YES; 375 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 376 | GCC_WARN_UNUSED_FUNCTION = YES; 377 | GCC_WARN_UNUSED_VARIABLE = YES; 378 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 379 | MTL_ENABLE_DEBUG_INFO = NO; 380 | SDKROOT = iphoneos; 381 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 382 | TARGETED_DEVICE_FAMILY = "1,2"; 383 | VALIDATE_PRODUCT = YES; 384 | }; 385 | name = Release; 386 | }; 387 | FA61C9611AEAEC5F00920E37 /* Debug */ = { 388 | isa = XCBuildConfiguration; 389 | buildSettings = { 390 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 391 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 392 | DEVELOPMENT_TEAM = GAPQH4AM76; 393 | INFOPLIST_FILE = "$(SRCROOT)/Example/Supporting Files/Info.plist"; 394 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 395 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 396 | PRODUCT_BUNDLE_IDENTIFIER = com.alx.zero.TextInputBar; 397 | PRODUCT_NAME = "$(TARGET_NAME)"; 398 | SWIFT_SWIFT3_OBJC_INFERENCE = Off; 399 | SWIFT_VERSION = 5.0; 400 | TARGETED_DEVICE_FAMILY = "1,2"; 401 | }; 402 | name = Debug; 403 | }; 404 | FA61C9621AEAEC5F00920E37 /* Release */ = { 405 | isa = XCBuildConfiguration; 406 | buildSettings = { 407 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 408 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 409 | DEVELOPMENT_TEAM = GAPQH4AM76; 410 | INFOPLIST_FILE = "$(SRCROOT)/Example/Supporting Files/Info.plist"; 411 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 412 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 413 | PRODUCT_BUNDLE_IDENTIFIER = com.zero.TextInputBar; 414 | PRODUCT_NAME = "$(TARGET_NAME)"; 415 | SWIFT_SWIFT3_OBJC_INFERENCE = Off; 416 | SWIFT_VERSION = 5.0; 417 | TARGETED_DEVICE_FAMILY = "1,2"; 418 | }; 419 | name = Release; 420 | }; 421 | FA61C9641AEAEC5F00920E37 /* Debug */ = { 422 | isa = XCBuildConfiguration; 423 | buildSettings = { 424 | BUNDLE_LOADER = "$(TEST_HOST)"; 425 | FRAMEWORK_SEARCH_PATHS = ( 426 | "$(SDKROOT)/Developer/Library/Frameworks", 427 | "$(inherited)", 428 | ); 429 | GCC_PREPROCESSOR_DEFINITIONS = ( 430 | "DEBUG=1", 431 | "$(inherited)", 432 | ); 433 | INFOPLIST_FILE = "ALTextInputBarTests/Supporting Files/Info.plist"; 434 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 435 | PRODUCT_BUNDLE_IDENTIFIER = "com.zero.$(PRODUCT_NAME:rfc1034identifier)"; 436 | PRODUCT_NAME = "$(TARGET_NAME)"; 437 | SWIFT_VERSION = 5.0; 438 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ALTextInputBar.app/ALTextInputBar"; 439 | }; 440 | name = Debug; 441 | }; 442 | FA61C9651AEAEC5F00920E37 /* Release */ = { 443 | isa = XCBuildConfiguration; 444 | buildSettings = { 445 | BUNDLE_LOADER = "$(TEST_HOST)"; 446 | FRAMEWORK_SEARCH_PATHS = ( 447 | "$(SDKROOT)/Developer/Library/Frameworks", 448 | "$(inherited)", 449 | ); 450 | INFOPLIST_FILE = "ALTextInputBarTests/Supporting Files/Info.plist"; 451 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 452 | PRODUCT_BUNDLE_IDENTIFIER = "com.zero.$(PRODUCT_NAME:rfc1034identifier)"; 453 | PRODUCT_NAME = "$(TARGET_NAME)"; 454 | SWIFT_VERSION = 5.0; 455 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ALTextInputBar.app/ALTextInputBar"; 456 | }; 457 | name = Release; 458 | }; 459 | /* End XCBuildConfiguration section */ 460 | 461 | /* Begin XCConfigurationList section */ 462 | FA61C93C1AEAEC5F00920E37 /* Build configuration list for PBXProject "ALTextInputBar" */ = { 463 | isa = XCConfigurationList; 464 | buildConfigurations = ( 465 | FA61C95E1AEAEC5F00920E37 /* Debug */, 466 | FA61C95F1AEAEC5F00920E37 /* Release */, 467 | ); 468 | defaultConfigurationIsVisible = 0; 469 | defaultConfigurationName = Release; 470 | }; 471 | FA61C9601AEAEC5F00920E37 /* Build configuration list for PBXNativeTarget "ALTextInputBar" */ = { 472 | isa = XCConfigurationList; 473 | buildConfigurations = ( 474 | FA61C9611AEAEC5F00920E37 /* Debug */, 475 | FA61C9621AEAEC5F00920E37 /* Release */, 476 | ); 477 | defaultConfigurationIsVisible = 0; 478 | defaultConfigurationName = Release; 479 | }; 480 | FA61C9631AEAEC5F00920E37 /* Build configuration list for PBXNativeTarget "ALTextInputBarTests" */ = { 481 | isa = XCConfigurationList; 482 | buildConfigurations = ( 483 | FA61C9641AEAEC5F00920E37 /* Debug */, 484 | FA61C9651AEAEC5F00920E37 /* Release */, 485 | ); 486 | defaultConfigurationIsVisible = 0; 487 | defaultConfigurationName = Release; 488 | }; 489 | /* End XCConfigurationList section */ 490 | }; 491 | rootObject = FA61C9391AEAEC5F00920E37 /* Project object */; 492 | } 493 | -------------------------------------------------------------------------------- /ALTextInputBar.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ALTextInputBar.xcodeproj/xcshareddata/xcschemes/Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 51 | 52 | 62 | 64 | 70 | 71 | 72 | 73 | 79 | 81 | 87 | 88 | 89 | 90 | 92 | 93 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /ALTextInputBar/ALKeyboardObservingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ALKeyboardObservingView.swift 3 | // ALTextInputBar 4 | // 5 | // Created by Alex Littlejohn on 2015/05/14. 6 | // Copyright (c) 2015 zero. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public let ALKeyboardFrameDidChangeNotification = "ALKeyboardFrameDidChangeNotification" 12 | 13 | public class ALKeyboardObservingView: UIView { 14 | 15 | private weak var observedView: UIView? 16 | private var defaultHeight: CGFloat = 44 17 | 18 | override public var intrinsicContentSize: CGSize { 19 | return CGSize(width: UIView.noIntrinsicMetric, height: defaultHeight) 20 | } 21 | 22 | 23 | 24 | public override func willMove(toSuperview newSuperview: UIView?) { 25 | 26 | removeKeyboardObserver() 27 | if let _newSuperview = newSuperview { 28 | addKeyboardObserver(newSuperview: _newSuperview) 29 | } 30 | 31 | super.willMove(toSuperview: newSuperview) 32 | } 33 | 34 | public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 35 | if object as? NSObject == superview && keyPath == keyboardHandlingKeyPath(), let s = superview { 36 | 37 | let keyboardFrame = s.frame 38 | let screenBounds = UIScreen.main.bounds 39 | let intersectRect = keyboardFrame.intersection(screenBounds) 40 | var keyboardHeight: CGFloat = 0 41 | if !intersectRect.isNull { 42 | keyboardHeight = intersectRect.size.height 43 | } 44 | 45 | let changeRect = CGRect(x: s.frame.origin.x, y: s.frame.origin.y, width: s.frame.width, height: keyboardHeight) 46 | 47 | keyboardDidChangeFrame(frame: changeRect) 48 | } else { 49 | super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) 50 | } 51 | } 52 | 53 | public func updateHeight(height: CGFloat) { 54 | for constraint in constraints { 55 | if constraint.firstAttribute == NSLayoutConstraint.Attribute.height && constraint.firstItem as! NSObject == self { 56 | constraint.constant = height < defaultHeight ? defaultHeight : height 57 | } 58 | } 59 | } 60 | 61 | private func keyboardHandlingKeyPath() -> String { 62 | return "center" 63 | } 64 | 65 | private func addKeyboardObserver(newSuperview: UIView) { 66 | observedView = newSuperview 67 | newSuperview.addObserver(self, forKeyPath: keyboardHandlingKeyPath(), options: NSKeyValueObservingOptions.new, context: nil) 68 | } 69 | 70 | private func removeKeyboardObserver() { 71 | if observedView != nil { 72 | observedView!.removeObserver(self, forKeyPath: keyboardHandlingKeyPath()) 73 | observedView = nil 74 | } 75 | } 76 | 77 | private func keyboardDidChangeFrame(frame: CGRect) { 78 | let userInfo: [AnyHashable : Any] = [UIResponder.keyboardFrameEndUserInfoKey: NSValue(cgRect:frame)] 79 | NotificationCenter.default.post(name: NSNotification.Name(rawValue: ALKeyboardFrameDidChangeNotification), object: nil, userInfo: userInfo) 80 | } 81 | 82 | deinit { 83 | removeKeyboardObserver() 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /ALTextInputBar/ALTextInputBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ALTextInputBar.swift 3 | // ALTextInputBar 4 | // 5 | // Created by Alex Littlejohn on 2015/04/24. 6 | // Copyright (c) 2015 zero. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | open class ALTextInputBar: UIView, ALTextViewDelegate { 12 | 13 | public weak var delegate: ALTextInputBarDelegate? 14 | public weak var keyboardObserver: ALKeyboardObservingView? 15 | 16 | // If true, display a border around the text view 17 | public var showTextViewBorder = false { 18 | didSet { 19 | textViewBorderView.isHidden = !showTextViewBorder 20 | } 21 | } 22 | 23 | // TextView border insets 24 | public var textViewBorderPadding: UIEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) 25 | 26 | // TextView corner radius 27 | public var textViewCornerRadius: CGFloat = 4 { 28 | didSet { 29 | textViewBorderView.layer.cornerRadius = textViewCornerRadius 30 | } 31 | } 32 | 33 | // TextView border width 34 | public var textViewBorderWidth: CGFloat = 1 { 35 | didSet { 36 | textViewBorderView.layer.borderWidth = textViewBorderWidth 37 | } 38 | } 39 | 40 | // TextView border color 41 | public var textViewBorderColor = UIColor(white: 0.9, alpha: 1) { 42 | didSet { 43 | textViewBorderView.layer.borderColor = textViewBorderColor.cgColor 44 | } 45 | } 46 | 47 | // TextView background color 48 | public var textViewBackgroundColor = UIColor.white { 49 | didSet { 50 | textViewBorderView.backgroundColor = textViewBackgroundColor 51 | } 52 | } 53 | 54 | /// Used for the intrinsic content size for autolayout 55 | public var defaultHeight: CGFloat = 44 56 | 57 | /// If true the right button will always be visible else it will only show when there is text in the text view 58 | public var alwaysShowRightButton = false 59 | 60 | /// The horizontal padding between the view edges and its subviews 61 | public var horizontalPadding: CGFloat = 10 62 | 63 | /// The horizontal spacing between subviews 64 | public var horizontalSpacing: CGFloat = 5 65 | 66 | /// Convenience set and retrieve the text view text 67 | public var text: String! { 68 | get { 69 | return textView.text 70 | } 71 | set(newValue) { 72 | textView.text = newValue 73 | textView.delegate?.textViewDidChange?(textView) 74 | } 75 | } 76 | 77 | /** 78 | This view will be displayed on the left of the text view. 79 | 80 | If this view is nil nothing will be displayed, and the text view will fill the space 81 | */ 82 | public var leftView: UIView? { 83 | willSet(newValue) { 84 | if let view = leftView { 85 | view.removeFromSuperview() 86 | } 87 | } 88 | didSet { 89 | if let view = leftView { 90 | addSubview(view) 91 | } 92 | } 93 | } 94 | 95 | /** 96 | This view will be displayed on the right of the text view. 97 | 98 | If this view is nil nothing will be displayed, and the text view will fill the space 99 | If alwaysShowRightButton is false this view will animate in from the right when the text view has content 100 | */ 101 | public var rightView: UIView? { 102 | willSet(newValue) { 103 | if let view = rightView { 104 | view.removeFromSuperview() 105 | } 106 | } 107 | didSet { 108 | if let view = rightView { 109 | addSubview(view) 110 | } 111 | } 112 | } 113 | 114 | /// The text view instance 115 | public let textView: ALTextView = { 116 | 117 | let _textView = ALTextView() 118 | 119 | _textView.textContainerInset = UIEdgeInsets(top: 6, left: 8, bottom: 6, right: 8) 120 | _textView.textContainer.lineFragmentPadding = 0 121 | 122 | _textView.maxNumberOfLines = defaultNumberOfLines() 123 | 124 | _textView.placeholder = "Type here" 125 | _textView.placeholderColor = UIColor.lightGray 126 | 127 | _textView.font = UIFont.systemFont(ofSize: 14) 128 | _textView.textColor = UIColor.darkGray 129 | 130 | _textView.backgroundColor = UIColor.clear 131 | 132 | // This changes the caret color 133 | _textView.tintColor = UIColor.lightGray 134 | 135 | return _textView 136 | }() 137 | 138 | private var showRightButton = false 139 | private var showLeftButton = false 140 | 141 | private var textViewBorderView: UIView! 142 | 143 | override public init(frame: CGRect) { 144 | super.init(frame: frame) 145 | commonInit() 146 | } 147 | 148 | required public init?(coder aDecoder: NSCoder) { 149 | super.init(coder: aDecoder) 150 | commonInit() 151 | } 152 | 153 | private func commonInit() { 154 | 155 | textViewBorderView = createBorderView() 156 | 157 | addSubview(textViewBorderView) 158 | addSubview(textView) 159 | 160 | textViewBorderView.isHidden = !showTextViewBorder 161 | textView.textViewDelegate = self 162 | 163 | backgroundColor = UIColor.groupTableViewBackground 164 | } 165 | 166 | private func createBorderView() -> UIView { 167 | let borderView = UIView() 168 | 169 | borderView.backgroundColor = textViewBackgroundColor 170 | borderView.layer.borderColor = textViewBorderColor.cgColor 171 | borderView.layer.borderWidth = textViewBorderWidth 172 | borderView.layer.cornerRadius = textViewCornerRadius 173 | 174 | 175 | return borderView 176 | } 177 | 178 | // MARK: - View positioning and layout - 179 | 180 | override open var intrinsicContentSize: CGSize { 181 | return CGSize(width: UIView.noIntrinsicMetric, height: defaultHeight) 182 | } 183 | 184 | override open func layoutSubviews() { 185 | super.layoutSubviews() 186 | 187 | let size = frame.size 188 | let height = floor(size.height) 189 | 190 | var leftViewSize = CGSize.zero 191 | var rightViewSize = CGSize.zero 192 | 193 | if let view = leftView { 194 | leftViewSize = view.bounds.size 195 | 196 | let leftViewX: CGFloat = horizontalPadding 197 | let leftViewVerticalPadding = (defaultHeight - leftViewSize.height) / 2 198 | let leftViewY: CGFloat = height - (leftViewSize.height + leftViewVerticalPadding) 199 | 200 | UIView.performWithoutAnimation { 201 | view.frame = CGRect(x: leftViewX, y: leftViewY, width: leftViewSize.width, height: leftViewSize.height) 202 | } 203 | } 204 | 205 | if let view = rightView { 206 | rightViewSize = view.bounds.size 207 | 208 | let rightViewVerticalPadding = (defaultHeight - rightViewSize.height) / 2 209 | var rightViewX = size.width 210 | let rightViewY = height - (rightViewSize.height + rightViewVerticalPadding) 211 | 212 | if showRightButton || alwaysShowRightButton { 213 | rightViewX -= (rightViewSize.width + horizontalPadding) 214 | } 215 | 216 | view.frame = CGRect(x: rightViewX, y: rightViewY, width: rightViewSize.width, height: rightViewSize.height) 217 | } 218 | 219 | let textViewPadding = (defaultHeight - textView.minimumHeight) / 2 220 | var textViewX = horizontalPadding 221 | let textViewY = textViewPadding 222 | let textViewHeight = textView.expectedHeight 223 | var textViewWidth = size.width - (horizontalPadding + horizontalPadding) 224 | 225 | if leftViewSize.width > 0 { 226 | textViewX += leftViewSize.width + horizontalSpacing 227 | textViewWidth -= leftViewSize.width + horizontalSpacing 228 | } 229 | 230 | if showTextViewBorder { 231 | textViewX += textViewBorderPadding.left 232 | textViewWidth -= textViewBorderPadding.left + textViewBorderPadding.right 233 | } 234 | 235 | if (showRightButton || alwaysShowRightButton) && rightViewSize.width > 0 { 236 | textViewWidth -= (horizontalSpacing + rightViewSize.width) 237 | } else { 238 | 239 | } 240 | 241 | textView.frame = CGRect(x: textViewX, y: textViewY, width: textViewWidth, height: textViewHeight) 242 | 243 | let offset = UIEdgeInsets.init(top: -textViewBorderPadding.top, left: -textViewBorderPadding.left, bottom: -textViewBorderPadding.bottom, right: -textViewBorderPadding.right) 244 | textViewBorderView.frame = textView.frame.inset(by: offset) 245 | } 246 | 247 | public func updateViews(animated: Bool) { 248 | if animated { 249 | UIView.animate(withDuration: 0.2) { 250 | self.setNeedsLayout() 251 | self.layoutIfNeeded() 252 | } 253 | 254 | } else { 255 | setNeedsLayout() 256 | layoutIfNeeded() 257 | } 258 | } 259 | 260 | // MARK: - ALTextViewDelegate - 261 | 262 | public final func textViewHeightChanged(textView: ALTextView, newHeight: CGFloat) { 263 | 264 | let padding = defaultHeight - textView.minimumHeight 265 | let height = padding + newHeight 266 | 267 | for constraint in constraints { 268 | if constraint.firstAttribute == NSLayoutConstraint.Attribute.height && constraint.firstItem as! NSObject == self { 269 | constraint.constant = height < defaultHeight ? defaultHeight : height 270 | } 271 | } 272 | 273 | frame.size.height = height 274 | 275 | if let ko = keyboardObserver { 276 | ko.updateHeight(height: height) 277 | } 278 | 279 | if let d = delegate, let m = d.inputBarDidChangeHeight { 280 | m(height) 281 | } 282 | 283 | textView.frame.size.height = newHeight 284 | } 285 | 286 | public final func textViewDidChange(_ textView: UITextView) { 287 | 288 | self.textView.textViewDidChange() 289 | 290 | let shouldShowButton = !textView.text.isEmpty 291 | 292 | if showRightButton != shouldShowButton && !alwaysShowRightButton { 293 | showRightButton = shouldShowButton 294 | updateViews(animated: true) 295 | } 296 | 297 | 298 | if let d = delegate, let m = d.textViewDidChange { 299 | m(self.textView) 300 | } 301 | } 302 | 303 | public func textViewShouldBeginEditing(_ textView: UITextView) -> Bool { 304 | var beginEditing: Bool = true 305 | if let d = delegate, let m = d.textViewShouldBeginEditing { 306 | beginEditing = m(self.textView) 307 | } 308 | return beginEditing 309 | } 310 | 311 | public func textViewShouldEndEditing(_ textView: UITextView) -> Bool { 312 | var endEditing = true 313 | if let d = delegate, let m = d.textViewShouldEndEditing { 314 | endEditing = m(self.textView) 315 | } 316 | return endEditing 317 | } 318 | 319 | public func textViewDidBeginEditing(_ textView: UITextView) { 320 | if let d = delegate, let m = d.textViewDidBeginEditing { 321 | m(self.textView) 322 | } 323 | } 324 | 325 | public func textViewDidEndEditing(_ textView: UITextView) { 326 | if let d = delegate, let m = d.textViewDidEndEditing { 327 | m(self.textView) 328 | } 329 | } 330 | 331 | public func textViewDidChangeSelection(_ textView: UITextView) { 332 | if let d = delegate, let m = d.textViewDidChangeSelection { 333 | m(self.textView) 334 | } 335 | } 336 | 337 | public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { 338 | var shouldChange = true 339 | if let d = delegate, let m = d.textView { 340 | shouldChange = m(self.textView, range, text) 341 | } 342 | return shouldChange 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /ALTextInputBar/ALTextInputBarDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ALTextInputBarDelegate.swift 3 | // ALTextInputBar 4 | // 5 | // Created by Alex Littlejohn on 2015/05/14. 6 | // Copyright (c) 2015 zero. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @objc 12 | public protocol ALTextInputBarDelegate: NSObjectProtocol { 13 | @objc optional func textViewShouldBeginEditing(textView: ALTextView) -> Bool 14 | @objc optional func textViewShouldEndEditing(textView: ALTextView) -> Bool 15 | 16 | @objc optional func textViewDidBeginEditing(textView: ALTextView) 17 | @objc optional func textViewDidEndEditing(textView: ALTextView) 18 | 19 | @objc optional func textViewDidChange(textView: ALTextView) 20 | @objc optional func textViewDidChangeSelection(textView: ALTextView) 21 | 22 | @objc optional func textView(textView: ALTextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool 23 | 24 | @objc optional func inputBarDidChangeHeight(height: CGFloat) 25 | } 26 | -------------------------------------------------------------------------------- /ALTextInputBar/ALTextInputUtilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ALTextInputUtilities.swift 3 | // ALTextInputBar 4 | // 5 | // Created by Alex Littlejohn on 2015/05/14. 6 | // Copyright (c) 2015 zero. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | internal func defaultNumberOfLines() -> CGFloat { 12 | if UIDevice.isIPad { 13 | return 8; 14 | } 15 | if UIDevice.isIPhone4 { 16 | return 4; 17 | } 18 | 19 | return 6; 20 | } 21 | 22 | internal extension UIDevice { 23 | internal static var isIPad: Bool { 24 | return UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad 25 | } 26 | 27 | internal static var isIPhone: Bool { 28 | return UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.phone 29 | } 30 | 31 | internal static var isIPhone4: Bool { 32 | return UIDevice.isIPhone && UIScreen.main.bounds.size.height < 568.0 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ALTextInputBar/ALTextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ALTextView.swift 3 | // ALTextInputBar 4 | // 5 | // Created by Alex Littlejohn on 2015/04/24. 6 | // Copyright (c) 2015 zero. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public protocol ALTextViewDelegate: UITextViewDelegate { 12 | 13 | /** 14 | Notifies the receiver of a change to the contentSize of the textView 15 | 16 | The receiver is responsible for layout 17 | 18 | - parameter textView: The text view that triggered the size change 19 | - parameter newHeight: The ideal height for the new text view 20 | */ 21 | func textViewHeightChanged(textView: ALTextView, newHeight: CGFloat) 22 | } 23 | 24 | public class ALTextView: UITextView { 25 | 26 | override public var font: UIFont? { 27 | didSet { 28 | placeholderLabel.font = font 29 | } 30 | } 31 | 32 | override public var contentSize: CGSize { 33 | didSet { 34 | updateSize() 35 | } 36 | } 37 | 38 | /// The delegate object to be notified if the content size will change 39 | /// The delegate should update handle text view layout 40 | public weak var textViewDelegate: ALTextViewDelegate? { 41 | didSet { 42 | delegate = textViewDelegate 43 | } 44 | } 45 | 46 | /// The text that appears as a placeholder when the text view is empty 47 | public var placeholder: String = "" { 48 | didSet { 49 | placeholderLabel.text = placeholder 50 | } 51 | } 52 | 53 | /// The color of the placeholder text 54 | public var placeholderColor: UIColor! { 55 | get { 56 | return placeholderLabel.textColor 57 | } 58 | set(newValue) { 59 | placeholderLabel.textColor = newValue 60 | } 61 | } 62 | 63 | private lazy var placeholderLabel: UILabel = { 64 | var _placeholderLabel = UILabel() 65 | 66 | _placeholderLabel.clipsToBounds = false 67 | _placeholderLabel.autoresizesSubviews = false 68 | _placeholderLabel.numberOfLines = 1 69 | _placeholderLabel.font = self.font 70 | _placeholderLabel.backgroundColor = UIColor.clear 71 | _placeholderLabel.textColor = self.tintColor 72 | _placeholderLabel.isHidden = true 73 | 74 | self.addSubview(_placeholderLabel) 75 | 76 | return _placeholderLabel 77 | }() 78 | 79 | public override var textAlignment: NSTextAlignment { 80 | get { 81 | return super.textAlignment 82 | } 83 | set { 84 | super.textAlignment = newValue 85 | placeholderLabel.textAlignment = newValue 86 | } 87 | } 88 | 89 | /// The maximum number of lines that will be shown before the text view will scroll. 0 = no limit 90 | public var maxNumberOfLines: CGFloat = 0 91 | public var expectedHeight: CGFloat = 0 92 | public var minimumHeight: CGFloat { 93 | get { 94 | return ceil(font!.lineHeight) + textContainerInset.top + textContainerInset.bottom 95 | } 96 | } 97 | 98 | override public init(frame: CGRect, textContainer: NSTextContainer?) { 99 | super.init(frame: frame, textContainer: textContainer) 100 | commonInit() 101 | } 102 | 103 | required public init?(coder aDecoder: NSCoder) { 104 | super.init(coder: aDecoder) 105 | commonInit() 106 | } 107 | 108 | private func commonInit() { 109 | isScrollEnabled = false 110 | } 111 | 112 | override public func layoutSubviews() { 113 | super.layoutSubviews() 114 | 115 | placeholderLabel.isHidden = shouldHidePlaceholder() 116 | if !placeholderLabel.isHidden { 117 | placeholderLabel.frame = placeholderRectThatFits(rect: bounds) 118 | sendSubviewToBack(placeholderLabel) 119 | } 120 | } 121 | 122 | //MARK: - Sizing and scrolling - 123 | /** 124 | Notify the delegate of size changes if necessary 125 | */ 126 | private func updateSize() { 127 | 128 | var maxHeight = CGFloat.greatestFiniteMagnitude 129 | 130 | if maxNumberOfLines > 0 { 131 | maxHeight = (ceil(font!.lineHeight) * maxNumberOfLines) + textContainerInset.top + textContainerInset.bottom 132 | } 133 | 134 | let roundedHeight = roundHeight() 135 | 136 | if roundedHeight >= maxHeight { 137 | expectedHeight = maxHeight 138 | isScrollEnabled = true 139 | } else { 140 | expectedHeight = roundedHeight 141 | isScrollEnabled = false 142 | } 143 | 144 | textViewDelegate?.textViewHeightChanged(textView: self, newHeight:expectedHeight) 145 | 146 | ensureCaretDisplaysCorrectly() 147 | } 148 | 149 | /** 150 | Calculates the correct height for the text currently in the textview as we cannot rely on contentsize to do the right thing 151 | */ 152 | private func roundHeight() -> CGFloat { 153 | var newHeight: CGFloat = 0 154 | 155 | if let font = font { 156 | let attributes = [NSAttributedString.Key.font: font] 157 | let boundingSize = CGSize(width: frame.size.width - textContainerInset.left - textContainerInset.right, height: .greatestFiniteMagnitude) 158 | let size = text.boundingRect(with: boundingSize, options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: attributes, context: nil) 159 | newHeight = ceil(size.height) 160 | } 161 | 162 | if let font = font, newHeight < font.lineHeight { 163 | newHeight = font.lineHeight 164 | } 165 | 166 | return newHeight + textContainerInset.top + textContainerInset.bottom 167 | } 168 | 169 | /** 170 | Ensure that when the text view is resized that the caret displays correctly withing the visible space 171 | */ 172 | private func ensureCaretDisplaysCorrectly() { 173 | guard let range = selectedTextRange else { 174 | return 175 | } 176 | 177 | DispatchQueue.main.async { 178 | let rect = self.caretRect(for: range.end) 179 | UIView.performWithoutAnimation({ () -> Void in 180 | self.scrollRectToVisible(rect, animated: false) 181 | }) 182 | } 183 | } 184 | 185 | //MARK: - Placeholder Layout - 186 | 187 | /** 188 | Determines if the placeholder should be hidden dependant on whether it was set and if there is text in the text view 189 | 190 | - returns: true if it should not be visible 191 | */ 192 | private func shouldHidePlaceholder() -> Bool { 193 | return placeholder.isEmpty || !text.isEmpty 194 | } 195 | 196 | /** 197 | Layout the placeholder label to fit in the rect specified 198 | 199 | - parameter rect: The constrained size in which to fit the label 200 | - returns: The placeholder label frame 201 | */ 202 | private func placeholderRectThatFits(rect: CGRect) -> CGRect { 203 | let padding = textContainer.lineFragmentPadding 204 | var placeHolderRect = rect.inset(by: textContainerInset) 205 | placeHolderRect.origin.x += padding 206 | placeHolderRect.size.width -= padding * 2 207 | 208 | return placeHolderRect 209 | } 210 | 211 | //MARK: - Notifications - 212 | 213 | internal func textViewDidChange() { 214 | placeholderLabel.isHidden = shouldHidePlaceholder() 215 | updateSize() 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /ALTextInputBarTests/ALTextInputBarTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ALTextInputBarTests.swift 3 | // ALTextInputBarTests 4 | // 5 | // Created by Alex Littlejohn on 2015/04/24. 6 | // Copyright (c) 2015 zero. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class ALTextInputBarTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | XCTAssert(true, "Pass") 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure() { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /ALTextInputBarTests/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ALTextInputBar 4 | // 5 | // Created by Alex Littlejohn on 2015/04/24. 6 | // Copyright (c) 2015 zero. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 18 | 19 | // Override point for customization after application launch. 20 | 21 | window = UIWindow(frame: UIScreen.main.bounds) 22 | window?.rootViewController = ViewController() 23 | window?.makeKeyAndVisible() 24 | 25 | return true 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /Example/Supporting Files/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "29x29", 5 | "idiom" : "iphone", 6 | "filename" : "icon@58.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "29x29", 11 | "idiom" : "iphone", 12 | "filename" : "icon@87.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "40x40", 17 | "idiom" : "iphone", 18 | "filename" : "icon@80.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "40x40", 23 | "idiom" : "iphone", 24 | "filename" : "icon@120.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "idiom" : "iphone", 29 | "size" : "60x60", 30 | "filename" : "icon@120.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "60x60", 35 | "idiom" : "iphone", 36 | "filename" : "icon@180.png", 37 | "scale" : "3x" 38 | } 39 | ], 40 | "info" : { 41 | "version" : 1, 42 | "author" : "xcode" 43 | } 44 | } -------------------------------------------------------------------------------- /Example/Supporting Files/Images.xcassets/AppIcon.appiconset/icon@120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexLittlejohn/ALTextInputBar/0ceda5cbb3f3e633e750a90cc9418361cd776bfd/Example/Supporting Files/Images.xcassets/AppIcon.appiconset/icon@120.png -------------------------------------------------------------------------------- /Example/Supporting Files/Images.xcassets/AppIcon.appiconset/icon@180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexLittlejohn/ALTextInputBar/0ceda5cbb3f3e633e750a90cc9418361cd776bfd/Example/Supporting Files/Images.xcassets/AppIcon.appiconset/icon@180.png -------------------------------------------------------------------------------- /Example/Supporting Files/Images.xcassets/AppIcon.appiconset/icon@58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexLittlejohn/ALTextInputBar/0ceda5cbb3f3e633e750a90cc9418361cd776bfd/Example/Supporting Files/Images.xcassets/AppIcon.appiconset/icon@58.png -------------------------------------------------------------------------------- /Example/Supporting Files/Images.xcassets/AppIcon.appiconset/icon@80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexLittlejohn/ALTextInputBar/0ceda5cbb3f3e633e750a90cc9418361cd776bfd/Example/Supporting Files/Images.xcassets/AppIcon.appiconset/icon@80.png -------------------------------------------------------------------------------- /Example/Supporting Files/Images.xcassets/AppIcon.appiconset/icon@87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexLittlejohn/ALTextInputBar/0ceda5cbb3f3e633e750a90cc9418361cd776bfd/Example/Supporting Files/Images.xcassets/AppIcon.appiconset/icon@87.png -------------------------------------------------------------------------------- /Example/Supporting Files/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "extent" : "full-screen", 5 | "idiom" : "iphone", 6 | "subtype" : "736h", 7 | "filename" : "splash-5.5.png", 8 | "minimum-system-version" : "8.0", 9 | "orientation" : "portrait", 10 | "scale" : "3x" 11 | }, 12 | { 13 | "extent" : "full-screen", 14 | "idiom" : "iphone", 15 | "subtype" : "667h", 16 | "filename" : "splash-4.7.png", 17 | "minimum-system-version" : "8.0", 18 | "orientation" : "portrait", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "orientation" : "portrait", 23 | "idiom" : "iphone", 24 | "extent" : "full-screen", 25 | "minimum-system-version" : "7.0", 26 | "filename" : "splash-3.5.png", 27 | "scale" : "2x" 28 | }, 29 | { 30 | "extent" : "full-screen", 31 | "idiom" : "iphone", 32 | "subtype" : "retina4", 33 | "filename" : "splash-4.png", 34 | "minimum-system-version" : "7.0", 35 | "orientation" : "portrait", 36 | "scale" : "2x" 37 | } 38 | ], 39 | "info" : { 40 | "version" : 1, 41 | "author" : "xcode" 42 | } 43 | } -------------------------------------------------------------------------------- /Example/Supporting Files/Images.xcassets/LaunchImage.launchimage/splash-3.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexLittlejohn/ALTextInputBar/0ceda5cbb3f3e633e750a90cc9418361cd776bfd/Example/Supporting Files/Images.xcassets/LaunchImage.launchimage/splash-3.5.png -------------------------------------------------------------------------------- /Example/Supporting Files/Images.xcassets/LaunchImage.launchimage/splash-4.7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexLittlejohn/ALTextInputBar/0ceda5cbb3f3e633e750a90cc9418361cd776bfd/Example/Supporting Files/Images.xcassets/LaunchImage.launchimage/splash-4.7.png -------------------------------------------------------------------------------- /Example/Supporting Files/Images.xcassets/LaunchImage.launchimage/splash-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexLittlejohn/ALTextInputBar/0ceda5cbb3f3e633e750a90cc9418361cd776bfd/Example/Supporting Files/Images.xcassets/LaunchImage.launchimage/splash-4.png -------------------------------------------------------------------------------- /Example/Supporting Files/Images.xcassets/LaunchImage.launchimage/splash-5.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexLittlejohn/ALTextInputBar/0ceda5cbb3f3e633e750a90cc9418361cd776bfd/Example/Supporting Files/Images.xcassets/LaunchImage.launchimage/splash-5.5.png -------------------------------------------------------------------------------- /Example/Supporting Files/Images.xcassets/leftIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "leftIcon.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "leftIcon@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "leftIcon@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Supporting Files/Images.xcassets/leftIcon.imageset/leftIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexLittlejohn/ALTextInputBar/0ceda5cbb3f3e633e750a90cc9418361cd776bfd/Example/Supporting Files/Images.xcassets/leftIcon.imageset/leftIcon.png -------------------------------------------------------------------------------- /Example/Supporting Files/Images.xcassets/leftIcon.imageset/leftIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexLittlejohn/ALTextInputBar/0ceda5cbb3f3e633e750a90cc9418361cd776bfd/Example/Supporting Files/Images.xcassets/leftIcon.imageset/leftIcon@2x.png -------------------------------------------------------------------------------- /Example/Supporting Files/Images.xcassets/leftIcon.imageset/leftIcon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexLittlejohn/ALTextInputBar/0ceda5cbb3f3e633e750a90cc9418361cd776bfd/Example/Supporting Files/Images.xcassets/leftIcon.imageset/leftIcon@3x.png -------------------------------------------------------------------------------- /Example/Supporting Files/Images.xcassets/rightIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "rightIcon.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "rightIcon@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "rightIcon@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Supporting Files/Images.xcassets/rightIcon.imageset/rightIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexLittlejohn/ALTextInputBar/0ceda5cbb3f3e633e750a90cc9418361cd776bfd/Example/Supporting Files/Images.xcassets/rightIcon.imageset/rightIcon.png -------------------------------------------------------------------------------- /Example/Supporting Files/Images.xcassets/rightIcon.imageset/rightIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexLittlejohn/ALTextInputBar/0ceda5cbb3f3e633e750a90cc9418361cd776bfd/Example/Supporting Files/Images.xcassets/rightIcon.imageset/rightIcon@2x.png -------------------------------------------------------------------------------- /Example/Supporting Files/Images.xcassets/rightIcon.imageset/rightIcon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexLittlejohn/ALTextInputBar/0ceda5cbb3f3e633e750a90cc9418361cd776bfd/Example/Supporting Files/Images.xcassets/rightIcon.imageset/rightIcon@3x.png -------------------------------------------------------------------------------- /Example/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0.9 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UIRequiresFullScreen 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // ALTextInputBar 4 | // 5 | // Created by Alex Littlejohn on 2015/04/24. 6 | // Copyright (c) 2015 zero. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | let textInputBar = ALTextInputBar() 14 | let keyboardObserver = ALKeyboardObservingView() 15 | 16 | let scrollView = UIScrollView() 17 | 18 | // This is how we observe the keyboard position 19 | override var inputAccessoryView: UIView? { 20 | get { 21 | return keyboardObserver 22 | } 23 | } 24 | 25 | // This is also required 26 | override var canBecomeFirstResponder: Bool { 27 | return true 28 | } 29 | 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | 33 | configureScrollView() 34 | configureInputBar() 35 | 36 | NotificationCenter.default.addObserver(self, selector: #selector(keyboardFrameChanged(notification:)), name: NSNotification.Name(rawValue: ALKeyboardFrameDidChangeNotification), object: nil) 37 | NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil) 38 | NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil) 39 | } 40 | 41 | override func viewWillLayoutSubviews() { 42 | super.viewWillLayoutSubviews() 43 | scrollView.frame = view.bounds 44 | textInputBar.frame.size.width = view.bounds.size.width 45 | } 46 | 47 | func configureScrollView() { 48 | view.addSubview(scrollView) 49 | 50 | let contentView = UIView(frame: CGRect(x: 0, y: 0, width: view.bounds.size.width, height: view.bounds.size.height * 2)) 51 | contentView.backgroundColor = UIColor(white: 0.8, alpha: 1) 52 | 53 | scrollView.addSubview(contentView) 54 | scrollView.contentSize = contentView.bounds.size 55 | scrollView.keyboardDismissMode = .interactive 56 | scrollView.backgroundColor = UIColor(white: 0.6, alpha: 1) 57 | } 58 | 59 | func configureInputBar() { 60 | let leftButton = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44)) 61 | let rightButton = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44)) 62 | 63 | leftButton.setImage(#imageLiteral(resourceName: "leftIcon"), for: .normal) 64 | rightButton.setImage(#imageLiteral(resourceName: "rightIcon"), for: .normal) 65 | 66 | keyboardObserver.isUserInteractionEnabled = false 67 | 68 | textInputBar.showTextViewBorder = true 69 | textInputBar.leftView = leftButton 70 | textInputBar.rightView = rightButton 71 | textInputBar.frame = CGRect(x: 0, y: view.frame.size.height - textInputBar.defaultHeight, width: view.frame.size.width, height: textInputBar.defaultHeight) 72 | textInputBar.backgroundColor = UIColor(white: 0.95, alpha: 1) 73 | textInputBar.keyboardObserver = keyboardObserver 74 | 75 | view.addSubview(textInputBar) 76 | } 77 | 78 | @objc func keyboardFrameChanged(notification: NSNotification) { 79 | if let userInfo = notification.userInfo { 80 | let frame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! CGRect 81 | textInputBar.frame.origin.y = frame.origin.y 82 | } 83 | } 84 | 85 | @objc func keyboardWillShow(notification: NSNotification) { 86 | if let userInfo = notification.userInfo { 87 | let frame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! CGRect 88 | textInputBar.frame.origin.y = frame.origin.y 89 | } 90 | } 91 | 92 | @objc func keyboardWillHide(notification: NSNotification) { 93 | if let userInfo = notification.userInfo { 94 | let frame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! CGRect 95 | textInputBar.frame.origin.y = frame.origin.y 96 | } 97 | } 98 | 99 | } 100 | 101 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 AlexLittlejohn 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :warning: This project is unmaintained 2 | 3 | # ALTextInputBar 4 | An auto growing text input bar for messaging apps. Written in Swift. 5 | ALTextInputBar is designed to solve a few issues that folks usually encounter when building messaging apps. 6 | 7 | ![With some text](https://cloud.githubusercontent.com/assets/932822/7333301/a510aa22-eb6a-11e4-988b-ac12e4e6c363.png) 8 | ![With lots of text](https://cloud.githubusercontent.com/assets/932822/7333307/cf101c04-eb6a-11e4-9a80-799cf3353a70.png) 9 | 10 | ### Features 11 | - Simple to use and configure 12 | - Automatic resizing based on content 13 | - Interactive dismiss gesture support 14 | 15 | ### Installation & Requirements 16 | 17 | This project requires Xcode 8.0 to run and compiles with swift 3.0 18 | 19 | ALTextInputBar is available on [CocoaPods](http://cocoapods.org). Add the following to your Podfile: 20 | 21 | ```ruby 22 | pod 'ALTextInputBar' 23 | ``` 24 | 25 | ### Usage 26 | 27 | This is the minimum configuration required to attach an input bar to the keyboard. 28 | ```swift 29 | class ViewController: UIViewController { 30 | 31 | let textInputBar = ALTextInputBar() 32 | 33 | // The magic sauce 34 | // This is how we attach the input bar to the keyboard 35 | override var inputAccessoryView: UIView? { 36 | get { 37 | return textInputBar 38 | } 39 | } 40 | 41 | // Another ingredient in the magic sauce 42 | override var canBecomeFirstResponder: Bool { 43 | return true 44 | } 45 | } 46 | ``` 47 | 48 | ## License 49 | ALTextInputBar is available under the MIT license. See the LICENSE file for more info. 50 | --------------------------------------------------------------------------------