123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 |
- // The MIT License (MIT)
- //
- // Copyright (c) 2014 Suyeol Jeon (http:xoul.kr)
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in all
- // copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- // SOFTWARE.
- #import <objc/runtime.h>
- #import "UITextView+Placeholder.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];
- UITextView *textView = objc_getAssociatedObject(self, @selector(placeholderTextView));
- if (textView) {
- 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 = [UIColor grayColor];
- });
- return color;
- }
- #pragma mark - `observingKeys`
- + (NSArray *)observingKeys {
- return @[@"attributedText",
- @"bounds",
- @"font",
- @"frame",
- @"text",
- @"textAlignment",
- @"textContainerInset",
- @"textContainer.exclusionPaths"];
- }
- #pragma mark - Properties
- #pragma mark `placeholderTextView`
- - (UITextView *)placeholderTextView {
- UITextView *textView = objc_getAssociatedObject(self, @selector(placeholderTextView));
- if (!textView) {
- NSAttributedString *originalText = self.attributedText;
- self.text = @" "; // lazily set font of `UITextView`.
- self.attributedText = originalText;
- textView = [[UITextView alloc] init];
- textView.textColor = [self.class defaultPlaceholderColor];
- textView.userInteractionEnabled = NO;
- objc_setAssociatedObject(self, @selector(placeholderTextView), textView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- self.needsUpdateFont = YES;
- [self updatePlaceholderTextView];
- self.needsUpdateFont = NO;
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(updatePlaceholderTextView)
- name:UITextViewTextDidChangeNotification
- object:self];
- for (NSString *key in self.class.observingKeys) {
- [self addObserver:self forKeyPath:key options:NSKeyValueObservingOptionNew context:nil];
- }
- }
- return textView;
- }
- #pragma mark `placeholder`
- - (NSString *)placeholder {
- return self.placeholderTextView.text;
- }
- - (void)setPlaceholder:(NSString *)placeholder {
- self.placeholderTextView.text = placeholder;
- [self updatePlaceholderTextView];
- }
- - (NSAttributedString *)attributedPlaceholder {
- return self.placeholderTextView.attributedText;
- }
- - (void)setAttributedPlaceholder:(NSAttributedString *)attributedPlaceholder {
- self.placeholderTextView.attributedText = attributedPlaceholder;
- [self updatePlaceholderTextView];
- }
- #pragma mark `placeholderColor`
- - (UIColor *)placeholderColor {
- return self.placeholderTextView.textColor;
- }
- - (void)setPlaceholderColor:(UIColor *)placeholderColor {
- self.placeholderTextView.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 updatePlaceholderTextView];
- }
- #pragma mark - Update
- - (void)updatePlaceholderTextView {
- if (self.text.length) {
- [self.placeholderTextView removeFromSuperview];
- } else {
- [self insertSubview:self.placeholderTextView atIndex:0];
- }
- if (self.needsUpdateFont) {
- self.placeholderTextView.font = self.font;
- self.needsUpdateFont = NO;
- }
- self.placeholderTextView.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);
- }
- self.placeholderTextView.textContainer.exclusionPaths = self.textContainer.exclusionPaths;
- self.placeholderTextView.textContainerInset = self.textContainerInset;
- self.placeholderTextView.frame = self.bounds;
- }
- @end
|