├── .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 | 
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 | 
68 |
69 | You can create one using just the interface builder, drop in a `UITextView` and change the class to `ASJExpandableTextView`.
70 |
71 | 
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 |
--------------------------------------------------------------------------------