// // SlackTextViewController // https://github.com/slackhq/SlackTextViewController // // Copyright 2014-2016 Slack Technologies, Inc. // Licence: MIT-Licence // #import "SLKTextInputbar.h" #import "SLKTextView.h" #import "SLKInputAccessoryView.h" #import "SLKTextView+SLKAdditions.h" #import "UIView+SLKAdditions.h" #import "SLKUIConstants.h" NSString * const SLKTextInputbarDidMoveNotification = @"SLKTextInputbarDidMoveNotification"; @interface SLKTextInputbar () @property (nonatomic, strong) NSLayoutConstraint *textViewBottomMarginC; @property (nonatomic, strong) NSLayoutConstraint *contentViewHC; @property (nonatomic, strong) NSLayoutConstraint *leftButtonWC; @property (nonatomic, strong) NSLayoutConstraint *leftButtonHC; @property (nonatomic, strong) NSLayoutConstraint *leftMarginWC; @property (nonatomic, strong) NSLayoutConstraint *leftButtonBottomMarginC; @property (nonatomic, strong) NSLayoutConstraint *rightButtonWC; @property (nonatomic, strong) NSLayoutConstraint *rightMarginWC; @property (nonatomic, strong) NSLayoutConstraint *rightButtonTopMarginC; @property (nonatomic, strong) NSLayoutConstraint *rightButtonBottomMarginC; @property (nonatomic, strong) NSLayoutConstraint *editorContentViewHC; @property (nonatomic, strong) NSArray *charCountLabelVCs; @property (nonatomic, strong) UILabel *charCountLabel; @property (nonatomic) CGPoint previousOrigin; @property (nonatomic, strong) Class textViewClass; @property (nonatomic, getter=isHidden) BOOL hidden; // Required override @end @implementation SLKTextInputbar @synthesize textView = _textView; @synthesize contentView = _contentView; @synthesize inputAccessoryView = _inputAccessoryView; @synthesize hidden = _hidden; #pragma mark - Initialization - (instancetype)initWithTextViewClass:(Class)textViewClass { if (self = [super init]) { self.textViewClass = textViewClass; [self slk_commonInit]; } return self; } - (id)init { if (self = [super init]) { [self slk_commonInit]; } return self; } - (instancetype)initWithCoder:(NSCoder *)coder { if (self = [super initWithCoder:coder]) { [self slk_commonInit]; } return self; } - (void)slk_commonInit { self.charCountLabelNormalColor = [UIColor lightGrayColor]; self.charCountLabelWarningColor = [UIColor redColor]; self.autoHideRightButton = YES; self.editorContentViewHeight = 38.0; self.contentInset = UIEdgeInsetsMake(5.0, 8.0, 5.0, 8.0); [self addSubview:self.editorContentView]; [self addSubview:self.leftButton]; [self addSubview:self.rightButton]; [self addSubview:self.textView]; [self addSubview:self.charCountLabel]; [self addSubview:self.contentView]; [self slk_setupViewConstraints]; [self slk_updateConstraintConstants]; self.counterStyle = SLKCounterStyleNone; self.counterPosition = SLKCounterPositionTop; [self slk_registerNotifications]; [self slk_registerTo:self.layer forSelector:@selector(position)]; [self slk_registerTo:self.leftButton.imageView forSelector:@selector(image)]; [self slk_registerTo:self.rightButton.titleLabel forSelector:@selector(font)]; } #pragma mark - UIView Overrides - (void)layoutIfNeeded { if (self.constraints.count == 0 || !self.window) { return; } [self slk_updateConstraintConstants]; [super layoutIfNeeded]; } // Compensate for layout bug in iOS 11 Beta 2 // https://github.com/Recfive/SlackTextViewController/commit/5faf3952fb1623508120c7f44291bfb0259f25af - (void)layoutSubviews { [super layoutSubviews]; [self bringSubviewToFront:self.textView]; [self bringSubviewToFront:self.rightButton]; [self bringSubviewToFront:self.leftButton]; } - (CGSize)intrinsicContentSize { return CGSizeMake(UIViewNoIntrinsicMetric, [self minimumInputbarHeight]); } + (BOOL)requiresConstraintBasedLayout { return YES; } #pragma mark - Getters - (SLKTextView *)textView { if (!_textView) { Class class = self.textViewClass ? : [SLKTextView class]; _textView = [[class alloc] init]; _textView.translatesAutoresizingMaskIntoConstraints = NO; _textView.font = [UIFont systemFontOfSize:15.0]; _textView.maxNumberOfLines = [self slk_defaultNumberOfLines]; _textView.keyboardType = UIKeyboardTypeTwitter; _textView.returnKeyType = UIReturnKeyDefault; _textView.enablesReturnKeyAutomatically = YES; _textView.scrollIndicatorInsets = UIEdgeInsetsMake(0.0, -1.0, 0.0, 1.0); _textView.textContainerInset = UIEdgeInsetsMake(8.0, 4.0, 8.0, 0.0); _textView.layer.cornerRadius = 5.0; _textView.layer.borderWidth = 1 / [UIScreen mainScreen].scale; _textView.layer.borderColor = [UIColor colorWithRed:200.0/255.0 green:200.0/255.0 blue:205.0/255.0 alpha:1.0].CGColor; } return _textView; } - (UIView *)contentView { if (!_contentView) { _contentView = [UIView new]; _contentView.translatesAutoresizingMaskIntoConstraints = NO; _contentView.backgroundColor = [UIColor clearColor]; _contentView.clipsToBounds = YES; } return _contentView; } - (SLKInputAccessoryView *)inputAccessoryView { if (!_inputAccessoryView) { _inputAccessoryView = [[SLKInputAccessoryView alloc] initWithFrame:CGRectZero]; _inputAccessoryView.backgroundColor = [UIColor clearColor]; _inputAccessoryView.userInteractionEnabled = NO; } return _inputAccessoryView; } - (UIButton *)leftButton { if (!_leftButton) { _leftButton = [UIButton buttonWithType:UIButtonTypeSystem]; _leftButton.translatesAutoresizingMaskIntoConstraints = NO; _leftButton.titleLabel.font = [UIFont systemFontOfSize:15.0]; } return _leftButton; } - (UIButton *)rightButton { if (!_rightButton) { _rightButton = [UIButton buttonWithType:UIButtonTypeSystem]; _rightButton.translatesAutoresizingMaskIntoConstraints = NO; _rightButton.titleLabel.font = [UIFont boldSystemFontOfSize:15.0]; _rightButton.enabled = NO; NSString *title = NSLocalizedString(@"Send", nil); [_rightButton setTitle:title forState:UIControlStateNormal]; } return _rightButton; } - (UIView *)editorContentView { if (!_editorContentView) { _editorContentView = [UIView new]; _editorContentView.translatesAutoresizingMaskIntoConstraints = NO; _editorContentView.backgroundColor = self.backgroundColor; _editorContentView.clipsToBounds = YES; _editorContentView.hidden = YES; [_editorContentView addSubview:self.editorTitle]; [_editorContentView addSubview:self.editorLeftButton]; [_editorContentView addSubview:self.editorRightButton]; NSDictionary *views = @{@"label": self.editorTitle, @"leftButton": self.editorLeftButton, @"rightButton": self.editorRightButton, }; NSDictionary *metrics = @{@"left" : @(self.contentInset.left), @"right" : @(self.contentInset.right) }; [_editorContentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(left)-[leftButton(60)]-(left)-[label(>=0)]-(right)-[rightButton(60)]-(<=right)-|" options:0 metrics:metrics views:views]]; [_editorContentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[leftButton]|" options:0 metrics:metrics views:views]]; [_editorContentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[rightButton]|" options:0 metrics:metrics views:views]]; [_editorContentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[label]|" options:0 metrics:metrics views:views]]; } return _editorContentView; } - (UILabel *)editorTitle { if (!_editorTitle) { _editorTitle = [UILabel new]; _editorTitle.translatesAutoresizingMaskIntoConstraints = NO; _editorTitle.textAlignment = NSTextAlignmentCenter; _editorTitle.backgroundColor = [UIColor clearColor]; _editorTitle.font = [UIFont boldSystemFontOfSize:15.0]; NSString *title = NSLocalizedString(@"Editing Message", nil); _editorTitle.text = title; } return _editorTitle; } - (UIButton *)editorLeftButton { if (!_editorLeftButton) { _editorLeftButton = [UIButton buttonWithType:UIButtonTypeSystem]; _editorLeftButton.translatesAutoresizingMaskIntoConstraints = NO; _editorLeftButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; _editorLeftButton.titleLabel.font = [UIFont systemFontOfSize:15.0]; NSString *title = NSLocalizedString(@"Cancel", nil); [_editorLeftButton setTitle:title forState:UIControlStateNormal]; } return _editorLeftButton; } - (UIButton *)editorRightButton { if (!_editorRightButton) { _editorRightButton = [UIButton buttonWithType:UIButtonTypeSystem]; _editorRightButton.translatesAutoresizingMaskIntoConstraints = NO; _editorRightButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight; _editorRightButton.titleLabel.font = [UIFont boldSystemFontOfSize:15.0]; _editorRightButton.enabled = NO; NSString *title = NSLocalizedString(@"Save", nil); [_editorRightButton setTitle:title forState:UIControlStateNormal]; } return _editorRightButton; } - (UILabel *)charCountLabel { if (!_charCountLabel) { _charCountLabel = [UILabel new]; _charCountLabel.translatesAutoresizingMaskIntoConstraints = NO; _charCountLabel.backgroundColor = [UIColor clearColor]; _charCountLabel.textAlignment = NSTextAlignmentRight; _charCountLabel.font = [UIFont systemFontOfSize:11.0]; _charCountLabel.hidden = NO; } return _charCountLabel; } - (BOOL)isHidden { return _hidden; } - (CGFloat)minimumInputbarHeight { CGFloat minimumHeight = self.textView.intrinsicContentSize.height; minimumHeight += self.contentInset.top; minimumHeight += self.slk_bottomMargin; return minimumHeight; } - (CGFloat)appropriateHeight { CGFloat height = 0.0; CGFloat minimumHeight = [self minimumInputbarHeight]; if (self.textView.numberOfLines == 1) { height = minimumHeight; } else if (self.textView.numberOfLines < self.textView.maxNumberOfLines) { height = [self slk_inputBarHeightForLines:self.textView.numberOfLines]; } else { height = [self slk_inputBarHeightForLines:self.textView.maxNumberOfLines]; } if (height < minimumHeight) { height = minimumHeight; } if (self.isEditing) { height += self.editorContentViewHeight; } return roundf(height); } - (BOOL)limitExceeded { NSString *text = [self.textView.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; if (self.maxCharCount > 0 && text.length > self.maxCharCount) { return YES; } return NO; } - (CGFloat)slk_inputBarHeightForLines:(NSUInteger)numberOfLines { CGFloat height = self.textView.intrinsicContentSize.height; height -= self.textView.font.lineHeight; height += roundf(self.textView.font.lineHeight*numberOfLines); height += self.contentInset.top; height += self.slk_bottomMargin; return height; } - (CGFloat)slk_bottomMargin { CGFloat margin = self.contentInset.bottom; margin += self.slk_contentViewHeight; return margin; } - (CGFloat)slk_contentViewHeight { if (!self.editing) { return CGRectGetHeight(self.contentView.frame); } return 0.0; } - (CGFloat)slk_appropriateRightButtonWidth { if (self.autoHideRightButton) { if (self.textView.text.length == 0) { return 0.0; } } return [self.rightButton intrinsicContentSize].width; } - (CGFloat)slk_appropriateRightButtonMargin { if (self.autoHideRightButton) { if (self.textView.text.length == 0) { return 0.0; } } return self.contentInset.right; } - (NSUInteger)slk_defaultNumberOfLines { if (SLK_IS_IPAD) { return 8; } else if (SLK_IS_IPHONE4) { return 4; } else { return 6; } } #pragma mark - Setters - (void)setBackgroundColor:(UIColor *)color { self.barTintColor = color; self.editorContentView.backgroundColor = color; } - (void)setAutoHideRightButton:(BOOL)hide { if (self.autoHideRightButton == hide) { return; } _autoHideRightButton = hide; self.rightButtonWC.constant = [self slk_appropriateRightButtonWidth]; self.rightMarginWC.constant = [self slk_appropriateRightButtonMargin]; [self layoutIfNeeded]; } - (void)setContentInset:(UIEdgeInsets)insets { if (UIEdgeInsetsEqualToEdgeInsets(self.contentInset, insets)) { return; } if (UIEdgeInsetsEqualToEdgeInsets(self.contentInset, UIEdgeInsetsZero)) { _contentInset = insets; return; } _contentInset = insets; // Add new constraints [self removeConstraints:self.constraints]; [self slk_setupViewConstraints]; // Add constant values and refresh layout [self slk_updateConstraintConstants]; [super layoutIfNeeded]; } - (void)setEditing:(BOOL)editing { if (self.isEditing == editing) { return; } _editing = editing; _editorContentView.hidden = !editing; self.contentViewHC.active = editing; [super setNeedsLayout]; [super layoutIfNeeded]; } - (void)setHidden:(BOOL)hidden { // We don't call super here, since we want to avoid to visually hide the view. // The hidden render state is handled by the view controller. _hidden = hidden; if (!self.isEditing) { self.contentViewHC.active = hidden; [super setNeedsLayout]; [super layoutIfNeeded]; } } - (void)setCounterPosition:(SLKCounterPosition)counterPosition { if (self.counterPosition == counterPosition && self.charCountLabelVCs) { return; } // Clears the previous constraints if (_charCountLabelVCs.count > 0) { [self removeConstraints:_charCountLabelVCs]; _charCountLabelVCs = nil; } _counterPosition = counterPosition; NSDictionary *views = @{@"rightButton": self.rightButton, @"charCountLabel": self.charCountLabel }; NSDictionary *metrics = @{@"top" : @(self.contentInset.top), @"bottom" : @(-self.slk_bottomMargin/2.0) }; // Constraints are different depending of the counter's position type if (counterPosition == SLKCounterPositionBottom) { _charCountLabelVCs = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[charCountLabel]-(bottom)-[rightButton]" options:0 metrics:metrics views:views]; } else { _charCountLabelVCs = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(top@750)-[charCountLabel]-(>=0)-|" options:0 metrics:metrics views:views]; } [self addConstraints:self.charCountLabelVCs]; } #pragma mark - Text Editing - (BOOL)canEditText:(NSString *)text { if ((self.isEditing && [self.textView.text isEqualToString:text]) || self.isHidden) { return NO; } return YES; } - (void)beginTextEditing { if (self.isEditing || self.isHidden) { return; } self.editing = YES; [self slk_updateConstraintConstants]; if (!self.isFirstResponder) { [self layoutIfNeeded]; } } - (void)endTextEdition { if (!self.isEditing || self.isHidden) { return; } self.editing = NO; [self slk_updateConstraintConstants]; } #pragma mark - Character Counter - (void)slk_updateCounter { NSString *text = [self.textView.text stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]; NSString *counter = nil; if (self.counterStyle == SLKCounterStyleNone) { counter = [NSString stringWithFormat:@"%lu", (unsigned long)text.length]; } if (self.counterStyle == SLKCounterStyleSplit) { counter = [NSString stringWithFormat:@"%lu/%lu", (unsigned long)text.length, (unsigned long)self.maxCharCount]; } if (self.counterStyle == SLKCounterStyleCountdown) { counter = [NSString stringWithFormat:@"%ld", (long)(text.length - self.maxCharCount)]; } if (self.counterStyle == SLKCounterStyleCountdownReversed) { counter = [NSString stringWithFormat:@"%ld", (long)(self.maxCharCount - text.length)]; } self.charCountLabel.text = counter; self.charCountLabel.textColor = [self limitExceeded] ? self.charCountLabelWarningColor : self.charCountLabelNormalColor; } #pragma mark - Notification Events - (void)slk_didChangeTextViewText:(NSNotification *)notification { SLKTextView *textView = (SLKTextView *)notification.object; // Skips this it's not the expected textView. if (![textView isEqual:self.textView]) { return; } // Updates the char counter label if (self.maxCharCount > 0) { [self slk_updateCounter]; } if (self.autoHideRightButton && !self.isEditing) { CGFloat rightButtonNewWidth = [self slk_appropriateRightButtonWidth]; // Only updates if the width did change if (self.rightButtonWC.constant == rightButtonNewWidth) { return; } self.rightButtonWC.constant = rightButtonNewWidth; self.rightMarginWC.constant = [self slk_appropriateRightButtonMargin]; [self.rightButton layoutIfNeeded]; // Avoids the right button to stretch when animating the constraint changes BOOL bounces = self.bounces && [self.textView isFirstResponder]; if (self.window) { [self slk_animateLayoutIfNeededWithBounce:bounces options:UIViewAnimationOptionCurveEaseInOut|UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionAllowUserInteraction animations:NULL]; } else { [self layoutIfNeeded]; } } } - (void)slk_didChangeTextViewContentSize:(NSNotification *)notification { if (self.maxCharCount > 0) { BOOL shouldHide = (self.textView.numberOfLines == 1) || self.editing; self.charCountLabel.hidden = shouldHide; } } - (void)slk_didChangeContentSizeCategory:(NSNotification *)notification { if (!self.textView.isDynamicTypeEnabled) { return; } [self layoutIfNeeded]; } #pragma mark - View Auto-Layout - (void)slk_setupViewConstraints { NSDictionary *views = @{@"textView": self.textView, @"leftButton": self.leftButton, @"rightButton": self.rightButton, @"editorContentView": self.editorContentView, @"charCountLabel": self.charCountLabel, @"contentView": self.contentView, }; NSDictionary *metrics = @{@"top" : @(self.contentInset.top), @"left" : @(self.contentInset.left), @"right" : @(self.contentInset.right), }; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(left)-[leftButton(0)]-(<=left)-[textView]-(right)-[rightButton(0)]-(right)-|" options:0 metrics:metrics views:views]]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=0)-[leftButton(0)]-(0@750)-|" options:0 metrics:metrics views:views]]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=0)-[rightButton]-(<=0)-|" options:0 metrics:metrics views:views]]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(left@250)-[charCountLabel(<=50@1000)]-(right@750)-|" options:0 metrics:metrics views:views]]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[editorContentView(0)]-(<=top)-[textView(0@999)]-(0)-|" options:0 metrics:metrics views:views]]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[editorContentView]|" options:0 metrics:metrics views:views]]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[contentView]|" options:0 metrics:metrics views:views]]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[contentView(0)]|" options:0 metrics:metrics views:views]]; self.textViewBottomMarginC = [self slk_constraintForAttribute:NSLayoutAttributeBottom firstItem:self secondItem:self.textView]; self.editorContentViewHC = [self slk_constraintForAttribute:NSLayoutAttributeHeight firstItem:self.editorContentView secondItem:nil]; self.contentViewHC = [self slk_constraintForAttribute:NSLayoutAttributeHeight firstItem:self.contentView secondItem:nil];; self.contentViewHC.active = NO; // Disabled by default, so the height is calculated with the height of its subviews self.leftButtonWC = [self slk_constraintForAttribute:NSLayoutAttributeWidth firstItem:self.leftButton secondItem:nil]; self.leftButtonHC = [self slk_constraintForAttribute:NSLayoutAttributeHeight firstItem:self.leftButton secondItem:nil]; self.leftButtonBottomMarginC = [self slk_constraintForAttribute:NSLayoutAttributeBottom firstItem:self secondItem:self.leftButton]; self.leftMarginWC = [[self slk_constraintsForAttribute:NSLayoutAttributeLeading] firstObject]; self.rightButtonWC = [self slk_constraintForAttribute:NSLayoutAttributeWidth firstItem:self.rightButton secondItem:nil]; self.rightMarginWC = [[self slk_constraintsForAttribute:NSLayoutAttributeTrailing] firstObject]; self.rightButtonTopMarginC = [self slk_constraintForAttribute:NSLayoutAttributeTop firstItem:self.rightButton secondItem:self]; self.rightButtonBottomMarginC = [self slk_constraintForAttribute:NSLayoutAttributeBottom firstItem:self secondItem:self.rightButton]; } - (void)slk_updateConstraintConstants { CGFloat zero = 0.0; self.textViewBottomMarginC.constant = self.slk_bottomMargin; if (self.isEditing) { self.editorContentViewHC.constant = self.editorContentViewHeight; self.leftButtonWC.constant = zero; self.leftButtonHC.constant = zero; self.leftMarginWC.constant = zero; self.leftButtonBottomMarginC.constant = zero; self.rightButtonWC.constant = zero; self.rightMarginWC.constant = zero; } else { self.editorContentViewHC.constant = zero; CGSize leftButtonSize = [self.leftButton imageForState:self.leftButton.state].size; if (leftButtonSize.width > 0) { self.leftButtonHC.constant = roundf(leftButtonSize.height); self.leftButtonBottomMarginC.constant = roundf((self.intrinsicContentSize.height - leftButtonSize.height) / 2.0) + self.slk_contentViewHeight / 2.0; } self.leftButtonWC.constant = roundf(leftButtonSize.width); self.leftMarginWC.constant = (leftButtonSize.width > 0) ? self.contentInset.left : zero; self.rightButtonWC.constant = [self slk_appropriateRightButtonWidth]; self.rightMarginWC.constant = [self slk_appropriateRightButtonMargin]; CGFloat rightVerMargin = (self.intrinsicContentSize.height - self.slk_contentViewHeight - self.rightButton.intrinsicContentSize.height) / 2.0; CGFloat rightVerBottomMargin = rightVerMargin + self.slk_contentViewHeight; self.rightButtonTopMarginC.constant = rightVerMargin; self.rightButtonBottomMarginC.constant = rightVerBottomMargin; } } #pragma mark - Observers - (void)slk_registerTo:(id)object forSelector:(SEL)selector { if (object) { [object addObserver:self forKeyPath:NSStringFromSelector(selector) options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL]; } } - (void)slk_unregisterFrom:(id)object forSelector:(SEL)selector { if (object) { [object removeObserver:self forKeyPath:NSStringFromSelector(selector)]; } } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([object isEqual:self.layer] && [keyPath isEqualToString:NSStringFromSelector(@selector(position))]) { if (!CGPointEqualToPoint(self.previousOrigin, self.frame.origin)) { self.previousOrigin = self.frame.origin; [[NSNotificationCenter defaultCenter] postNotificationName:SLKTextInputbarDidMoveNotification object:self userInfo:@{@"origin": [NSValue valueWithCGPoint:self.previousOrigin]}]; } } else if ([object isEqual:self.leftButton.imageView] && [keyPath isEqualToString:NSStringFromSelector(@selector(image))]) { UIImage *newImage = change[NSKeyValueChangeNewKey]; UIImage *oldImage = change[NSKeyValueChangeOldKey]; if (![newImage isEqual:oldImage]) { [self slk_updateConstraintConstants]; } } else if ([object isEqual:self.rightButton.titleLabel] && [keyPath isEqualToString:NSStringFromSelector(@selector(font))]) { [self slk_updateConstraintConstants]; } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } #pragma mark - NSNotificationCenter registration - (void)slk_registerNotifications { [self slk_unregisterNotifications]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_didChangeTextViewText:) name:UITextViewTextDidChangeNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_didChangeTextViewContentSize:) name:SLKTextViewContentSizeDidChangeNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_didChangeContentSizeCategory:) name:UIContentSizeCategoryDidChangeNotification object:nil]; } - (void)slk_unregisterNotifications { [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidChangeNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:SLKTextViewContentSizeDidChangeNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UIContentSizeCategoryDidChangeNotification object:nil]; } #pragma mark - Lifeterm - (void)dealloc { [self slk_unregisterNotifications]; [self slk_unregisterFrom:self.layer forSelector:@selector(position)]; [self slk_unregisterFrom:self.leftButton.imageView forSelector:@selector(image)]; [self slk_unregisterFrom:self.rightButton.titleLabel forSelector:@selector(font)]; } @end