123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909 |
- //
- // 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 "SLKDefaultTypingIndicatorView.h"
- #import "SLKTextView+SLKAdditions.h"
- #import "UIView+SLKAdditions.h"
- #import "SLKUIConstants.h"
- NSString * const SLKTextInputbarDidMoveNotification = @"SLKTextInputbarDidMoveNotification";
- NSString * const SLKTextInputbarContentSizeDidChangeNotification = @"SLKTextInputbarContentSizeDidChangeNotification";
- CGFloat const SLKTextInputbarMinButtonWidth = 44.0;
- CGFloat const SLKTextInputbarMinButtonHeight = 44.0;
- CGFloat const SLKTextInputbarTypingIndicatorHeight = 24.0;
- @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 *rightButtonHC;
- @property (nonatomic, strong) NSLayoutConstraint *rightMarginWC;
- @property (nonatomic, strong) NSLayoutConstraint *rightButtonTopMarginC;
- @property (nonatomic, strong) NSLayoutConstraint *rightButtonBottomMarginC;
- @property (nonatomic, strong) NSLayoutConstraint *editorContentViewHC;
- @property (nonatomic, strong) NSLayoutConstraint *typingIndicatorViewHC;
- @property (nonatomic, strong) NSLayoutConstraint *typingIndicatorViewTextViewPaddingConstraint;
- @property (nonatomic, strong) NSArray *charCountLabelVCs;
- @property (nonatomic, assign) UIEdgeInsets defaultInsets;
- @property (nonatomic, strong) UILabel *charCountLabel;
- @property (nonatomic) CGPoint previousOrigin;
- @property (nonatomic, strong) Class textViewClass;
- @property (nonatomic, strong) Class typingIndicatorClass;
- @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;
- }
- - (instancetype)initWithTextViewClass:(Class)textViewClass withTypingIndicatorViewClass:(Class)typingIndicatorClass
- {
- if (self = [super init]) {
- self.textViewClass = textViewClass;
- self.typingIndicatorClass = typingIndicatorClass;
- [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.defaultInsets = UIEdgeInsetsMake(5.0, 8.0, 5.0, 8.0);
- self.contentInset = _defaultInsets;
- // Since iOS 11, it is required to call -layoutSubviews before adding custom subviews
- // so private UIToolbar subviews don't interfere on the touch hierarchy
- [self layoutSubviews];
- [self addSubview:self.typingView];
- [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];
- }
- - (CGSize)intrinsicContentSize
- {
- return CGSizeMake(UIViewNoIntrinsicMetric, [self minimumInputbarHeight]);
- }
- + (BOOL)requiresConstraintBasedLayout
- {
- return YES;
- }
- - (void)safeAreaInsetsDidChange
- {
- UIEdgeInsets safeAreaInsets = self.safeAreaInsets;
- self.contentInset = UIEdgeInsetsMake(_defaultInsets.top + safeAreaInsets.top,
- _defaultInsets.left + safeAreaInsets.left,
- _defaultInsets.bottom + safeAreaInsets.bottom,
- _defaultInsets.right + safeAreaInsets.right);
- }
- #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 = 0.5;
- _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;
- }
- - (UIView *)typingView
- {
- if (!_typingView) {
- if (self.typingIndicatorClass == nil) {
- _typingView = [[SLKDefaultTypingIndicatorView alloc] init];
- } else {
- Class class = self.typingIndicatorClass;
- _typingView = [[class alloc] init];
- _typingView.translatesAutoresizingMaskIntoConstraints = NO;
- _typingView.clipsToBounds = YES;
- [_typingView addObserver:self forKeyPath:@"visible" options:NSKeyValueObservingOptionNew context:nil];
- }
- }
- return _typingView;
- }
- - (BOOL)isHidden
- {
- return _hidden;
- }
- - (CGFloat)minimumInputbarHeight
- {
- CGFloat minimumHeight = self.textView.intrinsicContentSize.height;
- minimumHeight += self.contentInset.top;
- minimumHeight += self.slk_bottomMargin;
- minimumHeight += [self slk_typingIndicatorHeight];
- 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;
- // Since the intrinsicContentSize (see SLKTextView) contains the lineHeight, we remove it here again
- height -= self.textView.font.lineHeight;
- height += roundf(self.textView.font.lineHeight * numberOfLines);
- height += roundf(self.textView.font.leading * numberOfLines);
- height += self.contentInset.top;
- height += self.slk_bottomMargin;
- height += [self slk_typingIndicatorHeight];
- 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_textViewHeight
- {
- return [self slk_contentViewHeight] - [self slk_typingIndicatorHeight];
- }
- - (CGFloat)slk_typingIndicatorHeight
- {
- return self.typingIndicatorViewHC.constant + self.typingIndicatorViewTextViewPaddingConstraint.constant;
- }
- - (CGFloat)slk_appropriateRightButtonWidth
- {
- if (self.autoHideRightButton) {
- if (self.textView.text.length == 0) {
- return 0.0;
- }
- }
-
- CGFloat width = [self.rightButton intrinsicContentSize].width;
- width = (width >= SLKTextInputbarMinButtonWidth) ? width : SLKTextInputbarMinButtonWidth;
- return 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 {
- 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];
- [self setCounterPosition:_counterPosition];
-
- // 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;
-
- [self slk_updateConstraintConstants];
- [super setNeedsLayout];
- [super layoutIfNeeded];
- }
- }
- - (void)setCounterPosition:(SLKCounterPosition)counterPosition
- {
- // 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)];
- }
- if (self.counterStyle == SLKCounterStyleLimitExceeded)
- {
- counter = [self limitExceeded] ? [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,
- @"typingView": self.typingView
- };
-
- NSDictionary *metrics = @{@"top" : @(self.contentInset.top),
- @"left" : @(self.contentInset.left),
- @"right" : @(self.contentInset.right),
- @"buttonMargin" : @(MIN(self.contentInset.left, self.contentInset.right)),
- };
-
- [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(left)-[leftButton(0)]-(<=buttonMargin)-[textView]-(buttonMargin)-[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)]-(<=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:@"H:|-(50)-[typingView]|" 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]];
- NSArray<NSLayoutConstraint *> *verticalTypingIndicatorTextViewContraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[editorContentView(0)]-(<=top)-[typingView(0)]-(<=0)-[textView(0@999)]-(0)-|" options:0 metrics:metrics views:views];
- self.typingIndicatorViewTextViewPaddingConstraint = verticalTypingIndicatorTextViewContraints[4];
- [self addConstraints:verticalTypingIndicatorTextViewContraints];
- self.textViewBottomMarginC = [self slk_constraintForAttribute:NSLayoutAttributeBottom firstItem:self secondItem:self.textView];
- self.editorContentViewHC = [self slk_constraintForAttribute:NSLayoutAttributeHeight firstItem:self.editorContentView secondItem:nil];
- self.typingIndicatorViewHC = [self slk_constraintForAttribute:NSLayoutAttributeHeight firstItem:self.typingView 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.rightButtonHC = [self slk_constraintForAttribute:NSLayoutAttributeHeight 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.rightButtonHC.constant = zero;
- self.rightMarginWC.constant = zero;
- }
- else {
- self.editorContentViewHC.constant = zero;
- // When the inputbar is hidden, we need to hide the buttons as well
- if (self->_hidden) {
- self.leftButtonHC.constant = zero;
- self.rightButtonHC.constant = zero;
- return;
- }
-
- CGSize leftButtonSize = [self.leftButton imageForState:self.leftButton.state].size;
- CGSize rightButtonSize = [self.rightButton imageForState:self.rightButton.state].size;
-
- if (leftButtonSize.width > 0) {
- leftButtonSize.width = (leftButtonSize.width >= SLKTextInputbarMinButtonWidth) ? leftButtonSize.width : SLKTextInputbarMinButtonWidth;
- float leftButtonHeight = (leftButtonSize.height >= SLKTextInputbarMinButtonHeight) ? leftButtonSize.height : SLKTextInputbarMinButtonHeight;
- self.leftButtonHC.constant = roundf(leftButtonHeight);
- self.leftButtonBottomMarginC.constant = roundf((self.intrinsicContentSize.height - leftButtonHeight) / 2.0) + self.slk_textViewHeight / 2.0;
- }
-
- self.leftButtonWC.constant = roundf(leftButtonSize.width);
- self.leftMarginWC.constant = (leftButtonSize.width > 0) ? self.contentInset.left : 16;
-
- self.rightButtonWC.constant = [self slk_appropriateRightButtonWidth];
- self.rightMarginWC.constant = [self slk_appropriateRightButtonMargin];
- float rightButtonHeight = (rightButtonSize.height >= SLKTextInputbarMinButtonHeight) ? rightButtonSize.height : SLKTextInputbarMinButtonHeight;
- self.rightButtonHC.constant = roundf(rightButtonHeight);
- self.rightButtonBottomMarginC.constant = roundf((self.intrinsicContentSize.height - rightButtonHeight) / 2.0) + self.slk_textViewHeight / 2.0;
- }
- }
- #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 if ([object conformsToProtocol:@protocol(SLKVisibleViewProtocol)] && [keyPath isEqualToString:@"visible"]) {
- [self slk_animateLayoutIfNeededWithBounce:NO options:UIViewAnimationOptionCurveEaseInOut|UIViewAnimationOptionLayoutSubviews|UIViewAnimationOptionBeginFromCurrentState animations:^{
- if (self.typingView.isVisible) {
- self.typingIndicatorViewHC.constant = SLKTextInputbarTypingIndicatorHeight;
- self.typingIndicatorViewTextViewPaddingConstraint.constant = self.contentInset.top;
- } else {
- self.typingIndicatorViewHC.constant = 0;
- self.typingIndicatorViewTextViewPaddingConstraint.constant = 0;
- }
- // Make sure we update the scrollView position in the viewController as well
- [[NSNotificationCenter defaultCenter] postNotificationName:SLKTextInputbarContentSizeDidChangeNotification object:self];
- }];
- }
- 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)];
- [self.typingView removeObserver:self forKeyPath:@"visible"];
- }
- @end
|