-
Notifications
You must be signed in to change notification settings - Fork 16
/
MPTextView.m
194 lines (146 loc) · 6 KB
/
MPTextView.m
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
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
//
// MPTextView.m
//
// Created by Christopher Bowns on 7/25/13.
// Licensed under the Apache v2 license.
//
#import "MPTextView.h"
// Manually-selected label offsets to align placeholder label with text entry.
static CGFloat const kLabelLeftOffset = 8.f;
static CGFloat const kLabelTopOffset = 0.f;
// When instantiated from IB, the text view has an 8 point top offset:
static CGFloat const kLabelTopOffsetFromIB = 8.f;
// On retina iPhones and iPads, the label is offset by 0.5 points:
static CGFloat const kLabelTopOffsetRetina = 0.5f;
@interface MPTextView ()
@property (nonatomic, strong) UILabel *placeholderLabel;
// The top offset differs when the view is instantiated from IB or programmatically.
// Use this to track the instantiation route and offset the label accordingly.
@property (nonatomic, assign) CGFloat topLabelOffset;
// Handle text changed event so we can update the placeholder appropriately
- (void)textChanged:(NSNotification *)note;
@end
@implementation MPTextView
#pragma mark - Initializers
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
// Account for IB offset:
_topLabelOffset = kLabelTopOffsetFromIB;
[self finishInitialization];
}
return self;
}
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_topLabelOffset = kLabelTopOffset;
[self finishInitialization];
}
return self;
}
// Private method for finishing initialization.
// Since this class isn't documented for subclassing,
// I don't feel comfortable changing the initializer chain.
// Let's do it this way rather than overriding UIView's designated initializer.
- (void)finishInitialization {
// Sign up for notifications for text changes:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(textChanged:)
name:UITextViewTextDidChangeNotification
object:self];
CGFloat labelLeftOffset = kLabelLeftOffset;
// Use our calculated label offset from initWith…:
CGFloat labelTopOffset = self.topLabelOffset;
// On retina iPhones and iPads, the label is offset by 0.5 points.
if ([[UIScreen mainScreen] scale] == 2.0) {
labelTopOffset += kLabelTopOffsetRetina;
}
CGSize labelOffset = CGSizeMake(labelLeftOffset, labelTopOffset);
CGRect labelFrame = [self placeholderLabelFrameWithOffset:labelOffset];
[self createPlaceholderLabel:labelFrame];
}
#pragma mark - Placeholder label helpers
// Create our label:
- (void)createPlaceholderLabel:(CGRect)labelFrame {
self.placeholderLabel = [[UILabel alloc] initWithFrame:labelFrame];
self.placeholderLabel.lineBreakMode = NSLineBreakByWordWrapping;
self.placeholderLabel.numberOfLines = 0;
self.placeholderLabel.font = self.font;
self.placeholderLabel.backgroundColor = [UIColor clearColor];
self.placeholderLabel.text = self.placeholderText;
// Color-matched to UITextField's placeholder text color:
self.placeholderLabel.textColor = [UIColor colorWithWhite:0.71f alpha:1.0f];
// UIKit effects on the UITextView, like selection ranges
// and the cursor, are done in a view above the text view,
// so no need to order this below anything else.
// Add the label as a subview.
[self addSubview:self.placeholderLabel];
}
- (CGRect)placeholderLabelFrameWithOffset:(CGSize)labelOffset {
return CGRectMake(labelOffset.width,
labelOffset.height,
self.bounds.size.width - (2 * labelOffset.width),
self.bounds.size.height - (2 * labelOffset.height));
}
#pragma mark - Custom accessors
- (void)setPlaceholderText:(NSString *)string {
_placeholderText = [string copy];
self.placeholderLabel.text = string;
[self.placeholderLabel sizeToFit];
}
#pragma mark - UITextView subclass methods
// Keep the placeholder label font in sync with the view's text font.
- (void)setFont:(UIFont *)font {
// Call super.
[super setFont:font];
self.placeholderLabel.font = self.font;
}
// Keep placeholder label alignment in sync with the view's text alignment.
- (void)setTextAlignment:(NSTextAlignment)textAlignment {
// Call super.
[super setTextAlignment:textAlignment];
self.placeholderLabel.textAlignment = textAlignment;
}
// Todo: override setAttributedText to capture changes
// in text alignment?
#pragma mark - UITextInput overrides
// Listen to dictation events to hide the placeholder as is appropriate.
// Hide when there's a dictation result placeholder
- (id)insertDictationResultPlaceholder {
// Call super.
id placeholder = [super insertDictationResultPlaceholder];
// Use -[setHidden] here instead of setAlpha:
// these events also trigger -[textChanged],
// which has a different criteria by which it shows the label,
// but we undeniably know we want this placeholder hidden.
self.placeholderLabel.hidden = YES;
return placeholder;
}
// Update visibility when dictation ends.
- (void)removeDictationResultPlaceholder:(id)placeholder willInsertResult:(BOOL)willInsertResult {
// Call super.
[super removeDictationResultPlaceholder:placeholder willInsertResult:willInsertResult];
// Unset the hidden flag from insertDictationResultPlaceholder.
self.placeholderLabel.hidden = NO;
// Update our text label based on the entered text.
[self updatePlaceholderLabelVisibility];
}
#pragma mark - Text change listeners
- (void)updatePlaceholderLabelVisibility {
if ([self.text length] == 0) {
self.placeholderLabel.alpha = 1.f;
} else {
self.placeholderLabel.alpha = 0.f;
}
}
// When text is set or changed, update the label's visibility.
- (void)setText:(NSString *)text {
// Call super.
[super setText:text];
[self updatePlaceholderLabelVisibility];
}
- (void)textChanged:(NSNotification *)notification {
[self updatePlaceholderLabelVisibility];
}
@end