├── .gitignore ├── ASJExpandableTextView.podspec ├── ASJExpandableTextView ├── ASJExpandableTextView.h ├── ASJExpandableTextView.m └── ASJInputAccessoryView.xib ├── CHANGELOG.md ├── Example ├── ASJExpandableTextViewExample.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── ASJExpandableTextViewExample.xccheckout │ └── xcuserdata │ │ └── sudeep.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ ├── ASJExpandableTextViewExample.xcscheme │ │ └── xcschememanagement.plist └── ASJExpandableTextViewExample │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard │ ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json │ ├── Info.plist │ ├── ViewController.h │ ├── ViewController.m │ └── main.m ├── Images ├── CustomClass.png ├── IBInspectable.png └── Screenshot.png ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | .DS_Store 4 | build/ 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | xcuserdata 14 | *.xccheckout 15 | *.moved-aside 16 | DerivedData 17 | *.hmap 18 | *.ipa 19 | *.xcuserstate 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/ -------------------------------------------------------------------------------- /ASJExpandableTextView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'ASJExpandableTextView' 3 | s.version = '0.5' 4 | s.platform = :ios, '9.0' 5 | s.license = { :type => 'MIT' } 6 | s.homepage = 'https://github.com/sdpjswl/ASJExpandableTextView' 7 | s.authors = { 'Sudeep' => 'sdpjswl1@gmail.com' } 8 | s.summary = 'A UITextView with placeholder that can expand and contract according to its content' 9 | s.source = { :git => 'https://github.com/sdpjswl/ASJExpandableTextView.git', :tag => s.version } 10 | s.source_files = 'ASJExpandableTextView/*.{h,m}' 11 | s.resources = 'ASJExpandableTextView/*.{xib}' 12 | s.requires_arc = true 13 | end -------------------------------------------------------------------------------- /ASJExpandableTextView/ASJExpandableTextView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ASJExpandableTextView.h 3 | // 4 | // Copyright (c) 2015 Sudeep Jaiswal 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | @import UIKit; 25 | 26 | NS_ASSUME_NONNULL_BEGIN 27 | 28 | typedef void (^DoneTappedBlock)(NSString *text); 29 | typedef void (^HeightChangedBlock)(CGFloat newHeight); 30 | 31 | @interface ASJExpandableTextView : UITextView 32 | 33 | /** 34 | * Sets the placeholder text when no text has been input. 35 | */ 36 | @property (nullable, copy, nonatomic) IBInspectable NSString *placeholder; 37 | 38 | /** 39 | * Sets the placeholder text color. 40 | */ 41 | @property (nullable, strong, nonatomic) IBInspectable UIColor *placeholderTextColor; 42 | 43 | /** 44 | * Sets the text paragraph's line spacing. 45 | */ 46 | @property (assign, nonatomic) CGFloat lineSpacing; 47 | 48 | /** 49 | * Sets whether the text view should increase and decrease 50 | * in height according to its content. 51 | */ 52 | @property (assign, nonatomic) IBInspectable BOOL isExpandable; 53 | 54 | /** 55 | * This property is only of use when 'isExpandable' is YES. 56 | * Sets the maximum number of visible lines of text. 57 | */ 58 | @property (assign, nonatomic) IBInspectable NSUInteger maximumLineCount; 59 | 60 | /** 61 | * Set this property "YES" to show a "Done" button over the 62 | * keyboard. Tapping it will hide the keyboard. 63 | */ 64 | @property (assign, nonatomic) IBInspectable BOOL shouldShowDoneButtonOverKeyboard; 65 | 66 | /** 67 | * Set this property "YES" to make placeholder occupy 68 | * the full height of the text view. Default is "NO". 69 | */ 70 | @property (assign, nonatomic) BOOL placeholderUsesFullViewHeight; 71 | 72 | /** 73 | * A block that will be executed when the "Done" button over 74 | * the keyboard will be tapped. Unusable if "shouldShowDoneButtonOverKeyboard" 75 | * not set to YES. 76 | */ 77 | @property (nullable, copy) DoneTappedBlock doneTappedBlock; 78 | 79 | /** 80 | * A block that will be executed when the height of the text view changes. 81 | * Unusable if the text view is not expandable. 82 | */ 83 | @property (nullable, copy) HeightChangedBlock heightChangedBlock; 84 | 85 | - (void)setDoneTappedBlock:(DoneTappedBlock _Nullable)doneTappedBlock; 86 | - (void)setHeightChangedBlock:(HeightChangedBlock _Nullable)heightChangedBlock; 87 | 88 | @end 89 | 90 | NS_ASSUME_NONNULL_END 91 | -------------------------------------------------------------------------------- /ASJExpandableTextView/ASJExpandableTextView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ASJExpandableTextView.m 3 | // 4 | // Copyright (c) 2015 Sudeep Jaiswal 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import "ASJExpandableTextView.h" 25 | 26 | typedef void (^AccessoryViewDoneBlock)(void); 27 | 28 | #define kDefaultPlaceholderTextColor [UIColor colorWithWhite:0.7f alpha:1.0f] 29 | static CGFloat const kPadding = 1.05f; 30 | 31 | @interface ASJInputAccessoryView : UIView 32 | 33 | @property (copy) AccessoryViewDoneBlock doneTappedBlock; 34 | 35 | @end 36 | 37 | @implementation ASJInputAccessoryView 38 | 39 | - (IBAction)doneButtonTapped:(UIBarButtonItem *)sender 40 | { 41 | if (_doneTappedBlock) { 42 | _doneTappedBlock(); 43 | } 44 | } 45 | 46 | @end 47 | 48 | @interface ASJExpandableTextView () { 49 | NSInteger currentLine; 50 | CGFloat previousContentHeight, defaultTextViewHeight, defaultContentHeight; 51 | BOOL isPlaceholderVisible, areLayoutDefaultsSet; 52 | } 53 | 54 | @property (assign, nonatomic) CGFloat heightOfOneLine; 55 | @property (assign, nonatomic) CGFloat currentContentHeight; 56 | @property (assign, nonatomic) CGFloat currentTextViewHeight; 57 | @property (assign, nonatomic) BOOL shouldShowPlaceholder; 58 | @property (strong, nonatomic) ASJInputAccessoryView *asjInputAccessoryView; 59 | @property (strong, nonatomic) UILabel *placeholderLabel; 60 | @property (strong, nonatomic) NSLayoutConstraint *heightConstraint; 61 | 62 | - (void)setup; 63 | - (void)setLayoutDefaults; 64 | - (void)setDefaults; 65 | - (void)executeDefaultFontHack; 66 | - (void)setPlaceholderLabel; 67 | - (void)listenForContentSizeChanges; 68 | - (void)listenForNotifications; 69 | - (void)handleTextChange; 70 | - (void)handleExpansion; 71 | - (void)handleNextLine:(NSInteger)multiplier; 72 | - (void)handlePreviousLine:(NSInteger)multiplier; 73 | - (void)animateConstraintToHeight:(CGFloat)height; 74 | - (void)animateFrameToHeight:(CGFloat)height; 75 | - (void)scrollToBottom; 76 | - (void)prepareInputAccessoryView; 77 | 78 | @end 79 | 80 | @implementation ASJExpandableTextView 81 | 82 | - (instancetype)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer 83 | { 84 | self = [super initWithFrame:frame textContainer:textContainer]; 85 | if (self) { 86 | [self setup]; 87 | } 88 | return self; 89 | } 90 | 91 | - (instancetype)initWithCoder:(NSCoder *)coder 92 | { 93 | self = [super initWithCoder:coder]; 94 | if (self) { 95 | [self setup]; 96 | } 97 | return self; 98 | } 99 | 100 | - (void)layoutSubviews 101 | { 102 | [super layoutSubviews]; 103 | if (!areLayoutDefaultsSet && _isExpandable) 104 | { 105 | [self setLayoutDefaults]; 106 | areLayoutDefaultsSet = YES; 107 | } 108 | if (!_placeholderLabel) { 109 | [self setPlaceholderLabel]; 110 | } 111 | } 112 | 113 | - (void)setLayoutDefaults 114 | { 115 | currentLine = 1; 116 | defaultContentHeight = self.currentContentHeight; 117 | defaultTextViewHeight = self.frame.size.height; 118 | previousContentHeight = _currentContentHeight = defaultContentHeight; 119 | } 120 | 121 | #pragma mark - Accessory view 122 | 123 | - (BOOL)becomeFirstResponder 124 | { 125 | if (_shouldShowDoneButtonOverKeyboard) { 126 | [self prepareInputAccessoryView]; 127 | } 128 | return [super becomeFirstResponder]; 129 | } 130 | 131 | - (void)prepareInputAccessoryView 132 | { 133 | ASJInputAccessoryView *inputAccessoryView = self.asjInputAccessoryView; 134 | inputAccessoryView.doneTappedBlock = ^{ 135 | [self resignFirstResponder]; 136 | if (self->_doneTappedBlock) { 137 | self->_doneTappedBlock(self.text); 138 | } 139 | }; 140 | self.inputAccessoryView = inputAccessoryView; 141 | } 142 | 143 | - (ASJInputAccessoryView *)asjInputAccessoryView 144 | { 145 | NSBundle *bundle = [NSBundle bundleForClass:[self class]]; 146 | return (ASJInputAccessoryView *)[bundle loadNibNamed:@"ASJInputAccessoryView" owner:self options:nil][0]; 147 | } 148 | 149 | #pragma mark - Setup 150 | 151 | - (void)setup 152 | { 153 | [self setDefaults]; 154 | [self executeDefaultFontHack]; 155 | [self listenForContentSizeChanges]; 156 | [self listenForNotifications]; 157 | } 158 | 159 | - (void)setDefaults 160 | { 161 | _isExpandable = NO; 162 | _maximumLineCount = 4; 163 | _placeholderTextColor = kDefaultPlaceholderTextColor; 164 | _shouldShowDoneButtonOverKeyboard = NO; 165 | _placeholderUsesFullViewHeight = NO; 166 | self.shouldShowPlaceholder = NO; 167 | } 168 | 169 | - (void)executeDefaultFontHack 170 | { 171 | /** 172 | Unless text is set, self.font is nil, it doesn't seem to initialise when the text view is created. 173 | */ 174 | self.text = @"weirdness"; 175 | self.text = nil; 176 | } 177 | 178 | - (void)setPlaceholderLabel 179 | { 180 | CGFloat x = self.textContainer.lineFragmentPadding + self.textContainerInset.left; 181 | CGFloat y = self.textContainerInset.top; 182 | CGFloat width = self.frame.size.width - (2.0f * x); 183 | CGFloat height = _placeholderUsesFullViewHeight ? CGRectGetHeight(self.frame) - (2.0f * y) : 0.0; 184 | 185 | _placeholderLabel = [[UILabel alloc] initWithFrame:CGRectMake(x, y, width, height)]; 186 | _placeholderLabel.enabled = YES; 187 | _placeholderLabel.highlighted = NO; 188 | _placeholderLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; 189 | _placeholderLabel.lineBreakMode = NSLineBreakByWordWrapping; 190 | _placeholderLabel.numberOfLines = 0; 191 | _placeholderLabel.textColor = _placeholderTextColor; 192 | _placeholderLabel.text = self.placeholder; 193 | _placeholderLabel.font = self.font; 194 | _placeholderLabel.backgroundColor = [UIColor clearColor]; 195 | 196 | [self addSubview:_placeholderLabel]; 197 | 198 | if (!_placeholderUsesFullViewHeight) { 199 | [_placeholderLabel sizeToFit]; 200 | } 201 | 202 | if (self.text.length) { 203 | self.shouldShowPlaceholder = NO; 204 | } 205 | } 206 | 207 | #pragma mark - Content size change 208 | 209 | - (void)listenForContentSizeChanges 210 | { 211 | [self addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:nil]; 212 | } 213 | 214 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 215 | { 216 | if (![keyPath isEqualToString:@"contentSize"]) { 217 | return; 218 | } 219 | [self handleExpansion]; 220 | } 221 | 222 | - (void)dealloc 223 | { 224 | [self removeObserver:self forKeyPath:@"contentSize"]; 225 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 226 | } 227 | 228 | #pragma mark - Text change 229 | 230 | - (void)listenForNotifications 231 | { 232 | __weak typeof(self) weakSelf = self; 233 | 234 | [[NSNotificationCenter defaultCenter] 235 | addObserverForName:UITextViewTextDidBeginEditingNotification 236 | object:self queue:[NSOperationQueue mainQueue] 237 | usingBlock:^(NSNotification *note) 238 | { 239 | typeof(weakSelf) strongSelf = weakSelf; 240 | [strongSelf handleTextChange]; 241 | }]; 242 | 243 | [[NSNotificationCenter defaultCenter] 244 | addObserverForName:UITextViewTextDidChangeNotification 245 | object:self queue:[NSOperationQueue mainQueue] 246 | usingBlock:^(NSNotification *note) 247 | { 248 | typeof(weakSelf) strongSelf = weakSelf; 249 | [strongSelf handleTextChange]; 250 | [strongSelf handleExpansion]; 251 | }]; 252 | } 253 | 254 | - (void)handleTextChange 255 | { 256 | if (self.text.length) { 257 | self.shouldShowPlaceholder = NO; 258 | return; 259 | } 260 | if (!isPlaceholderVisible) { 261 | self.shouldShowPlaceholder = YES;; 262 | } 263 | } 264 | 265 | - (void)handleExpansion 266 | { 267 | if (!self.isExpandable) { 268 | return; 269 | } 270 | 271 | BOOL isOnCurrentLine = (self.currentContentHeight == previousContentHeight) ? YES : NO; 272 | if (isOnCurrentLine) { 273 | return; 274 | } 275 | 276 | BOOL isOnNextLine = (self.currentContentHeight > previousContentHeight) ? YES : NO; 277 | NSInteger multiplier = @(ceil(self.currentContentHeight/self.heightOfOneLine)).integerValue; 278 | 279 | if (isOnNextLine) 280 | { 281 | previousContentHeight = self.currentContentHeight; 282 | [self handleNextLine:multiplier]; 283 | return; 284 | } 285 | 286 | multiplier = @(ceil(previousContentHeight/self.heightOfOneLine)).integerValue; 287 | previousContentHeight = self.currentContentHeight; 288 | [self handlePreviousLine:multiplier]; 289 | } 290 | 291 | - (CGFloat)currentContentHeight 292 | { 293 | return self.contentSize.height; 294 | } 295 | 296 | #pragma mark - Next and previous lines 297 | 298 | - (void)handleNextLine:(NSInteger)multiplier 299 | { 300 | currentLine += multiplier; 301 | if (currentLine > _maximumLineCount) { 302 | currentLine = _maximumLineCount; 303 | } 304 | 305 | if (self.currentContentHeight <= self.currentTextViewHeight) { 306 | return; 307 | } 308 | CGFloat newHeight = round(self.heightOfOneLine) * currentLine; 309 | BOOL isHeightConstraintAvailable = self.heightConstraint ? YES : NO; 310 | if (isHeightConstraintAvailable) { 311 | [self animateConstraintToHeight:newHeight]; 312 | } 313 | else { 314 | [self animateFrameToHeight:newHeight]; 315 | } 316 | } 317 | 318 | - (void)handlePreviousLine:(NSInteger)multiplier 319 | { 320 | currentLine -= multiplier; 321 | if (self.currentContentHeight >= self.currentTextViewHeight) { 322 | return; 323 | } 324 | if (self.currentTextViewHeight <= defaultTextViewHeight) { 325 | return; 326 | } 327 | CGFloat newHeight = newHeight = round(self.heightOfOneLine) * currentLine; 328 | if (newHeight < defaultTextViewHeight) { 329 | newHeight = defaultTextViewHeight; 330 | } 331 | 332 | BOOL isHeightConstraintAvailable = self.heightConstraint ? YES : NO; 333 | if (isHeightConstraintAvailable) { 334 | [self animateConstraintToHeight:newHeight]; 335 | } 336 | else { 337 | [self animateFrameToHeight:newHeight]; 338 | } 339 | } 340 | 341 | - (CGFloat)heightOfOneLine 342 | { 343 | return self.font.lineHeight + kPadding; 344 | } 345 | 346 | - (void)animateConstraintToHeight:(CGFloat)height 347 | { 348 | [self.superview layoutIfNeeded]; 349 | self.heightConstraint.constant = height; 350 | [UIView animateWithDuration:0.30f 351 | delay:0.0f 352 | options:UIViewAnimationOptionLayoutSubviews 353 | animations:^{ 354 | [self scrollToBottom]; 355 | [self.superview layoutIfNeeded]; 356 | } completion:nil]; 357 | 358 | if (_heightChangedBlock) { 359 | _heightChangedBlock(height); 360 | } 361 | } 362 | 363 | - (void)animateFrameToHeight:(CGFloat)height 364 | { 365 | if (_heightChangedBlock) { 366 | _heightChangedBlock(height); 367 | } 368 | 369 | [UIView animateWithDuration:0.30f 370 | delay:0.0f 371 | options:UIViewAnimationOptionLayoutSubviews 372 | animations:^{ 373 | CGFloat x = self.frame.origin.x; 374 | CGFloat y = self.frame.origin.y; 375 | CGFloat width = self.frame.size.width; 376 | self.frame = CGRectMake(x, y, width, height); 377 | } completion:nil]; 378 | } 379 | 380 | - (NSLayoutConstraint *)heightConstraint 381 | { 382 | for (NSLayoutConstraint *constraint in self.constraints) { 383 | if (constraint.firstAttribute == NSLayoutAttributeHeight) { 384 | return constraint; 385 | } 386 | } 387 | return nil; 388 | } 389 | 390 | - (CGFloat)currentTextViewHeight 391 | { 392 | return self.frame.size.height; 393 | } 394 | 395 | - (void)scrollToBottom 396 | { 397 | NSRange range = NSMakeRange(self.text.length - 1, 1); 398 | [self scrollRangeToVisible:range]; 399 | } 400 | 401 | #pragma mark - Property setter overrides 402 | 403 | - (void)setPlaceholder:(NSString *)placeholder 404 | { 405 | if (!placeholder.length) { 406 | return; 407 | } 408 | 409 | _placeholder = placeholder; 410 | _placeholderLabel.text = placeholder; 411 | [_placeholderLabel sizeToFit]; 412 | self.shouldShowPlaceholder = YES; 413 | } 414 | 415 | - (void)setPlaceholderTextColor:(UIColor *)placeholderTextColor 416 | { 417 | if (!placeholderTextColor) { 418 | _placeholderTextColor = kDefaultPlaceholderTextColor; 419 | } 420 | else { 421 | _placeholderTextColor = placeholderTextColor; 422 | } 423 | 424 | _placeholderLabel.textColor = _placeholderTextColor; 425 | } 426 | 427 | - (void)setText:(NSString *)text 428 | { 429 | [super setText:text]; 430 | if (text && text.length) { 431 | self.shouldShowPlaceholder = NO; 432 | return; 433 | } 434 | self.shouldShowPlaceholder = YES; 435 | } 436 | 437 | - (void)setShouldShowPlaceholder:(BOOL)shouldShowPlaceholder 438 | { 439 | _shouldShowPlaceholder = shouldShowPlaceholder; 440 | if (_shouldShowPlaceholder) { 441 | _placeholderLabel.alpha = 1.0f; 442 | isPlaceholderVisible = YES; 443 | return; 444 | } 445 | _placeholderLabel.alpha = 0.0f; 446 | isPlaceholderVisible = NO; 447 | } 448 | 449 | // https://stackoverflow.com/a/12993071 450 | - (void)setLineSpacing:(CGFloat)lineSpacing 451 | { 452 | _lineSpacing = lineSpacing; 453 | 454 | NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init]; 455 | paragraphStyle.lineSpacing = lineSpacing; 456 | 457 | self.typingAttributes = @{ NSFontAttributeName : self.font, 458 | NSParagraphStyleAttributeName : paragraphStyle }; 459 | } 460 | 461 | @end 462 | -------------------------------------------------------------------------------- /ASJExpandableTextView/ASJInputAccessoryView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [0.5](https://github.com/sdpjswl/ASJExpandableTextView/releases/tag/0.5) 4 | Released on Tuesday, 25 Jan, 2022. 5 | 6 | #### Added 7 | * New property to set line spacing of the paragraph text. 8 | * New boolean to set whether placeholder label uses full height of text view. 9 | 10 | #### Fixed 11 | * Crash while opening accessory view in workspace. 12 | 13 | ## [0.4](https://github.com/sdpjswl/ASJExpandableTextView/releases/tag/0.4) 14 | Released on Friday, 10 Jun, 2016. 15 | 16 | #### Fixed 17 | * [Issue #3](https://github.com/sdpjswl/ASJExpandableTextView/issues/3) - Correctly handle text view size when large text is pasted or deleted. 18 | 19 | #### Updated 20 | * Example project with a button to copy large text. 21 | 22 | ## [0.3](https://github.com/sdpjswl/ASJExpandableTextView/releases/tag/0.3) 23 | Released on Wednesday, 18 May, 2016. 24 | 25 | #### Added 26 | * New property to set placeholder color. 27 | 28 | ## [0.2](https://github.com/sdpjswl/ASJExpandableTextView/releases/tag/0.2) 29 | Released on Wednesday, 18 May, 2016. 30 | 31 | #### Updated 32 | * Explicitly specifying floating point variables. 33 | * Formatted readme. 34 | 35 | ## [0.1](https://github.com/sdpjswl/ASJExpandableTextView/releases/tag/0.1) 36 | Released on Wednesday, 11 May, 2016. 37 | 38 | #### Added 39 | * Nullability annotations. 40 | * Thanks to [devxoul](https://github.com/devxoul/UITextView-Placeholder) in the readme. 41 | 42 | #### Fixed 43 | * [Issue #2](https://github.com/sdpjswl/ASJExpandableTextView/issues/2) - Fixed retain cycles. 44 | 45 | #### Updated 46 | * Used the text view's text container property to set placeholder label's position. 47 | 48 | ## [0.0.4](https://github.com/sdpjswl/ASJExpandableTextView/releases/tag/0.0.4) 49 | Released on Monday, 1 Feb, 2016. 50 | 51 | #### Updated 52 | * Used the text view's text container property to set placeholder label's position. 53 | 54 | ## [0.0.3](https://github.com/sdpjswl/ASJExpandableTextView/releases/tag/0.0.3) 55 | Released on Friday, 15 Jan, 2016. 56 | 57 | #### Updated 58 | * Independently check and show placeholder label if not created. 59 | * Using the [default placeholder color](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITextField_Class/#//apple_ref/occ/instp/UITextField/placeholder) Apple specifies. 60 | 61 | ## [0.0.2](https://github.com/sdpjswl/ASJExpandableTextView/releases/tag/0.0.2) 62 | Released on Wednesday, 23 Dec, 2015. 63 | 64 | #### Fixed 65 | * Podspec syntax to correctly handle Xib file. 66 | 67 | #### Updated 68 | * Readme to show how to install from CocoaPods. 69 | 70 | ## [0.0.1](https://github.com/sdpjswl/ASJExpandableTextView/releases/tag/0.0.1) 71 | Released on Sunday, 20 Dec, 2015. 72 | 73 | #### Added 74 | * Initial release. 75 | -------------------------------------------------------------------------------- /Example/ASJExpandableTextViewExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C0122AB71B518E45004DED5D /* ASJInputAccessoryView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C0122AB61B518E45004DED5D /* ASJInputAccessoryView.xib */; }; 11 | C05A9DC41B4D6698003CD214 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C05A9DC31B4D6698003CD214 /* main.m */; }; 12 | C05A9DC71B4D6698003CD214 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = C05A9DC61B4D6698003CD214 /* AppDelegate.m */; }; 13 | C05A9DCA1B4D6698003CD214 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C05A9DC91B4D6698003CD214 /* ViewController.m */; }; 14 | C05A9DCD1B4D6698003CD214 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C05A9DCB1B4D6698003CD214 /* Main.storyboard */; }; 15 | C05A9DCF1B4D6698003CD214 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C05A9DCE1B4D6698003CD214 /* Images.xcassets */; }; 16 | C05A9DD21B4D6698003CD214 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = C05A9DD01B4D6698003CD214 /* LaunchScreen.xib */; }; 17 | C0CBA3A71B5033F70013DD90 /* ASJExpandableTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = C0CBA3A61B5033F70013DD90 /* ASJExpandableTextView.m */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | C0122AB61B518E45004DED5D /* ASJInputAccessoryView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ASJInputAccessoryView.xib; sourceTree = ""; }; 22 | C05A9DBE1B4D6698003CD214 /* ASJExpandableTextViewExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ASJExpandableTextViewExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | C05A9DC21B4D6698003CD214 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 24 | C05A9DC31B4D6698003CD214 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 25 | C05A9DC51B4D6698003CD214 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 26 | C05A9DC61B4D6698003CD214 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 27 | C05A9DC81B4D6698003CD214 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 28 | C05A9DC91B4D6698003CD214 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 29 | C05A9DCC1B4D6698003CD214 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 30 | C05A9DCE1B4D6698003CD214 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 31 | C05A9DD11B4D6698003CD214 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 32 | C0CBA3A51B5033F70013DD90 /* ASJExpandableTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASJExpandableTextView.h; sourceTree = ""; }; 33 | C0CBA3A61B5033F70013DD90 /* ASJExpandableTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASJExpandableTextView.m; sourceTree = ""; }; 34 | /* End PBXFileReference section */ 35 | 36 | /* Begin PBXFrameworksBuildPhase section */ 37 | C05A9DBB1B4D6698003CD214 /* Frameworks */ = { 38 | isa = PBXFrameworksBuildPhase; 39 | buildActionMask = 2147483647; 40 | files = ( 41 | ); 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXFrameworksBuildPhase section */ 45 | 46 | /* Begin PBXGroup section */ 47 | C05A9DB51B4D6698003CD214 = { 48 | isa = PBXGroup; 49 | children = ( 50 | C05A9DC01B4D6698003CD214 /* ASJExpandableTextViewExample */, 51 | C05A9DBF1B4D6698003CD214 /* Products */, 52 | ); 53 | indentWidth = 4; 54 | sourceTree = ""; 55 | tabWidth = 4; 56 | }; 57 | C05A9DBF1B4D6698003CD214 /* Products */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | C05A9DBE1B4D6698003CD214 /* ASJExpandableTextViewExample.app */, 61 | ); 62 | name = Products; 63 | sourceTree = ""; 64 | }; 65 | C05A9DC01B4D6698003CD214 /* ASJExpandableTextViewExample */ = { 66 | isa = PBXGroup; 67 | children = ( 68 | C0CBA3A41B5033F70013DD90 /* ASJExpandableTextView */, 69 | C05A9DC51B4D6698003CD214 /* AppDelegate.h */, 70 | C05A9DC61B4D6698003CD214 /* AppDelegate.m */, 71 | C05A9DC81B4D6698003CD214 /* ViewController.h */, 72 | C05A9DC91B4D6698003CD214 /* ViewController.m */, 73 | C05A9DCB1B4D6698003CD214 /* Main.storyboard */, 74 | C05A9DD01B4D6698003CD214 /* LaunchScreen.xib */, 75 | C05A9DCE1B4D6698003CD214 /* Images.xcassets */, 76 | C05A9DC11B4D6698003CD214 /* Supporting Files */, 77 | ); 78 | path = ASJExpandableTextViewExample; 79 | sourceTree = ""; 80 | }; 81 | C05A9DC11B4D6698003CD214 /* Supporting Files */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | C05A9DC21B4D6698003CD214 /* Info.plist */, 85 | C05A9DC31B4D6698003CD214 /* main.m */, 86 | ); 87 | name = "Supporting Files"; 88 | sourceTree = ""; 89 | }; 90 | C0CBA3A41B5033F70013DD90 /* ASJExpandableTextView */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | C0CBA3A51B5033F70013DD90 /* ASJExpandableTextView.h */, 94 | C0CBA3A61B5033F70013DD90 /* ASJExpandableTextView.m */, 95 | C0122AB61B518E45004DED5D /* ASJInputAccessoryView.xib */, 96 | ); 97 | name = ASJExpandableTextView; 98 | path = ../../ASJExpandableTextView; 99 | sourceTree = ""; 100 | }; 101 | /* End PBXGroup section */ 102 | 103 | /* Begin PBXNativeTarget section */ 104 | C05A9DBD1B4D6698003CD214 /* ASJExpandableTextViewExample */ = { 105 | isa = PBXNativeTarget; 106 | buildConfigurationList = C05A9DE11B4D6698003CD214 /* Build configuration list for PBXNativeTarget "ASJExpandableTextViewExample" */; 107 | buildPhases = ( 108 | C05A9DBA1B4D6698003CD214 /* Sources */, 109 | C05A9DBB1B4D6698003CD214 /* Frameworks */, 110 | C05A9DBC1B4D6698003CD214 /* Resources */, 111 | ); 112 | buildRules = ( 113 | ); 114 | dependencies = ( 115 | ); 116 | name = ASJExpandableTextViewExample; 117 | productName = ASJExpandableTextViewExample; 118 | productReference = C05A9DBE1B4D6698003CD214 /* ASJExpandableTextViewExample.app */; 119 | productType = "com.apple.product-type.application"; 120 | }; 121 | /* End PBXNativeTarget section */ 122 | 123 | /* Begin PBXProject section */ 124 | C05A9DB61B4D6698003CD214 /* Project object */ = { 125 | isa = PBXProject; 126 | attributes = { 127 | LastUpgradeCheck = 1320; 128 | ORGANIZATIONNAME = "Sudeep Jaiswal"; 129 | TargetAttributes = { 130 | C05A9DBD1B4D6698003CD214 = { 131 | CreatedOnToolsVersion = 6.3.2; 132 | }; 133 | }; 134 | }; 135 | buildConfigurationList = C05A9DB91B4D6698003CD214 /* Build configuration list for PBXProject "ASJExpandableTextViewExample" */; 136 | compatibilityVersion = "Xcode 3.2"; 137 | developmentRegion = en; 138 | hasScannedForEncodings = 0; 139 | knownRegions = ( 140 | en, 141 | Base, 142 | ); 143 | mainGroup = C05A9DB51B4D6698003CD214; 144 | productRefGroup = C05A9DBF1B4D6698003CD214 /* Products */; 145 | projectDirPath = ""; 146 | projectRoot = ""; 147 | targets = ( 148 | C05A9DBD1B4D6698003CD214 /* ASJExpandableTextViewExample */, 149 | ); 150 | }; 151 | /* End PBXProject section */ 152 | 153 | /* Begin PBXResourcesBuildPhase section */ 154 | C05A9DBC1B4D6698003CD214 /* Resources */ = { 155 | isa = PBXResourcesBuildPhase; 156 | buildActionMask = 2147483647; 157 | files = ( 158 | C05A9DCD1B4D6698003CD214 /* Main.storyboard in Resources */, 159 | C05A9DD21B4D6698003CD214 /* LaunchScreen.xib in Resources */, 160 | C05A9DCF1B4D6698003CD214 /* Images.xcassets in Resources */, 161 | C0122AB71B518E45004DED5D /* ASJInputAccessoryView.xib in Resources */, 162 | ); 163 | runOnlyForDeploymentPostprocessing = 0; 164 | }; 165 | /* End PBXResourcesBuildPhase section */ 166 | 167 | /* Begin PBXSourcesBuildPhase section */ 168 | C05A9DBA1B4D6698003CD214 /* Sources */ = { 169 | isa = PBXSourcesBuildPhase; 170 | buildActionMask = 2147483647; 171 | files = ( 172 | C0CBA3A71B5033F70013DD90 /* ASJExpandableTextView.m in Sources */, 173 | C05A9DCA1B4D6698003CD214 /* ViewController.m in Sources */, 174 | C05A9DC71B4D6698003CD214 /* AppDelegate.m in Sources */, 175 | C05A9DC41B4D6698003CD214 /* main.m in Sources */, 176 | ); 177 | runOnlyForDeploymentPostprocessing = 0; 178 | }; 179 | /* End PBXSourcesBuildPhase section */ 180 | 181 | /* Begin PBXVariantGroup section */ 182 | C05A9DCB1B4D6698003CD214 /* Main.storyboard */ = { 183 | isa = PBXVariantGroup; 184 | children = ( 185 | C05A9DCC1B4D6698003CD214 /* Base */, 186 | ); 187 | name = Main.storyboard; 188 | sourceTree = ""; 189 | }; 190 | C05A9DD01B4D6698003CD214 /* LaunchScreen.xib */ = { 191 | isa = PBXVariantGroup; 192 | children = ( 193 | C05A9DD11B4D6698003CD214 /* Base */, 194 | ); 195 | name = LaunchScreen.xib; 196 | sourceTree = ""; 197 | }; 198 | /* End PBXVariantGroup section */ 199 | 200 | /* Begin XCBuildConfiguration section */ 201 | C05A9DDF1B4D6698003CD214 /* Debug */ = { 202 | isa = XCBuildConfiguration; 203 | buildSettings = { 204 | ALWAYS_SEARCH_USER_PATHS = NO; 205 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 206 | CLANG_CXX_LIBRARY = "libc++"; 207 | CLANG_ENABLE_MODULES = YES; 208 | CLANG_ENABLE_OBJC_ARC = YES; 209 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 210 | CLANG_WARN_BOOL_CONVERSION = YES; 211 | CLANG_WARN_COMMA = YES; 212 | CLANG_WARN_CONSTANT_CONVERSION = YES; 213 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 214 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 215 | CLANG_WARN_EMPTY_BODY = YES; 216 | CLANG_WARN_ENUM_CONVERSION = YES; 217 | CLANG_WARN_INFINITE_RECURSION = YES; 218 | CLANG_WARN_INT_CONVERSION = YES; 219 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 220 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 221 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 222 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 223 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 224 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 225 | CLANG_WARN_STRICT_PROTOTYPES = YES; 226 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 227 | CLANG_WARN_UNREACHABLE_CODE = YES; 228 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 229 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 230 | COPY_PHASE_STRIP = NO; 231 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 232 | ENABLE_STRICT_OBJC_MSGSEND = YES; 233 | ENABLE_TESTABILITY = YES; 234 | GCC_C_LANGUAGE_STANDARD = gnu99; 235 | GCC_DYNAMIC_NO_PIC = NO; 236 | GCC_NO_COMMON_BLOCKS = YES; 237 | GCC_OPTIMIZATION_LEVEL = 0; 238 | GCC_PREPROCESSOR_DEFINITIONS = ( 239 | "DEBUG=1", 240 | "$(inherited)", 241 | ); 242 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 243 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 244 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 245 | GCC_WARN_UNDECLARED_SELECTOR = YES; 246 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 247 | GCC_WARN_UNUSED_FUNCTION = YES; 248 | GCC_WARN_UNUSED_VARIABLE = YES; 249 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 250 | MTL_ENABLE_DEBUG_INFO = YES; 251 | ONLY_ACTIVE_ARCH = YES; 252 | SDKROOT = iphoneos; 253 | }; 254 | name = Debug; 255 | }; 256 | C05A9DE01B4D6698003CD214 /* Release */ = { 257 | isa = XCBuildConfiguration; 258 | buildSettings = { 259 | ALWAYS_SEARCH_USER_PATHS = NO; 260 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 261 | CLANG_CXX_LIBRARY = "libc++"; 262 | CLANG_ENABLE_MODULES = YES; 263 | CLANG_ENABLE_OBJC_ARC = YES; 264 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 265 | CLANG_WARN_BOOL_CONVERSION = YES; 266 | CLANG_WARN_COMMA = YES; 267 | CLANG_WARN_CONSTANT_CONVERSION = YES; 268 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 269 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 270 | CLANG_WARN_EMPTY_BODY = YES; 271 | CLANG_WARN_ENUM_CONVERSION = YES; 272 | CLANG_WARN_INFINITE_RECURSION = YES; 273 | CLANG_WARN_INT_CONVERSION = YES; 274 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 275 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 276 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 277 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 278 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 279 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 280 | CLANG_WARN_STRICT_PROTOTYPES = YES; 281 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 282 | CLANG_WARN_UNREACHABLE_CODE = YES; 283 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 284 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 285 | COPY_PHASE_STRIP = NO; 286 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 287 | ENABLE_NS_ASSERTIONS = NO; 288 | ENABLE_STRICT_OBJC_MSGSEND = YES; 289 | GCC_C_LANGUAGE_STANDARD = gnu99; 290 | GCC_NO_COMMON_BLOCKS = YES; 291 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 292 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 293 | GCC_WARN_UNDECLARED_SELECTOR = YES; 294 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 295 | GCC_WARN_UNUSED_FUNCTION = YES; 296 | GCC_WARN_UNUSED_VARIABLE = YES; 297 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 298 | MTL_ENABLE_DEBUG_INFO = NO; 299 | SDKROOT = iphoneos; 300 | VALIDATE_PRODUCT = YES; 301 | }; 302 | name = Release; 303 | }; 304 | C05A9DE21B4D6698003CD214 /* Debug */ = { 305 | isa = XCBuildConfiguration; 306 | buildSettings = { 307 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 308 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = ""; 309 | INFOPLIST_FILE = ASJExpandableTextViewExample/Info.plist; 310 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 311 | PRODUCT_BUNDLE_IDENTIFIER = "com.sudeep.$(PRODUCT_NAME:rfc1034identifier)"; 312 | PRODUCT_NAME = "$(TARGET_NAME)"; 313 | }; 314 | name = Debug; 315 | }; 316 | C05A9DE31B4D6698003CD214 /* Release */ = { 317 | isa = XCBuildConfiguration; 318 | buildSettings = { 319 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 320 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = ""; 321 | INFOPLIST_FILE = ASJExpandableTextViewExample/Info.plist; 322 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 323 | PRODUCT_BUNDLE_IDENTIFIER = "com.sudeep.$(PRODUCT_NAME:rfc1034identifier)"; 324 | PRODUCT_NAME = "$(TARGET_NAME)"; 325 | }; 326 | name = Release; 327 | }; 328 | /* End XCBuildConfiguration section */ 329 | 330 | /* Begin XCConfigurationList section */ 331 | C05A9DB91B4D6698003CD214 /* Build configuration list for PBXProject "ASJExpandableTextViewExample" */ = { 332 | isa = XCConfigurationList; 333 | buildConfigurations = ( 334 | C05A9DDF1B4D6698003CD214 /* Debug */, 335 | C05A9DE01B4D6698003CD214 /* Release */, 336 | ); 337 | defaultConfigurationIsVisible = 0; 338 | defaultConfigurationName = Release; 339 | }; 340 | C05A9DE11B4D6698003CD214 /* Build configuration list for PBXNativeTarget "ASJExpandableTextViewExample" */ = { 341 | isa = XCConfigurationList; 342 | buildConfigurations = ( 343 | C05A9DE21B4D6698003CD214 /* Debug */, 344 | C05A9DE31B4D6698003CD214 /* Release */, 345 | ); 346 | defaultConfigurationIsVisible = 0; 347 | defaultConfigurationName = Release; 348 | }; 349 | /* End XCConfigurationList section */ 350 | }; 351 | rootObject = C05A9DB61B4D6698003CD214 /* Project object */; 352 | } 353 | -------------------------------------------------------------------------------- /Example/ASJExpandableTextViewExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/ASJExpandableTextViewExample.xcodeproj/project.xcworkspace/xcshareddata/ASJExpandableTextViewExample.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 96C1B4C1-8F80-4BA2-A431-474D2FC11BBF 9 | IDESourceControlProjectName 10 | ASJExpandableTextViewExample 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | DC2FD56E4E0C9FBEAA14B23A38B113E2DC92A07B 14 | https://github.com/sudeep-jaiswal/ASJExpandableTextView.git 15 | 16 | IDESourceControlProjectPath 17 | Example/ASJExpandableTextViewExample.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | DC2FD56E4E0C9FBEAA14B23A38B113E2DC92A07B 21 | ../../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/sudeep-jaiswal/ASJExpandableTextView.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | DC2FD56E4E0C9FBEAA14B23A38B113E2DC92A07B 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | DC2FD56E4E0C9FBEAA14B23A38B113E2DC92A07B 36 | IDESourceControlWCCName 37 | ASJExpandableTextViewExample 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Example/ASJExpandableTextViewExample.xcodeproj/xcuserdata/sudeep.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /Example/ASJExpandableTextViewExample.xcodeproj/xcuserdata/sudeep.xcuserdatad/xcschemes/ASJExpandableTextViewExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 51 | 52 | 53 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 76 | 78 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /Example/ASJExpandableTextViewExample.xcodeproj/xcuserdata/sudeep.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ASJExpandableTextViewExample.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | C05A9DBD1B4D6698003CD214 16 | 17 | primary 18 | 19 | 20 | C05A9DD61B4D6698003CD214 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Example/ASJExpandableTextViewExample/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // ASJExpandableTextViewExample 4 | // 5 | // Created by sudeep on 08/07/15. 6 | // Copyright (c) 2015 Sudeep Jaiswal. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /Example/ASJExpandableTextViewExample/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // ASJExpandableTextViewExample 4 | // 5 | // Created by sudeep on 08/07/15. 6 | // Copyright (c) 2015 Sudeep Jaiswal. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Example/ASJExpandableTextViewExample/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Example/ASJExpandableTextViewExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Example/ASJExpandableTextViewExample/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /Example/ASJExpandableTextViewExample/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "8.0", 8 | "subtype" : "736h", 9 | "scale" : "3x" 10 | }, 11 | { 12 | "orientation" : "portrait", 13 | "idiom" : "iphone", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "8.0", 16 | "subtype" : "667h", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "orientation" : "portrait", 21 | "idiom" : "iphone", 22 | "extent" : "full-screen", 23 | "minimum-system-version" : "7.0", 24 | "scale" : "2x" 25 | }, 26 | { 27 | "orientation" : "portrait", 28 | "idiom" : "iphone", 29 | "extent" : "full-screen", 30 | "minimum-system-version" : "7.0", 31 | "subtype" : "retina4", 32 | "scale" : "2x" 33 | } 34 | ], 35 | "info" : { 36 | "version" : 1, 37 | "author" : "xcode" 38 | } 39 | } -------------------------------------------------------------------------------- /Example/ASJExpandableTextViewExample/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 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarStyle 34 | UIStatusBarStyleDefault 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UIViewControllerBasedStatusBarAppearance 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Example/ASJExpandableTextViewExample/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // ASJExpandableTextViewExample 4 | // 5 | // Created by sudeep on 08/07/15. 6 | // Copyright (c) 2015 Sudeep Jaiswal. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Example/ASJExpandableTextViewExample/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // ASJExpandableTextViewExample 4 | // 5 | // Created by sudeep on 08/07/15. 6 | // Copyright (c) 2015 Sudeep Jaiswal. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "ASJExpandableTextView.h" 11 | 12 | @interface ViewController () 13 | { 14 | IBOutlet ASJExpandableTextView *textView; 15 | } 16 | 17 | - (void)setup; 18 | - (IBAction)copyTapped:(id)sender; 19 | 20 | @end 21 | 22 | @implementation ViewController 23 | 24 | - (void)viewDidLoad 25 | { 26 | [super viewDidLoad]; 27 | [self setup]; 28 | } 29 | 30 | #pragma mark - Setup 31 | 32 | - (void)setup 33 | { 34 | textView.placeholder = @"Type something here..."; 35 | textView.isExpandable = YES; 36 | textView.maximumLineCount = 14; 37 | textView.lineSpacing = 1.5f; 38 | textView.shouldShowDoneButtonOverKeyboard = YES; 39 | [textView setDoneTappedBlock:^(NSString * _Nonnull text) 40 | { 41 | NSLog(@"you typed: %@", text); 42 | }]; 43 | } 44 | 45 | - (IBAction)copyTapped:(id)sender 46 | { 47 | [UIPasteboard generalPasteboard].string = @"'Cause this music can put a human being in a trance like state and deprive it for the sneaking feeling of existing. 'Cause music is bigger than words and wider than pictures. If someone said that Mogwai are the stars I would not object. If the stars had a sound it would sound like this. The punishment for these solemn words can be hard. Can blood boil like this at the sound of a noisy tape that I've heard. I know one thing. On Saturday, the sky will crumble together (or something) with a huge bang to fit into the cave."; 48 | } 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /Example/ASJExpandableTextViewExample/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // ASJExpandableTextViewExample 4 | // 5 | // Created by sudeep on 08/07/15. 6 | // Copyright (c) 2015 Sudeep Jaiswal. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Images/CustomClass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdpjswl/ASJExpandableTextView/d9e91c36ec827e54b5ef9d97cda1e16e694393c2/Images/CustomClass.png -------------------------------------------------------------------------------- /Images/IBInspectable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdpjswl/ASJExpandableTextView/d9e91c36ec827e54b5ef9d97cda1e16e694393c2/Images/IBInspectable.png -------------------------------------------------------------------------------- /Images/Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdpjswl/ASJExpandableTextView/d9e91c36ec827e54b5ef9d97cda1e16e694393c2/Images/Screenshot.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Sudeep Jaiswal 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 | # ASJExpandableTextView 2 | 3 | `UITextView`'s most obvious omission is the ability to set placeholder text. This class fixes that and provides more. You can make the text view expand and contract according to its content and have a "Done" button over the keyboard to hide it. 4 | 5 | ![alt tag](Images/Screenshot.png) 6 | 7 | # Installation 8 | 9 | CocoaPods is the preferred way to install this library. Add this command to your `Podfile`: 10 | 11 | ```ruby 12 | pod 'ASJExpandableTextView' 13 | ``` 14 | 15 | # Usage 16 | 17 | Creating an `ASJExpandableTextView` is easy. It has a simple interface consisting of four properties which are `IBInspectable`. This means that they can be set using the interface builder of your choice; xibs or storyboards. 18 | 19 | ```objc 20 | @property (nullable, copy, nonatomic) IBInspectable NSString *placeholder; 21 | ``` 22 | 23 | Sets the placeholder. Visible when there is nothing typed in the text view. 24 | 25 | ```objc 26 | @property (nullable, strong, nonatomic) IBInspectable UIColor *placeholderTextColor; 27 | ``` 28 | 29 | Sets the placeholder text color. Will work only when the placeholder is visible. 30 | 31 | ```objc 32 | @property (assign, nonatomic) CGFloat lineSpacing; 33 | ``` 34 | 35 | Sets the spacing between two lines of text. 36 | 37 | ```objc 38 | @property (assign, nonatomic) IBInspectable BOOL isExpandable; 39 | ``` 40 | 41 | Set this to make the text view expand and contract according to its content. 42 | 43 | ```objc 44 | @property (assign, nonatomic) IBInspectable NSUInteger maximumLineCount; 45 | ``` 46 | 47 | You can set the number of visible lines of the text view. Default is 4. To use this property, `isExpandable` must be set to `YES`. 48 | 49 | ```objc 50 | @property (assign, nonatomic) IBInspectable BOOL shouldShowDoneButtonOverKeyboard; 51 | ``` 52 | 53 | The "return" key on the keyboard for a `UITextView` brings a new line, unlike a `UITextField` where the keyboard gets hidden. Set this property to show a "Done" button over the keyboard which can hide the keyboard. 54 | 55 | ```objc 56 | @property (assign, nonatomic) BOOL placeholderUsesFullViewHeight; 57 | ``` 58 | 59 | Determines whether the placeholder view is spread over the whole text view or is shown at the top left corner like usual. Defaults to `NO`. 60 | 61 | ```objc 62 | @property (nullable, copy) DoneTappedBlock doneTappedBlock; 63 | ``` 64 | 65 | You can handle the event of the keyboard getting hidden using this block. To use this property, `shouldShowDoneButtonOverKeyboard` must be set to `YES`. 66 | 67 | ![alt tag](Images/IBInspectable.png) 68 | 69 | You can create one using just the interface builder, drop in a `UITextView` and change the class to `ASJExpandableTextView`. 70 | 71 | ![alt tag](Images/CustomClass.png) 72 | 73 | # Credits 74 | 75 | - To [Abhijit Kayande](https://github.com/Abhijit-Kayande) for fixing the choppy animation 76 | - To [devxoul](https://github.com/devxoul/UITextView-Placeholder) for placeholder label position fix 77 | - To [Daleijn](https://github.com/Dalein) for adding new placeholder property 78 | 79 | # License 80 | 81 | `ASJExpandableTextView` is available under the MIT license. See the LICENSE file for more info. 82 | --------------------------------------------------------------------------------