123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- #import "UITextView+Placeholder.h"
- #import <objc/runtime.h>
- @implementation UITextView (Placeholder)
- #pragma mark - Swizzle Dealloc
- + (void)load {
- // is this the best solution?
- method_exchangeImplementations(class_getInstanceMethod(self.class, NSSelectorFromString(@"dealloc")),
- class_getInstanceMethod(self.class, @selector(swizzledDealloc)));
- }
- - (void)swizzledDealloc {
- [[NSNotificationCenter defaultCenter] removeObserver:self];
- UILabel *label = objc_getAssociatedObject(self, @selector(placeholderLabel));
- if (label) {
- for (NSString *key in self.class.observingKeys) {
- @try {
- [self removeObserver:self forKeyPath:key];
- }
- @catch (NSException *exception) {
- // Do nothing
- }
- }
- }
- [self swizzledDealloc];
- }
- #pragma mark - Class Methods
- #pragma mark `defaultPlaceholderColor`
- + (UIColor *)defaultPlaceholderColor {
- static UIColor *color = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- UITextField *textField = [[UITextField alloc] init];
- textField.placeholder = @" ";
- color = [textField valueForKeyPath:@"_placeholderLabel.textColor"];
- });
- return color;
- }
- #pragma mark - `observingKeys`
- + (NSArray *)observingKeys {
- return @[@"attributedText",
- @"bounds",
- @"font",
- @"frame",
- @"text",
- @"textAlignment",
- @"textContainerInset"];
- }
- #pragma mark - Properties
- #pragma mark `placeholderLabel`
- - (UILabel *)placeholderLabel {
- UILabel *label = objc_getAssociatedObject(self, @selector(placeholderLabel));
- if (!label) {
- NSAttributedString *originalText = self.attributedText;
- self.text = @" "; // lazily set font of `UITextView`.
- self.attributedText = originalText;
- label = [[UILabel alloc] init];
- label.textColor = [self.class defaultPlaceholderColor];
- label.numberOfLines = 0;
- label.userInteractionEnabled = NO;
- objc_setAssociatedObject(self, @selector(placeholderLabel), label, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- self.needsUpdateFont = YES;
- [self updatePlaceholderLabel];
- self.needsUpdateFont = NO;
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(updatePlaceholderLabel)
- name:UITextViewTextDidChangeNotification
- object:self];
- for (NSString *key in self.class.observingKeys) {
- [self addObserver:self forKeyPath:key options:NSKeyValueObservingOptionNew context:nil];
- }
- }
- return label;
- }
- #pragma mark `placeholder`
- - (NSString *)placeholder {
- return self.placeholderLabel.text;
- }
- - (void)setPlaceholder:(NSString *)placeholder {
- self.placeholderLabel.text = placeholder;
- [self updatePlaceholderLabel];
- }
- - (NSAttributedString *)attributedPlaceholder {
- return self.placeholderLabel.attributedText;
- }
- - (void)setAttributedPlaceholder:(NSAttributedString *)attributedPlaceholder {
- self.placeholderLabel.attributedText = attributedPlaceholder;
- [self updatePlaceholderLabel];
- }
- #pragma mark `placeholderColor`
- - (UIColor *)placeholderColor {
- return self.placeholderLabel.textColor;
- }
- - (void)setPlaceholderColor:(UIColor *)placeholderColor {
- self.placeholderLabel.textColor = placeholderColor;
- }
- #pragma mark `needsUpdateFont`
- - (BOOL)needsUpdateFont {
- return [objc_getAssociatedObject(self, @selector(needsUpdateFont)) boolValue];
- }
- - (void)setNeedsUpdateFont:(BOOL)needsUpdate {
- objc_setAssociatedObject(self, @selector(needsUpdateFont), @(needsUpdate), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- }
- #pragma mark - KVO
- - (void)observeValueForKeyPath:(NSString *)keyPath
- ofObject:(id)object
- change:(NSDictionary *)change
- context:(void *)context {
- if ([keyPath isEqualToString:@"font"]) {
- self.needsUpdateFont = (change[NSKeyValueChangeNewKey] != nil);
- }
- [self updatePlaceholderLabel];
- }
- #pragma mark - Update
- - (void)updatePlaceholderLabel {
- if (self.text.length) {
- [self.placeholderLabel removeFromSuperview];
- return;
- }
- [self insertSubview:self.placeholderLabel atIndex:0];
- if (self.needsUpdateFont) {
- self.placeholderLabel.font = self.font;
- self.needsUpdateFont = NO;
- }
- self.placeholderLabel.textAlignment = self.textAlignment;
- // `NSTextContainer` is available since iOS 7
- CGFloat lineFragmentPadding;
- UIEdgeInsets textContainerInset;
- #pragma deploymate push "ignored-api-availability"
- // iOS 7+
- if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1) {
- lineFragmentPadding = self.textContainer.lineFragmentPadding;
- textContainerInset = self.textContainerInset;
- }
- #pragma deploymate pop
- // iOS 6
- else {
- lineFragmentPadding = 5;
- textContainerInset = UIEdgeInsetsMake(8, 0, 8, 0);
- }
- CGFloat x = lineFragmentPadding + textContainerInset.left;
- CGFloat y = textContainerInset.top;
- CGFloat width = CGRectGetWidth(self.bounds) - x - lineFragmentPadding - textContainerInset.right;
- CGFloat height = [self.placeholderLabel sizeThatFits:CGSizeMake(width, 0)].height;
- self.placeholderLabel.frame = CGRectMake(x, y, width, height);
- }
- @end
|