Skip to content

Commit 6c2a385

Browse files
committed
Add initial implementation.
1 parent 17c2295 commit 6c2a385

File tree

3 files changed

+209
-1
lines changed

3 files changed

+209
-1
lines changed

MPTextView.h

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//
2+
// MPTextView.h
3+
//
4+
// Created by Christopher Bowns on 7/25/13.
5+
// Licensed under the Apache v2 license.
6+
//
7+
8+
#import <UIKit/UIKit.h>
9+
10+
@interface MPTextView : UITextView
11+
12+
// Named .placeholderText, in case UITextView gains
13+
// a .placeholder property (like UITextField) in future iOS versions.
14+
@property (nonatomic, copy) NSString *placeholderText;
15+
16+
@end

MPTextView.m

+184
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
//
2+
// MPTextView.m
3+
//
4+
// Created by Christopher Bowns on 7/25/13.
5+
// Licensed under the Apache v2 license.
6+
//
7+
8+
#import "MPTextView.h"
9+
10+
// Manually-selected label offset to align placeholder label with real text.
11+
static CGFloat const kLabelLeftOffset = 8.f;
12+
static CGFloat const kLabelTopOffsetRetina = 0.5f;
13+
14+
@interface MPTextView ()
15+
16+
@property (nonatomic, strong) UILabel *placeholderLabel;
17+
18+
// Calculate and save the label's left and top offset.
19+
@property (nonatomic, assign) CGSize labelOffset;
20+
21+
// Handle text changed event so we can update the placeholder appropriately
22+
- (void)textChanged:(NSNotification *)note;
23+
24+
@end
25+
26+
27+
@implementation MPTextView
28+
29+
#pragma mark - Initializers
30+
31+
- (id)initWithCoder:(NSCoder *)aDecoder {
32+
self = [super initWithCoder:aDecoder];
33+
if (self) {
34+
[self finishInitialization];
35+
}
36+
return self;
37+
}
38+
39+
40+
- (id)initWithFrame:(CGRect)frame {
41+
self = [super initWithFrame:frame];
42+
if (self) {
43+
[self finishInitialization];
44+
}
45+
return self;
46+
}
47+
48+
49+
// Private method for finishing initialization.
50+
// Rather than muck with designated initializers, let's do it the easy way.
51+
- (void)finishInitialization {
52+
// Sign up for notifications for text changes:
53+
[[NSNotificationCenter defaultCenter] addObserver:self
54+
selector:@selector(textChanged:)
55+
name:UITextViewTextDidChangeNotification
56+
object:self];
57+
58+
CGFloat labelLeftOffset = kLabelLeftOffset;
59+
CGFloat labelTopOffset = 0.f;
60+
61+
// On retina iPhones and iPads, the label is offset by 0.5 points.
62+
if ([[UIScreen mainScreen] scale] == 2.0) {
63+
labelTopOffset = kLabelTopOffsetRetina;
64+
}
65+
self.labelOffset = CGSizeMake(labelLeftOffset, labelTopOffset);
66+
67+
[self createPlaceholderLabel];
68+
}
69+
70+
71+
#pragma mark - Placeholder label helper
72+
73+
// Create our label:
74+
- (void)createPlaceholderLabel {
75+
CGRect labelFrame = [self calculatePlaceholderLabelFrame];
76+
77+
self.placeholderLabel = [[UILabel alloc] initWithFrame:labelFrame];
78+
self.placeholderLabel.lineBreakMode = NSLineBreakByWordWrapping;
79+
self.placeholderLabel.numberOfLines = 0;
80+
self.placeholderLabel.font = self.font;
81+
self.placeholderLabel.backgroundColor = [UIColor clearColor];
82+
self.placeholderLabel.text = self.placeholderText;
83+
// Manually matched to UITextField's placeholder text color.
84+
self.placeholderLabel.textColor = [UIColor colorWithWhite:0.71f alpha:1.0f];
85+
86+
[self addSubview:self.placeholderLabel];
87+
}
88+
89+
90+
- (CGRect)calculatePlaceholderLabelFrame {
91+
return CGRectMake(self.labelOffset.width,
92+
self.labelOffset.height,
93+
self.bounds.size.width - (2 * self.labelOffset.width),
94+
self.bounds.size.height - (2 * self.labelOffset.height));
95+
}
96+
97+
#pragma mark - Custom accessors
98+
99+
- (void)setPlaceholderText:(NSString *)string {
100+
_placeholderText = [string copy];
101+
self.placeholderLabel.text = string;
102+
[self.placeholderLabel sizeToFit];
103+
}
104+
105+
106+
#pragma mark - UITextView subclass methods
107+
108+
// Keep the placeholder label font in sync with the text view's
109+
- (void)setFont:(UIFont *)font {
110+
// Call super.
111+
[super setFont:font];
112+
113+
self.placeholderLabel.font = self.font;
114+
}
115+
116+
117+
// Keep placeholder label alignment in sync
118+
- (void)setTextAlignment:(NSTextAlignment)textAlignment {
119+
// Call super.
120+
[super setTextAlignment:textAlignment];
121+
122+
self.placeholderLabel.textAlignment = textAlignment;
123+
}
124+
125+
// Todo: override setAttributedText to capture changes
126+
// in text alignment?
127+
128+
129+
#pragma mark - UITextInput overrides
130+
131+
// Listen to dictation events to hide the placeholder as is appropriate.
132+
133+
// Hide when there's a dictation result placeholder
134+
- (id)insertDictationResultPlaceholder {
135+
// Call super.
136+
id placeholder = [super insertDictationResultPlaceholder];
137+
138+
// Use .hidden (instead of alpha), since these events also trigger
139+
// -[textChanged] (which has a different criteria by which it shows the label)
140+
self.placeholderLabel.hidden = YES;
141+
return placeholder;
142+
}
143+
144+
145+
// Update visibility when dictation ends.
146+
- (void)removeDictationResultPlaceholder:(id)placeholder willInsertResult:(BOOL)willInsertResult {
147+
// Call super.
148+
[super removeDictationResultPlaceholder:placeholder willInsertResult:willInsertResult];
149+
150+
// Unset the hidden flag from insertDictationResultPlaceholder.
151+
self.placeholderLabel.hidden = NO;
152+
153+
// Update our text label based on the entered text.
154+
[self updatePlaceholderLabelVisibility];
155+
}
156+
157+
158+
#pragma mark - Text change listeners
159+
160+
- (void)updatePlaceholderLabelVisibility {
161+
if ([self.text length] == 0) {
162+
self.placeholderLabel.alpha = 1.f;
163+
} else {
164+
self.placeholderLabel.alpha = 0.f;
165+
}
166+
}
167+
168+
169+
// When text is set or changed, update the label's visibility:
170+
171+
- (void)setText:(NSString *)text {
172+
// Call super.
173+
[super setText:text];
174+
175+
[self updatePlaceholderLabelVisibility];
176+
}
177+
178+
179+
- (void)textChanged:(NSNotification *)notification {
180+
[self updatePlaceholderLabelVisibility];
181+
}
182+
183+
184+
@end

README.md

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
MPTextView
22
==========
33

4-
A UITextView subclass with support for placeholder text (like UITextField)
4+
A UITextView subclass with support for placeholder text (similar to UITextField)
5+
6+
### Notes and errata
7+
8+
- The property is named `.placeholderText` to avoid colliding with `.placeholder` if UITextView gains that property in a future version of iOS.
9+
- Some setters on UITextView are overridden to pass font and text alignment data through to the placeholder label. `setAttributedText:` has *not* been.
10+
- This has been tested on an iPhone 5 running iOS 6.1.4, and in the iOS 6.1 Simulator for both non-Retina and Retina iPads.
11+
12+
Open an issue if you have any problems!

0 commit comments

Comments
 (0)