http://git-wip-us.apache.org/repos/asf/usergrid/blob/c638c774/sdks/swift/Samples/ActivityFeed/Pods/SlackTextViewController/Source/SLKTextViewController.m ---------------------------------------------------------------------- diff --git a/sdks/swift/Samples/ActivityFeed/Pods/SlackTextViewController/Source/SLKTextViewController.m b/sdks/swift/Samples/ActivityFeed/Pods/SlackTextViewController/Source/SLKTextViewController.m deleted file mode 100644 index 5febfcb..0000000 --- a/sdks/swift/Samples/ActivityFeed/Pods/SlackTextViewController/Source/SLKTextViewController.m +++ /dev/null @@ -1,2392 +0,0 @@ -// -// Copyright 2014 Slack Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#import "SLKTextViewController.h" -#import "SLKInputAccessoryView.h" - -#import "UIResponder+SLKAdditions.h" - -/** Feature flagged while waiting to implement a more reliable technique. */ -#define SLKBottomPanningEnabled 0 - -#define kSLKAlertViewClearTextTag [NSStringFromClass([SLKTextViewController class]) hash] - -NSString * const SLKKeyboardWillShowNotification = @"SLKKeyboardWillShowNotification"; -NSString * const SLKKeyboardDidShowNotification = @"SLKKeyboardDidShowNotification"; -NSString * const SLKKeyboardWillHideNotification = @"SLKKeyboardWillHideNotification"; -NSString * const SLKKeyboardDidHideNotification = @"SLKKeyboardDidHideNotification"; - -CGFloat const SLKAutoCompletionViewDefaultHeight = 140.0; - -@interface SLKTextViewController () -{ - CGPoint _scrollViewOffsetBeforeDragging; - CGFloat _keyboardHeightBeforeDragging; -} - -// The shared scrollView pointer, either a tableView or collectionView -@property (nonatomic, weak) UIScrollView *scrollViewProxy; - -// A hairline displayed on top of the auto-completion view, to better separate the content from the control. -@property (nonatomic, strong) UIView *autoCompletionHairline; - -// Auto-Layout height constraints used for updating their constants -@property (nonatomic, strong) NSLayoutConstraint *scrollViewHC; -@property (nonatomic, strong) NSLayoutConstraint *textInputbarHC; -@property (nonatomic, strong) NSLayoutConstraint *typingIndicatorViewHC; -@property (nonatomic, strong) NSLayoutConstraint *autoCompletionViewHC; -@property (nonatomic, strong) NSLayoutConstraint *keyboardHC; - -// YES if the user is moving the keyboard with a gesture -@property (nonatomic, assign, getter = isMovingKeyboard) BOOL movingKeyboard; - -// The current keyboard status (hidden, showing, etc.) -@property (nonatomic) SLKKeyboardStatus keyboardStatus; - -// YES if the view controller did appear and everything is finished configurating. This allows blocking some layout animations among other things. -@property (nonatomic, getter=isViewVisible) BOOL viewVisible; - -// YES if the view controller's view's size is changing by its parent (i.e. when its window rotates or is resized) -@property (nonatomic, getter = isTransitioning) BOOL transitioning; - -// Optional classes to be used instead of the default ones. -@property (nonatomic, strong) Class textViewClass; -@property (nonatomic, strong) Class typingIndicatorViewClass; - -@end - -@implementation SLKTextViewController -@synthesize tableView = _tableView; -@synthesize collectionView = _collectionView; -@synthesize scrollView = _scrollView; -@synthesize typingIndicatorProxyView = _typingIndicatorProxyView; -@synthesize textInputbar = _textInputbar; -@synthesize autoCompletionView = _autoCompletionView; -@synthesize autoCompleting = _autoCompleting; -@synthesize scrollViewProxy = _scrollViewProxy; -@synthesize presentedInPopover = _presentedInPopover; - -#pragma mark - Initializer - -- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil -{ - return [self initWithTableViewStyle:UITableViewStylePlain]; -} - -- (instancetype)init -{ - return [self initWithTableViewStyle:UITableViewStylePlain]; -} - -- (instancetype)initWithTableViewStyle:(UITableViewStyle)style -{ - NSAssert([self class] != [SLKTextViewController class], @"Oops! You must subclass SLKTextViewController."); - - if (self = [super initWithNibName:nil bundle:nil]) - { - self.scrollViewProxy = [self tableViewWithStyle:style]; - [self slk_commonInit]; - } - return self; -} - -- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout -{ - NSAssert([self class] != [SLKTextViewController class], @"Oops! You must subclass SLKTextViewController."); - - if (self = [super initWithNibName:nil bundle:nil]) - { - self.scrollViewProxy = [self collectionViewWithLayout:layout]; - [self slk_commonInit]; - } - return self; -} - -- (instancetype)initWithScrollView:(UIScrollView *)scrollView -{ - NSAssert([self class] != [SLKTextViewController class], @"Oops! You must subclass SLKTextViewController."); - - if (self = [super initWithNibName:nil bundle:nil]) - { - _scrollView = scrollView; - _scrollView.translatesAutoresizingMaskIntoConstraints = NO; // Makes sure the scrollView plays nice with auto-layout - - self.scrollViewProxy = _scrollView; - [self slk_commonInit]; - } - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)decoder -{ - NSAssert([self class] != [SLKTextViewController class], @"Oops! You must subclass SLKTextViewController."); - - if (self = [super initWithCoder:decoder]) - { - UITableViewStyle tableViewStyle = [[self class] tableViewStyleForCoder:decoder]; - UICollectionViewLayout *collectionViewLayout = [[self class] collectionViewLayoutForCoder:decoder]; - - if ([collectionViewLayout isKindOfClass:[UICollectionViewLayout class]]) { - self.scrollViewProxy = [self collectionViewWithLayout:collectionViewLayout]; - } - else { - self.scrollViewProxy = [self tableViewWithStyle:tableViewStyle]; - } - - [self slk_commonInit]; - } - return self; -} - -- (void)slk_commonInit -{ - [self slk_registerNotifications]; - - self.bounces = YES; - self.inverted = YES; - self.shakeToClearEnabled = NO; - self.keyboardPanningEnabled = YES; - self.shouldClearTextAtRightButtonPress = YES; - self.shouldScrollToBottomAfterKeyboardShows = NO; - - self.automaticallyAdjustsScrollViewInsets = YES; - self.extendedLayoutIncludesOpaqueBars = YES; -} - - -#pragma mark - View lifecycle - -- (void)loadView -{ - [super loadView]; -} - -- (void)viewDidLoad -{ - [super viewDidLoad]; - - [self.view addSubview:self.scrollViewProxy]; - [self.view addSubview:self.autoCompletionView]; - [self.view addSubview:self.typingIndicatorProxyView]; - [self.view addSubview:self.textInputbar]; - - [self slk_setupViewConstraints]; -} - -- (void)viewWillAppear:(BOOL)animated -{ - [super viewWillAppear:animated]; - - // Invalidates this flag when the view appears - self.textView.didNotResignFirstResponder = NO; - - // Forces laying out the recently added subviews and update their constraints - [self.view layoutIfNeeded]; - - [UIView performWithoutAnimation:^{ - // Reloads any cached text - [self slk_reloadTextView]; - }]; -} - -- (void)viewDidAppear:(BOOL)animated -{ - [super viewDidAppear:animated]; - - [self.scrollViewProxy flashScrollIndicators]; - - self.viewVisible = YES; -} - -- (void)viewWillDisappear:(BOOL)animated -{ - [super viewWillDisappear:animated]; - - // Stops the keyboard from being dismissed during the navigation controller's "swipe-to-pop" - self.textView.didNotResignFirstResponder = self.isMovingFromParentViewController; - - self.viewVisible = NO; - - // Caches the text before it's too late! - [self slk_cacheTextView]; -} - -- (void)viewDidDisappear:(BOOL)animated -{ - [super viewDidDisappear:animated]; -} - -- (void)viewWillLayoutSubviews -{ - [super viewWillLayoutSubviews]; - - [self slk_adjustContentConfigurationIfNeeded]; -} - -- (void)viewDidLayoutSubviews -{ - [super viewDidLayoutSubviews]; -} - - -#pragma mark - Getters - -+ (UITableViewStyle)tableViewStyleForCoder:(NSCoder *)decoder -{ - return UITableViewStylePlain; -} - -+ (UICollectionViewLayout *)collectionViewLayoutForCoder:(NSCoder *)decoder -{ - return nil; -} - -- (UITableView *)tableViewWithStyle:(UITableViewStyle)style -{ - if (!_tableView) { - _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:style]; - _tableView.translatesAutoresizingMaskIntoConstraints = NO; - _tableView.scrollsToTop = YES; - _tableView.dataSource = self; - _tableView.delegate = self; - _tableView.clipsToBounds = NO; - } - return _tableView; -} - -- (UICollectionView *)collectionViewWithLayout:(UICollectionViewLayout *)layout -{ - if (!_collectionView) { - _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; - _collectionView.translatesAutoresizingMaskIntoConstraints = NO; - _collectionView.scrollsToTop = YES; - _collectionView.dataSource = self; - _collectionView.delegate = self; - } - return _collectionView; -} - -- (UITableView *)autoCompletionView -{ - if (!_autoCompletionView) { - _autoCompletionView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; - _autoCompletionView.translatesAutoresizingMaskIntoConstraints = NO; - _autoCompletionView.backgroundColor = [UIColor colorWithWhite:0.97 alpha:1.0]; - _autoCompletionView.scrollsToTop = NO; - _autoCompletionView.dataSource = self; - _autoCompletionView.delegate = self; - -#ifdef __IPHONE_9_0 - if ([_autoCompletionView respondsToSelector:@selector(cellLayoutMarginsFollowReadableWidth)]) { - _autoCompletionView.cellLayoutMarginsFollowReadableWidth = NO; - } -#endif - - CGRect rect = CGRectZero; - rect.size = CGSizeMake(CGRectGetWidth(self.view.frame), 0.5); - - _autoCompletionHairline = [[UIView alloc] initWithFrame:rect]; - _autoCompletionHairline.autoresizingMask = UIViewAutoresizingFlexibleWidth; - _autoCompletionHairline.backgroundColor = _autoCompletionView.separatorColor; - [_autoCompletionView addSubview:_autoCompletionHairline]; - } - return _autoCompletionView; -} - -- (SLKTextInputbar *)textInputbar -{ - if (!_textInputbar) { - _textInputbar = [[SLKTextInputbar alloc] initWithTextViewClass:self.textViewClass]; - _textInputbar.translatesAutoresizingMaskIntoConstraints = NO; - _textInputbar.controller = self; - - [_textInputbar.leftButton addTarget:self action:@selector(didPressLeftButton:) forControlEvents:UIControlEventTouchUpInside]; - [_textInputbar.rightButton addTarget:self action:@selector(didPressRightButton:) forControlEvents:UIControlEventTouchUpInside]; - [_textInputbar.editorLeftButton addTarget:self action:@selector(didCancelTextEditing:) forControlEvents:UIControlEventTouchUpInside]; - [_textInputbar.editorRightButton addTarget:self action:@selector(didCommitTextEditing:) forControlEvents:UIControlEventTouchUpInside]; - - _textInputbar.textView.delegate = self; - - _verticalPanGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(slk_didPanTextInputBar:)]; - _verticalPanGesture.delegate = self; - - [_textInputbar addGestureRecognizer:self.verticalPanGesture]; - } - return _textInputbar; -} - -- (UIView <SLKTypingIndicatorProtocol> *)typingIndicatorProxyView -{ - if (!_typingIndicatorProxyView) { - Class class = self.typingIndicatorViewClass ? : [SLKTypingIndicatorView class]; - - _typingIndicatorProxyView = [[class alloc] init]; - _typingIndicatorProxyView.translatesAutoresizingMaskIntoConstraints = NO; - _typingIndicatorProxyView.hidden = YES; - - [_typingIndicatorProxyView addObserver:self forKeyPath:@"visible" options:NSKeyValueObservingOptionNew context:nil]; - } - return _typingIndicatorProxyView; -} - -- (SLKTypingIndicatorView *)typingIndicatorView -{ - if ([_typingIndicatorProxyView isKindOfClass:[SLKTypingIndicatorView class]]) { - return (SLKTypingIndicatorView *)self.typingIndicatorProxyView; - } - return nil; -} - -- (BOOL)isPresentedInPopover -{ - return _presentedInPopover && SLK_IS_IPAD; -} - -- (SLKTextView *)textView -{ - return self.textInputbar.textView; -} - -- (UIButton *)leftButton -{ - return self.textInputbar.leftButton; -} - -- (UIButton *)rightButton -{ - return self.textInputbar.rightButton; -} - -- (UIModalPresentationStyle)modalPresentationStyle -{ - if (self.navigationController) { - return self.navigationController.modalPresentationStyle; - } - return [super modalPresentationStyle]; -} - -- (CGFloat)slk_appropriateKeyboardHeightFromNotification:(NSNotification *)notification -{ - // Let's first detect keyboard special states such as external keyboard, undocked or split layouts. - [self slk_detectKeyboardStatesInNotification:notification]; - - if ([self ignoreTextInputbarAdjustment]) { - return [self slk_appropriateBottomMargin]; - } - - CGRect keyboardRect = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; - - return [self slk_appropriateKeyboardHeightFromRect:keyboardRect]; -} - -- (CGFloat)slk_appropriateKeyboardHeightFromRect:(CGRect)rect -{ - CGRect keyboardRect = [self.view convertRect:rect fromView:nil]; - - CGFloat viewHeight = CGRectGetHeight(self.view.bounds); - CGFloat keyboardMinY = CGRectGetMinY(keyboardRect); - - CGFloat keyboardHeight = MAX(0.0, viewHeight - keyboardMinY); - CGFloat bottomMargin = [self slk_appropriateBottomMargin]; - - // When the keyboard height is zero, we can assume there is no keyboard visible - // In that case, let's see if there are any other views outside of the view hiearchy - // requiring to adjust the text input bottom margin - if (keyboardHeight < bottomMargin) { - keyboardHeight = bottomMargin; - } - - return keyboardHeight; -} - -- (CGFloat)slk_appropriateBottomMargin -{ - // A bottom margin is required only if the view is extended out of it bounds - if ((self.edgesForExtendedLayout & UIRectEdgeBottom) > 0) { - if (self.tabBarController) { - return CGRectGetHeight(self.tabBarController.tabBar.frame); - } - } - - return 0.0; -} - -- (CGFloat)slk_appropriateScrollViewHeight -{ - CGFloat scrollViewHeight = CGRectGetHeight(self.view.bounds); - - scrollViewHeight -= self.keyboardHC.constant; - scrollViewHeight -= self.textInputbarHC.constant; - scrollViewHeight -= self.autoCompletionViewHC.constant; - scrollViewHeight -= self.typingIndicatorViewHC.constant; - - if (scrollViewHeight < 0) return 0; - else return scrollViewHeight; -} - -- (CGFloat)slk_topBarsHeight -{ - // No need to adjust if the edge isn't available - if ((self.edgesForExtendedLayout & UIRectEdgeTop) == 0) { - return 0.0; - } - - CGFloat topBarsHeight = CGRectGetHeight(self.navigationController.navigationBar.frame); - - if ((SLK_IS_IPHONE && SLK_IS_LANDSCAPE && SLK_IS_IOS8_AND_HIGHER) || - (SLK_IS_IPAD && self.modalPresentationStyle == UIModalPresentationFormSheet) || - self.isPresentedInPopover) { - return topBarsHeight; - } - - topBarsHeight += CGRectGetHeight([UIApplication sharedApplication].statusBarFrame); - - return topBarsHeight; -} - -- (NSString *)slk_appropriateKeyboardNotificationName:(NSNotification *)notification -{ - NSString *name = notification.name; - - if ([name isEqualToString:UIKeyboardWillShowNotification]) { - return SLKKeyboardWillShowNotification; - } - if ([name isEqualToString:UIKeyboardWillHideNotification]) { - return SLKKeyboardWillHideNotification; - } - if ([name isEqualToString:UIKeyboardDidShowNotification]) { - return SLKKeyboardDidShowNotification; - } - if ([name isEqualToString:UIKeyboardDidHideNotification]) { - return SLKKeyboardDidHideNotification; - } - return nil; -} - -- (SLKKeyboardStatus)slk_keyboardStatusForNotification:(NSNotification *)notification -{ - NSString *name = notification.name; - - if ([name isEqualToString:UIKeyboardWillShowNotification]) { - return SLKKeyboardStatusWillShow; - } - if ([name isEqualToString:UIKeyboardDidShowNotification]) { - return SLKKeyboardStatusDidShow; - } - if ([name isEqualToString:UIKeyboardWillHideNotification]) { - return SLKKeyboardStatusWillHide; - } - if ([name isEqualToString:UIKeyboardDidHideNotification]) { - return SLKKeyboardStatusDidHide; - } - return -1; -} - -- (BOOL)slk_isIllogicalKeyboardStatus:(SLKKeyboardStatus)newStatus -{ - if ((self.keyboardStatus == SLKKeyboardStatusDidHide && newStatus == SLKKeyboardStatusWillShow) || - (self.keyboardStatus == SLKKeyboardStatusWillShow && newStatus == SLKKeyboardStatusDidShow) || - (self.keyboardStatus == SLKKeyboardStatusDidShow && newStatus == SLKKeyboardStatusWillHide) || - (self.keyboardStatus == SLKKeyboardStatusWillHide && newStatus == SLKKeyboardStatusDidHide)) { - return NO; - } - return YES; -} - - -#pragma mark - Setters - -- (void)setEdgesForExtendedLayout:(UIRectEdge)rectEdge -{ - if (self.edgesForExtendedLayout == rectEdge) { - return; - } - - [super setEdgesForExtendedLayout:rectEdge]; - - [self slk_updateViewConstraints]; -} - -- (void)setScrollViewProxy:(UIScrollView *)scrollView -{ - if ([_scrollViewProxy isEqual:scrollView]) { - return; - } - - _singleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(slk_didTapScrollView:)]; - _singleTapGesture.delegate = self; - [_singleTapGesture requireGestureRecognizerToFail:scrollView.panGestureRecognizer]; - - [scrollView addGestureRecognizer:self.singleTapGesture]; - - [scrollView.panGestureRecognizer addTarget:self action:@selector(slk_didPanTextInputBar:)]; - - _scrollViewProxy = scrollView; -} - -- (void)setAutoCompleting:(BOOL)autoCompleting -{ - if (_autoCompleting == autoCompleting) { - return; - } - - _autoCompleting = autoCompleting; - - self.scrollViewProxy.scrollEnabled = !autoCompleting; -} - -- (void)setInverted:(BOOL)inverted -{ - if (_inverted == inverted) { - return; - } - - _inverted = inverted; - - self.scrollViewProxy.transform = inverted ? CGAffineTransformMake(1, 0, 0, -1, 0, 0) : CGAffineTransformIdentity; -} - -- (BOOL)slk_updateKeyboardStatus:(SLKKeyboardStatus)status -{ - // Skips if trying to update the same status - if (_keyboardStatus == status) { - return NO; - } - - // Skips illogical conditions - if ([self slk_isIllogicalKeyboardStatus:status]) { - return NO; - } - - _keyboardStatus = status; - - [self didChangeKeyboardStatus:status]; - - return YES; -} - - -#pragma mark - Public & Subclassable Methods - -- (void)presentKeyboard:(BOOL)animated -{ - // Skips if already first responder - if ([self.textView isFirstResponder]) { - return; - } - - if (!animated) { - [UIView performWithoutAnimation:^{ - [self.textView becomeFirstResponder]; - }]; - } - else { - [self.textView becomeFirstResponder]; - } -} - -- (void)dismissKeyboard:(BOOL)animated -{ - // Dismisses the keyboard from any first responder in the window. - if (![self.textView isFirstResponder] && self.keyboardHC.constant > 0) { - [self.view.window endEditing:NO]; - } - - if (!animated) { - [UIView performWithoutAnimation:^{ - [self.textView resignFirstResponder]; - }]; - } - else { - [self.textView resignFirstResponder]; - } -} - -- (BOOL)forceTextInputbarAdjustmentForResponder:(UIResponder *)responder -{ - return NO; -} - -- (BOOL)ignoreTextInputbarAdjustment -{ - if (self.isExternalKeyboardDetected || self.isKeyboardUndocked) { - return YES; - } - - return NO; -} - -- (void)didChangeKeyboardStatus:(SLKKeyboardStatus)status -{ - // No implementation here. Meant to be overriden in subclass. -} - -- (void)textWillUpdate -{ - // No implementation here. Meant to be overriden in subclass. -} - -- (void)textDidUpdate:(BOOL)animated -{ - if (self.textInputbarHidden) { - return; - } - - CGFloat inputbarHeight = self.textInputbar.appropriateHeight; - - self.textInputbar.rightButton.enabled = [self canPressRightButton]; - self.textInputbar.editorRightButton.enabled = [self canPressRightButton]; - - if (inputbarHeight != self.textInputbarHC.constant) - { - self.textInputbarHC.constant = inputbarHeight; - self.scrollViewHC.constant = [self slk_appropriateScrollViewHeight]; - - if (animated) { - - BOOL bounces = self.bounces && [self.textView isFirstResponder]; - - [self.view slk_animateLayoutIfNeededWithBounce:bounces - options:UIViewAnimationOptionCurveEaseInOut|UIViewAnimationOptionLayoutSubviews|UIViewAnimationOptionBeginFromCurrentState - animations:^{ - if (self.textInputbar.isEditing) { - [self.textView slk_scrollToCaretPositonAnimated:NO]; - } - }]; - } - else { - [self.view layoutIfNeeded]; - } - } - - // Toggles auto-correction if requiered - [self slk_enableTypingSuggestionIfNeeded]; -} - -- (void)textSelectionDidChange -{ - // The text view must be first responder - if (![self.textView isFirstResponder]) { - return; - } - - // Skips there is a real text selection - if (self.textView.isTrackpadEnabled) { - return; - } - - if (self.textView.selectedRange.length > 0) { - if (self.isAutoCompleting) { - [self cancelAutoCompletion]; - } - return; - } - - // Process the text at every caret movement - [self slk_processTextForAutoCompletion]; -} - -- (BOOL)canPressRightButton -{ - NSString *text = [self.textView.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - - if (text.length > 0 && ![self.textInputbar limitExceeded]) { - return YES; - } - - return NO; -} - -- (void)didPressLeftButton:(id)sender -{ - // No implementation here. Meant to be overriden in subclass. -} - -- (void)didPressRightButton:(id)sender -{ - if (self.shouldClearTextAtRightButtonPress) { - // Clears the text and the undo manager - [self.textView slk_clearText:YES]; - } - - // Clears cache - [self clearCachedText]; -} - -- (void)editText:(NSString *)text -{ - if (![self.textInputbar canEditText:text]) { - return; - } - - // Caches the current text, in case the user cancels the edition - [self slk_cacheTextToDisk:self.textView.text]; - - [self.textInputbar beginTextEditing]; - - // Setting the text after calling -beginTextEditing is safer - [self.textView setText:text]; - - [self.textView slk_scrollToCaretPositonAnimated:YES]; - - // Brings up the keyboard if needed - [self presentKeyboard:YES]; -} - -- (void)didCommitTextEditing:(id)sender -{ - if (!self.textInputbar.isEditing) { - return; - } - - [self.textInputbar endTextEdition]; - - // Clears the text and but not the undo manager - [self.textView slk_clearText:NO]; -} - -- (void)didCancelTextEditing:(id)sender -{ - if (!self.textInputbar.isEditing) { - return; - } - - [self.textInputbar endTextEdition]; - - // Clears the text and but not the undo manager - [self.textView slk_clearText:NO]; - - // Restores any previous cached text before entering in editing mode - [self slk_reloadTextView]; -} - -- (BOOL)canShowTypingIndicator -{ - // Don't show if the text is being edited or auto-completed. - if (self.textInputbar.isEditing || self.isAutoCompleting) { - return NO; - } - - // Don't show if the content offset is not at top (when inverted) or at bottom (when not inverted) - if ((self.isInverted && ![self.scrollViewProxy slk_isAtTop]) || (!self.isInverted && ![self.scrollViewProxy slk_isAtBottom])) { - return NO; - } - - return YES; -} - -- (CGFloat)heightForAutoCompletionView -{ - return 0.0; -} - -- (CGFloat)maximumHeightForAutoCompletionView -{ - CGFloat maxiumumHeight = SLKAutoCompletionViewDefaultHeight; - - if (self.isAutoCompleting) { - CGFloat scrollViewHeight = self.scrollViewHC.constant; - scrollViewHeight -= [self slk_topBarsHeight]; - - if (scrollViewHeight < maxiumumHeight) { - maxiumumHeight = scrollViewHeight; - } - } - - return maxiumumHeight; -} - -- (void)didPasteMediaContent:(NSDictionary *)userInfo -{ - // No implementation here. Meant to be overriden in subclass. -} - -- (void)willRequestUndo -{ - NSString *title = NSLocalizedString(@"Undo Typing", nil); - NSString *acceptTitle = NSLocalizedString(@"Undo", nil); - NSString *cancelTitle = NSLocalizedString(@"Cancel", nil); - -#ifdef __IPHONE_8_0 - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleAlert]; - - [alertController addAction:[UIAlertAction actionWithTitle:acceptTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - // Clears the text but doesn't clear the undo manager - if (self.shakeToClearEnabled) { - [self.textView slk_clearText:NO]; - } - }]]; - - [alertController addAction:[UIAlertAction actionWithTitle:cancelTitle style:UIAlertActionStyleCancel handler:NULL]]; - - [self presentViewController:alertController animated:YES completion:nil]; -#else - UIAlertView *alert = [UIAlertView new]; - [alert setTitle:title]; - [alert addButtonWithTitle:acceptTitle]; - [alert addButtonWithTitle:cancelTitle]; - [alert setCancelButtonIndex:1]; - [alert setTag:kSLKAlertViewClearTextTag]; - [alert setDelegate:self]; - [alert show]; -#endif -} - -- (void)setTextInputbarHidden:(BOOL)hidden -{ - [self setTextInputbarHidden:hidden animated:NO]; -} - -- (void)setTextInputbarHidden:(BOOL)hidden animated:(BOOL)animated -{ - if (self.isTextInputbarHidden == hidden) { - return; - } - - _textInputbarHidden = hidden; - - __weak typeof(self) weakSelf = self; - - void (^animations)() = ^void(){ - - weakSelf.textInputbarHC.constant = hidden ? 0 : weakSelf.textInputbar.appropriateHeight; - - [weakSelf.view layoutIfNeeded]; - }; - - void (^completion)(BOOL finished) = ^void(BOOL finished){ - if (hidden) { - [self dismissKeyboard:YES]; - } - }; - - if (animated) { - [UIView animateWithDuration:0.25 animations:animations completion:completion]; - } - else { - animations(); - completion(NO); - } -} - - -#pragma mark - Private Methods - -- (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture -{ - // Textinput dragging isn't supported when - if (!self.view.window || !self.keyboardPanningEnabled || - [self ignoreTextInputbarAdjustment] || self.isPresentedInPopover) { - return; - } - - dispatch_async(dispatch_get_main_queue(), ^{ - [self slk_handlePanGestureRecognizer:gesture]; - }); -} - -- (void)slk_handlePanGestureRecognizer:(UIPanGestureRecognizer *)gesture -{ - // Local variables - static CGPoint startPoint; - static CGRect originalFrame; - static BOOL dragging = NO; - static BOOL presenting = NO; - - __block UIView *keyboardView = [self.textInputbar.inputAccessoryView keyboardViewProxy]; - - // When no keyboard view has been detecting, let's skip any handling. - if (!keyboardView) { - return; - } - - // Dynamic variables - CGPoint gestureLocation = [gesture locationInView:self.view]; - CGPoint gestureVelocity = [gesture velocityInView:self.view]; - - CGFloat keyboardMaxY = CGRectGetHeight(SLKKeyWindowBounds()); - CGFloat keyboardMinY = keyboardMaxY - CGRectGetHeight(keyboardView.frame); - - - // Skips this if it's not the expected textView. - // Checking the keyboard height constant helps to disable the view constraints update on iPad when the keyboard is undocked. - // Checking the keyboard status allows to keep the inputAccessoryView valid when still reacing the bottom of the screen. - if (![self.textView isFirstResponder] || (self.keyboardHC.constant == 0 && self.keyboardStatus == SLKKeyboardStatusDidHide)) { -#if SLKBottomPanningEnabled - if ([gesture.view isEqual:self.scrollViewProxy]) { - if (gestureVelocity.y > 0) { - return; - } - else if ((self.isInverted && ![self.scrollViewProxy slk_isAtTop]) || (!self.isInverted && ![self.scrollViewProxy slk_isAtBottom])) { - return; - } - } - - presenting = YES; -#else - if ([gesture.view isEqual:self.textInputbar] && gestureVelocity.y < 0) { - [self presentKeyboard:YES]; - } - return; -#endif - } - - switch (gesture.state) { - case UIGestureRecognizerStateBegan: { - - startPoint = CGPointZero; - dragging = NO; - - if (presenting) { - // Let's first present the keyboard without animation - [self presentKeyboard:NO]; - - // So we can capture the keyboard's view - keyboardView = [self.textInputbar.inputAccessoryView keyboardViewProxy]; - - originalFrame = keyboardView.frame; - originalFrame.origin.y = CGRectGetMaxY(self.view.frame); - - // And move the keyboard to the bottom edge - // TODO: Fix an occasional layout glitch when the keyboard appears for the first time. - keyboardView.frame = originalFrame; - } - - break; - } - case UIGestureRecognizerStateChanged: { - - if (CGRectContainsPoint(self.textInputbar.frame, gestureLocation) || dragging || presenting){ - - if (CGPointEqualToPoint(startPoint, CGPointZero)) { - startPoint = gestureLocation; - dragging = YES; - - if (!presenting) { - originalFrame = keyboardView.frame; - } - } - - self.movingKeyboard = YES; - - CGPoint transition = CGPointMake(gestureLocation.x - startPoint.x, gestureLocation.y - startPoint.y); - - CGRect keyboardFrame = originalFrame; - - if (presenting) { - keyboardFrame.origin.y += transition.y; - } - else { - keyboardFrame.origin.y += MAX(transition.y, 0.0); - } - - // Makes sure they keyboard is always anchored to the bottom - if (CGRectGetMinY(keyboardFrame) < keyboardMinY) { - keyboardFrame.origin.y = keyboardMinY; - } - - keyboardView.frame = keyboardFrame; - - - self.keyboardHC.constant = [self slk_appropriateKeyboardHeightFromRect:keyboardFrame]; - self.scrollViewHC.constant = [self slk_appropriateScrollViewHeight]; - - // layoutIfNeeded must be called before any further scrollView internal adjustments (content offset and size) - [self.view layoutIfNeeded]; - - // Overrides the scrollView's contentOffset to allow following the same position when dragging the keyboard - CGPoint offset = _scrollViewOffsetBeforeDragging; - - if (self.isInverted) { - if (!self.scrollViewProxy.isDecelerating && self.scrollViewProxy.isTracking) { - self.scrollViewProxy.contentOffset = _scrollViewOffsetBeforeDragging; - } - } - else { - CGFloat keyboardHeightDelta = _keyboardHeightBeforeDragging-self.keyboardHC.constant; - offset.y -= keyboardHeightDelta; - - self.scrollViewProxy.contentOffset = offset; - } - } - - break; - } - case UIGestureRecognizerStatePossible: - case UIGestureRecognizerStateCancelled: - case UIGestureRecognizerStateEnded: - case UIGestureRecognizerStateFailed: { - - if (!dragging) { - break; - } - - CGPoint transition = CGPointMake(0.0, fabs(gestureLocation.y - startPoint.y)); - - CGRect keyboardFrame = originalFrame; - - if (presenting) { - keyboardFrame.origin.y = keyboardMinY; - } - - // The velocity can be changed to hide or show the keyboard based on the gesture - CGFloat minVelocity = 20.0; - CGFloat minDistance = CGRectGetHeight(keyboardFrame)/2.0; - - BOOL hide = (gestureVelocity.y > minVelocity) || (presenting && transition.y < minDistance) || (!presenting && transition.y > minDistance); - - if (hide) keyboardFrame.origin.y = keyboardMaxY; - - self.keyboardHC.constant = [self slk_appropriateKeyboardHeightFromRect:keyboardFrame]; - self.scrollViewHC.constant = [self slk_appropriateScrollViewHeight]; - - [UIView animateWithDuration:0.25 - delay:0.0 - options:UIViewAnimationOptionCurveEaseInOut|UIViewAnimationOptionBeginFromCurrentState - animations:^{ - [self.view layoutIfNeeded]; - keyboardView.frame = keyboardFrame; - } - completion:^(BOOL finished) { - if (hide) { - [self dismissKeyboard:NO]; - } - - // Tear down - startPoint = CGPointZero; - originalFrame = CGRectZero; - dragging = NO; - presenting = NO; - - self.movingKeyboard = NO; - }]; - - break; - } - - default: - break; - } -} - -- (void)slk_didTapScrollView:(UIGestureRecognizer *)gesture -{ - if (!self.isPresentedInPopover && ![self ignoreTextInputbarAdjustment]) { - [self dismissKeyboard:YES]; - } -} - -- (void)slk_didPanTextView:(UIGestureRecognizer *)gesture -{ - [self presentKeyboard:YES]; -} - -- (void)slk_performRightAction -{ - NSArray *actions = [self.rightButton actionsForTarget:self forControlEvent:UIControlEventTouchUpInside]; - - if (actions.count > 0 && [self canPressRightButton]) { - [self.rightButton sendActionsForControlEvents:UIControlEventTouchUpInside]; - } -} - -- (void)slk_postKeyboarStatusNotification:(NSNotification *)notification -{ - if ([self ignoreTextInputbarAdjustment] || self.isTransitioning) { - return; - } - - NSMutableDictionary *userInfo = [notification.userInfo mutableCopy]; - - CGRect beginFrame = [notification.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue]; - CGRect endFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; - - // Fixes iOS7 oddness with inverted values on landscape orientation - if (!SLK_IS_IOS8_AND_HIGHER && SLK_IS_LANDSCAPE) { - beginFrame = SLKRectInvert(beginFrame); - endFrame = SLKRectInvert(endFrame); - } - - CGFloat keyboardHeight = CGRectGetHeight(endFrame); - - beginFrame.size.height = keyboardHeight; - endFrame.size.height = keyboardHeight; - - [userInfo setObject:[NSValue valueWithCGRect:beginFrame] forKey:UIKeyboardFrameBeginUserInfoKey]; - [userInfo setObject:[NSValue valueWithCGRect:endFrame] forKey:UIKeyboardFrameEndUserInfoKey]; - - NSString *name = [self slk_appropriateKeyboardNotificationName:notification]; - [[NSNotificationCenter defaultCenter] postNotificationName:name object:self.textView userInfo:userInfo]; -} - -- (void)slk_enableTypingSuggestionIfNeeded -{ - if (![self.textView isFirstResponder]) { - return; - } - - BOOL enable = !self.isAutoCompleting; - - // Toggling autocorrect on Japanese keyboards breaks autocompletion by replacing the autocompletion prefix by an empty string. - // So for now, let's not disable autocorrection for Japanese. - if ([self.textView.textInputMode.primaryLanguage isEqualToString:@"ja-JP"]) { - return; - } - - // During text autocompletion, the iOS 8 QuickType bar is hidden and auto-correction and spell checking are disabled. - [self.textView setTypingSuggestionEnabled:enable]; -} - -- (void)slk_dismissTextInputbarIfNeeded -{ - if (self.keyboardHC.constant == 0) { - return; - } - - self.keyboardHC.constant = 0.0; - self.scrollViewHC.constant = [self slk_appropriateScrollViewHeight]; - - [self slk_hideAutoCompletionViewIfNeeded]; - - [self.view layoutIfNeeded]; -} - -- (void)slk_detectKeyboardStatesInNotification:(NSNotification *)notification -{ - // Tear down - _externalKeyboardDetected = NO; - _keyboardUndocked = NO; - - if (self.isMovingKeyboard) { - return; - } - - // Based on http://stackoverflow.com/a/5760910/287403 - // We can determine if the external keyboard is showing by adding the origin.y of the target finish rect (end when showing, begin when hiding) to the inputAccessoryHeight. - // If it's greater(or equal) the window height, it's an external keyboard. - CGRect beginRect = [notification.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue]; - CGRect endRect = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; - - // Grab the base view for conversions as we don't want window coordinates in < iOS 8 - // iOS 8 fixes the whole coordinate system issue for us, but iOS 7 doesn't rotate the app window coordinate space. - UIView *baseView = self.view.window.rootViewController.view; - - CGRect screenBounds = [UIScreen mainScreen].bounds; - - // Convert the main screen bounds into the correct coordinate space but ignore the origin. - CGRect viewBounds = [self.view convertRect:SLKKeyWindowBounds() fromView:nil]; - viewBounds = CGRectMake(0, 0, viewBounds.size.width, viewBounds.size.height); - - // We want these rects in the correct coordinate space as well. - CGRect convertBegin = [baseView convertRect:beginRect fromView:nil]; - CGRect convertEnd = [baseView convertRect:endRect fromView:nil]; - - if ([notification.name isEqualToString:UIKeyboardWillShowNotification]) { - if (convertEnd.origin.y >= viewBounds.size.height) { - _externalKeyboardDetected = YES; - } - } - else if ([notification.name isEqualToString:UIKeyboardWillHideNotification]) { - // The additional logic check here (== to width) accounts for a glitch (iOS 8 only?) where the window has rotated it's coordinates - // but the beginRect doesn't yet reflect that. It should never cause a false positive. - if (convertBegin.origin.y >= viewBounds.size.height || - convertBegin.origin.y == viewBounds.size.width) { - _externalKeyboardDetected = YES; - } - } - - if (SLK_IS_IPAD && CGRectGetMaxY(convertEnd) < CGRectGetMaxY(screenBounds)) { - - // The keyboard is undocked or split (iPad Only) - _keyboardUndocked = YES; - - // An external keyboard cannot be detected anymore - _externalKeyboardDetected = NO; - } -} - -- (void)slk_adjustContentConfigurationIfNeeded -{ - UIEdgeInsets contentInset = self.scrollViewProxy.contentInset; - - // When inverted, we need to substract the top bars height (generally status bar + navigation bar's) to align the top of the - // scrollView correctly to its top edge. - if (self.inverted) { - contentInset.bottom = [self slk_topBarsHeight]; - contentInset.top = contentInset.bottom > 0.0 ? 0.0 : contentInset.top; - } - else { - contentInset.bottom = 0.0; - } - - self.scrollViewProxy.contentInset = contentInset; - self.scrollViewProxy.scrollIndicatorInsets = contentInset; -} - -- (void)slk_prepareForInterfaceTransitionWithDuration:(NSTimeInterval)duration -{ - self.transitioning = YES; - - [self.view layoutIfNeeded]; - - if ([self.textView isFirstResponder]) { - [self.textView slk_scrollToCaretPositonAnimated:NO]; - } - else { - [self.textView slk_scrollToBottomAnimated:NO]; - } - - // Disables the flag after the rotation animation is finished - // Hacky but works. - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - self.transitioning = NO; - }); -} - - -#pragma mark - Keyboard Events - -- (void)didPressReturnKey:(id)sender -{ - if (self.textInputbar.isEditing) { - [self didCommitTextEditing:sender]; - } - else { - [self slk_performRightAction]; - } -} - -- (void)didPressEscapeKey:(id)sender -{ - if (self.isAutoCompleting) { - [self cancelAutoCompletion]; - } - else if (self.textInputbar.isEditing) { - [self didCancelTextEditing:sender]; - } - - if ([self ignoreTextInputbarAdjustment] || ([self.textView isFirstResponder] && self.keyboardHC.constant == 0)) { - return; - } - - [self dismissKeyboard:YES]; -} - -- (void)didPressArrowKey:(id)sender -{ - [self.textView didPressAnyArrowKey:sender]; -} - - -#pragma mark - Notification Events - -- (void)slk_willShowOrHideKeyboard:(NSNotification *)notification -{ - // Skips if the view isn't visible. - if (!self.view.window) { - return; - } - - // Skips if it is presented inside of a popover. - if (self.isPresentedInPopover) { - return; - } - - // Skips if textview did refresh only. - if (self.textView.didNotResignFirstResponder) { - return; - } - - SLKKeyboardStatus status = [self slk_keyboardStatusForNotification:notification]; - - // Skips if it's the current status - if (self.keyboardStatus == status) { - return; - } - - // Updates and notifies about the keyboard status update - if ([self slk_updateKeyboardStatus:status]) { - // Posts custom keyboard notification, if logical conditions apply - [self slk_postKeyboarStatusNotification:notification]; - } - - // Skips this it's not the expected textView and shouldn't force adjustment of the text input bar. - // This will also dismiss the text input bar if it's visible, and exit auto-completion mode if enabled. - if (![self.textView isFirstResponder]) { - // Detect the current first responder. If there is no first responder, we should just ignore these notifications. - UIResponder *currentResponder = [UIResponder slk_currentFirstResponder]; - - if (!currentResponder) { - return; - } - else if (![self forceTextInputbarAdjustmentForResponder:currentResponder]) { - return [self slk_dismissTextInputbarIfNeeded]; - } - } - - // Programatically stops scrolling before updating the view constraints (to avoid scrolling glitch). - if (status == SLKKeyboardStatusWillShow) { - [self.scrollViewProxy slk_stopScrolling]; - } - - // Hides the auto-completion view if the keyboard is being dismissed. - if (![self.textView isFirstResponder] || status == SLKKeyboardStatusWillHide) { - [self slk_hideAutoCompletionViewIfNeeded]; - } - - // Updates the height constraints' constants - self.keyboardHC.constant = [self slk_appropriateKeyboardHeightFromNotification:notification]; - self.scrollViewHC.constant = [self slk_appropriateScrollViewHeight]; - - - NSInteger curve = [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]; - NSTimeInterval duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; - - CGRect beginFrame = [notification.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue]; - CGRect endFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; - - void (^animations)() = ^void() { - // Scrolls to bottom only if the keyboard is about to show. - if (self.shouldScrollToBottomAfterKeyboardShows && self.keyboardStatus == SLKKeyboardStatusWillShow) { - if (self.isInverted) { - [self.scrollViewProxy slk_scrollToTopAnimated:YES]; - } - else { - [self.scrollViewProxy slk_scrollToBottomAnimated:YES]; - } - } - }; - - // Begin and end frames are the same when the keyboard is shown during navigation controller's push animation. - // The animation happens in window coordinates (slides from right to left) but doesn't in the view controller's view coordinates. - if (!CGRectEqualToRect(beginFrame, endFrame)) - { - // Only for this animation, we set bo to bounce since we want to give the impression that the text input is glued to the keyboard. - [self.view slk_animateLayoutIfNeededWithDuration:duration - bounce:NO - options:(curve<<16)|UIViewAnimationOptionLayoutSubviews|UIViewAnimationOptionBeginFromCurrentState - animations:animations - completion:NULL]; - } - else { - animations(); - } -} - -- (void)slk_didShowOrHideKeyboard:(NSNotification *)notification -{ - // Skips if the view isn't visible - if (!self.view.window) { - return; - } - - // Skips if it is presented inside of a popover - if (self.isPresentedInPopover) { - return; - } - - // Skips if textview did refresh only - if (self.textView.didNotResignFirstResponder) { - return; - } - - SLKKeyboardStatus status = [self slk_keyboardStatusForNotification:notification]; - - // Skips if it's the current status - if (self.keyboardStatus == status) { - return; - } - - // Updates and notifies about the keyboard status update - if ([self slk_updateKeyboardStatus:status]) { - // Posts custom keyboard notification, if logical conditions apply - [self slk_postKeyboarStatusNotification:notification]; - } - - // After showing keyboard, check if the current cursor position could diplay autocompletion - if ([self.textView isFirstResponder] && status == SLKKeyboardStatusDidShow && !self.isAutoCompleting) { - - // Wait till the end of the current run loop - dispatch_async(dispatch_get_main_queue(), ^{ - [self slk_processTextForAutoCompletion]; - }); - } - - // Very important to invalidate this flag after the keyboard is dismissed or presented, to start with a clean state next time. - self.movingKeyboard = NO; -} - -- (void)slk_didPostSLKKeyboardNotification:(NSNotification *)notification -{ - if (![notification.object isEqual:self.textView]) { - return; - } - - // Used for debug only - NSLog(@"%@ %s: %@", NSStringFromClass([self class]), __FUNCTION__, notification); -} - -- (void)slk_willChangeTextViewText:(NSNotification *)notification -{ - // Skips this it's not the expected textView. - if (![notification.object isEqual:self.textView] || !self.textView.window) { - return; - } - - [self textWillUpdate]; -} - -- (void)slk_didChangeTextViewText:(NSNotification *)notification -{ - // Skips this it's not the expected textView. - if (![notification.object isEqual:self.textView] || !self.textView.window) { - return; - } - - // Animated only if the view already appeared. - [self textDidUpdate:self.isViewVisible]; - - // Process the text at every change, when the view is visible - if (self.isViewVisible) { - [self slk_processTextForAutoCompletion]; - } -} - -- (void)slk_didChangeTextViewContentSize:(NSNotification *)notification -{ - // Skips this it's not the expected textView. - if (![notification.object isEqual:self.textView] || !self.textView.window) { - return; - } - - // Animated only if the view already appeared. - [self textDidUpdate:self.isViewVisible]; -} - -- (void)slk_didChangeTextViewSelectedRange:(NSNotification *)notification -{ - // Skips this it's not the expected textView. - if (![notification.object isEqual:self.textView] || !self.textView.window) { - return; - } - - [self textSelectionDidChange]; -} - -- (void)slk_didChangeTextViewPasteboard:(NSNotification *)notification -{ - // Skips this if it's not the expected textView. - if (![self.textView isFirstResponder]) { - return; - } - - // Notifies only if the pasted item is nested in a dictionary. - if ([notification.userInfo isKindOfClass:[NSDictionary class]]) { - [self didPasteMediaContent:notification.userInfo]; - } -} - -- (void)slk_didShakeTextView:(NSNotification *)notification -{ - // Skips this if it's not the expected textView. - if (![self.textView isFirstResponder]) { - return; - } - - // Notifies of the shake gesture if undo mode is on and the text view is not empty - if (self.shakeToClearEnabled && self.textView.text.length > 0) { - [self willRequestUndo]; - } -} - -- (void)slk_willShowOrHideTypeIndicatorView:(UIView <SLKTypingIndicatorProtocol> *)typingIndicatorView -{ - // Skips if the typing indicator should not show. Ignores the checking if it's trying to hide. - if (![self canShowTypingIndicator] && typingIndicatorView.isVisible) { - return; - } - - CGFloat systemLayoutSizeHeight = [typingIndicatorView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height; - CGFloat height = typingIndicatorView.isVisible ? systemLayoutSizeHeight : 0.0; - - self.typingIndicatorViewHC.constant = height; - self.scrollViewHC.constant -= height; - - if (typingIndicatorView.isVisible) { - typingIndicatorView.hidden = NO; - } - - [self.view slk_animateLayoutIfNeededWithBounce:self.bounces - options:UIViewAnimationOptionCurveEaseInOut - animations:NULL - completion:^(BOOL finished) { - if (!typingIndicatorView.isVisible) { - typingIndicatorView.hidden = YES; - } - }]; -} - -- (void)slk_willTerminateApplication:(NSNotification *)notification -{ - // Caches the text before it's too late! - if (self.isViewVisible) { - [self slk_cacheTextView]; - } -} - - -#pragma mark - KVO Events - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context -{ - if ([object conformsToProtocol:@protocol(SLKTypingIndicatorProtocol)] && [keyPath isEqualToString:@"visible"]) { - [self slk_willShowOrHideTypeIndicatorView:object]; - } - else { - [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; - } -} - - -#pragma mark - Auto-Completion Text Processing - -- (void)registerPrefixesForAutoCompletion:(NSArray *)prefixes -{ - NSMutableArray *array = [NSMutableArray arrayWithArray:self.registeredPrefixes]; - - for (NSString *prefix in prefixes) { - // Skips if the prefix is not a valid string - if (![prefix isKindOfClass:[NSString class]] || prefix.length == 0) { - continue; - } - - // Adds the prefix if not contained already - if (![array containsObject:prefix]) { - [array addObject:prefix]; - } - } - - if (_registeredPrefixes) { - _registeredPrefixes = nil; - } - - _registeredPrefixes = [[NSArray alloc] initWithArray:array]; -} - -- (void)didChangeAutoCompletionPrefix:(NSString *)prefix andWord:(NSString *)word -{ - // No implementation here. Meant to be overriden in subclass. -} - -- (BOOL)canShowAutoCompletion -{ - // Let's keep this around for a bit, for backwards compatibility. - return NO; -} - -- (void)showAutoCompletionView:(BOOL)show -{ - // Reloads the tableview before showing/hiding - if (show) { - [self.autoCompletionView reloadData]; - } - - self.autoCompleting = show; - - // Toggles auto-correction if requiered - [self slk_enableTypingSuggestionIfNeeded]; - - CGFloat viewHeight = show ? [self heightForAutoCompletionView] : 0.0; - - if (self.autoCompletionViewHC.constant == viewHeight) { - return; - } - - // If the auto-completion view height is bigger than the maximum height allows, it is reduce to that size. Default 140 pts. - CGFloat maximumHeight = [self maximumHeightForAutoCompletionView]; - - if (viewHeight > maximumHeight) { - viewHeight = maximumHeight; - } - - CGFloat contentViewHeight = self.scrollViewHC.constant + self.autoCompletionViewHC.constant; - - // On iPhone, the auto-completion view can't extend beyond the content view height - if (SLK_IS_IPHONE && viewHeight > contentViewHeight) { - viewHeight = contentViewHeight; - } - - self.autoCompletionViewHC.constant = viewHeight; - - [self.view slk_animateLayoutIfNeededWithBounce:self.bounces - options:UIViewAnimationOptionCurveEaseInOut|UIViewAnimationOptionLayoutSubviews|UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionAllowUserInteraction - animations:NULL]; -} - -- (void)acceptAutoCompletionWithString:(NSString *)string -{ - [self acceptAutoCompletionWithString:string keepPrefix:YES]; -} - -- (void)acceptAutoCompletionWithString:(NSString *)string keepPrefix:(BOOL)keepPrefix -{ - if (string.length == 0) { - return; - } - - SLKTextView *textView = self.textView; - - NSUInteger location = self.foundPrefixRange.location; - if (keepPrefix) { - location += self.foundPrefixRange.length; - } - - NSUInteger length = self.foundWord.length; - if (!keepPrefix) { - length += self.foundPrefixRange.length; - } - - NSRange range = NSMakeRange(location, length); - NSRange insertionRange = [textView slk_insertText:string inRange:range]; - - textView.selectedRange = NSMakeRange(insertionRange.location, 0); - - [self cancelAutoCompletion]; - - [textView slk_scrollToCaretPositonAnimated:NO]; -} - -- (void)cancelAutoCompletion -{ - [self slk_invalidateAutoCompletion]; - [self slk_hideAutoCompletionViewIfNeeded]; -} - -- (void)slk_processTextForAutoCompletion -{ - if (self.isTransitioning) { - return; - } - - // Avoids text processing for auto-completion if the registered prefix list is empty. - if (self.registeredPrefixes.count == 0) { - return; - } - - NSString *text = self.textView.text; - - // Skip, when there is no text to process - if (text.length == 0) { - return [self cancelAutoCompletion]; - } - - NSRange range; - NSString *word = [self.textView slk_wordAtCaretRange:&range]; - - [self slk_invalidateAutoCompletion]; - - if (word.length > 0) { - - for (NSString *prefix in self.registeredPrefixes) { - if ([word hasPrefix:prefix]) { - // Captures the detected symbol prefix - _foundPrefix = prefix; - - // Used later for replacing the detected range with a new string alias returned in -acceptAutoCompletionWithString: - _foundPrefixRange = NSMakeRange(range.location, prefix.length); - } - } - } - - [self slk_handleProcessedWord:word range:range]; -} - -- (void)slk_handleProcessedWord:(NSString *)word range:(NSRange)range -{ - // Cancel auto-completion if the cursor is placed before the prefix - if (self.textView.selectedRange.location <= self.foundPrefixRange.location) { - return [self cancelAutoCompletion]; - } - - if (self.foundPrefix.length > 0) { - if (range.length == 0 || range.length != word.length) { - return [self cancelAutoCompletion]; - } - - if (word.length > 0) { - // Removes the found prefix - _foundWord = [word substringFromIndex:self.foundPrefix.length]; - - // If the prefix is still contained in the word, cancels - if ([self.foundWord rangeOfString:self.foundPrefix].location != NSNotFound) { - return [self cancelAutoCompletion]; - } - } - else { - return [self cancelAutoCompletion]; - } - } - else { - return [self cancelAutoCompletion]; - } - - [self didChangeAutoCompletionPrefix:self.foundPrefix andWord:self.foundWord]; -} - -- (void)slk_invalidateAutoCompletion -{ - _foundPrefix = nil; - _foundWord = nil; - _foundPrefixRange = NSMakeRange(0, 0); - - [self.autoCompletionView setContentOffset:CGPointZero]; -} - -- (void)slk_hideAutoCompletionViewIfNeeded -{ - if (self.isAutoCompleting) { - [self showAutoCompletionView:NO]; - } -} - - -#pragma mark - Text Caching - -- (NSString *)keyForTextCaching -{ - // No implementation here. Meant to be overriden in subclass. - return nil; -} - -- (NSString *)slk_keyForPersistency -{ - NSString *key = [self keyForTextCaching]; - if (key == nil) { - return nil; - } - return [NSString stringWithFormat:@"%@.%@", SLKTextViewControllerDomain, key]; -} - -- (void)slk_reloadTextView -{ - NSString *key = [self slk_keyForPersistency]; - if (key == nil) { - return; - } - NSString *cachedText = [[NSUserDefaults standardUserDefaults] objectForKey:key]; - - if (self.textView.text.length == 0 || cachedText.length > 0) { - self.textView.text = cachedText; - } -} - -- (void)slk_cacheTextView -{ - [self slk_cacheTextToDisk:self.textView.text]; -} - -- (void)clearCachedText -{ - [self slk_cacheTextToDisk:nil]; -} - -- (void)slk_cacheTextToDisk:(NSString *)text -{ - NSString *key = [self slk_keyForPersistency]; - - if (!key || key.length == 0) { - return; - } - - NSString *cachedText = [[NSUserDefaults standardUserDefaults] objectForKey:key]; - - // Caches text only if its a valid string and not already cached - if (text.length > 0 && ![text isEqualToString:cachedText]) { - [[NSUserDefaults standardUserDefaults] setObject:text forKey:key]; - } - // Clears cache only if it exists - else if (text.length == 0 && cachedText.length > 0) { - [[NSUserDefaults standardUserDefaults] removeObjectForKey:key]; - } - else { - // Skips so it doesn't hit 'synchronize' unnecessarily - return; - } - - [[NSUserDefaults standardUserDefaults] synchronize]; -} - -+ (void)clearAllCachedText -{ - NSMutableArray *cachedKeys = [NSMutableArray new]; - - for (NSString *key in [[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] allKeys]) { - if ([key rangeOfString:SLKTextViewControllerDomain].location != NSNotFound) { - [cachedKeys addObject:key]; - } - } - - if (cachedKeys.count == 0) { - return; - } - - for (NSString *cachedKey in cachedKeys) { - [[NSUserDefaults standardUserDefaults] removeObjectForKey:cachedKey]; - } - - [[NSUserDefaults standardUserDefaults] synchronize]; -} - - -#pragma mark - Customization - -- (void)registerClassForTextView:(Class)aClass -{ - if (aClass == nil) { - return; - } - - NSAssert([aClass isSubclassOfClass:[SLKTextView class]], @"The registered class is invalid, it must be a subclass of SLKTextView."); - self.textViewClass = aClass; -} - -- (void)registerClassForTypingIndicatorView:(Class)aClass -{ - if (aClass == nil) { - return; - } - - NSAssert([aClass isSubclassOfClass:[UIView class]], @"The registered class is invalid, it must be a subclass of UIView."); - self.typingIndicatorViewClass = aClass; -} - - -#pragma mark - UITextViewDelegate Methods - -- (BOOL)textView:(SLKTextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text -{ - if (![textView isKindOfClass:[SLKTextView class]]) { - return YES; - } - - BOOL newWordInserted = ([text rangeOfCharacterFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]].location != NSNotFound); - - // It should not change if auto-completion is active and trying to replace with an auto-correction suggested text. - if (self.isAutoCompleting && text.length > 1) { - return NO; - } - - // Records text for undo for every new word - if (newWordInserted) { - [textView slk_prepareForUndo:@"Word Change"]; - } - - // Detects double spacebar tapping, to replace the default "." insert with a formatting symbol, if needed. - if (textView.autoCompleteFormatting && range.location > 0 && [text length] > 0 && - [[NSCharacterSet whitespaceCharacterSet] characterIsMember:[text characterAtIndex:0]] && - [[NSCharacterSet whitespaceCharacterSet] characterIsMember:[textView.text characterAtIndex:range.location - 1]]) { - - BOOL shouldChange = YES; - - NSRange wordRange = range; - wordRange.location -= 2; // minus the white space added with the double space bar tapping - - NSArray *symbols = textView.registeredSymbols; - - NSMutableCharacterSet *invalidCharacters = [NSMutableCharacterSet new]; - [invalidCharacters formUnionWithCharacterSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - [invalidCharacters formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]]; - [invalidCharacters removeCharactersInString:[symbols componentsJoinedByString:@""]]; - - for (NSString *symbol in symbols) { - - // Detects the closest registered symbol to the caret, from right to left - NSRange searchRange = NSMakeRange(0, wordRange.location); - NSRange prefixRange = [textView.text rangeOfString:symbol options:NSBackwardsSearch range:searchRange]; - - if (prefixRange.location == NSNotFound) { - continue; - } - - NSRange nextCharRange = NSMakeRange(prefixRange.location+1, 1); - NSString *charAfterSymbol = [textView.text substringWithRange:nextCharRange]; - - if (prefixRange.location != NSNotFound && ![invalidCharacters characterIsMember:[charAfterSymbol characterAtIndex:0]]) { - - if ([self textView:textView shouldInsertSuffixForFormattingWithSymbol:symbol prefixRange:prefixRange]) { - - NSRange suffixRange; - [textView slk_wordAtRange:wordRange rangeInText:&suffixRange]; - - // Skip if the detected word already has a suffix - if ([[textView.text substringWithRange:suffixRange] hasSuffix:symbol]) { - continue; - } - - suffixRange.location += suffixRange.length; - suffixRange.length = 0; - - NSString *lastCharacter = [textView.text substringWithRange:NSMakeRange(suffixRange.location, 1)]; - - // Checks if the last character was a line break, so we append the symbol in the next line too - if ([[NSCharacterSet newlineCharacterSet] characterIsMember:[lastCharacter characterAtIndex:0]]) { - suffixRange.location += 1; - } - - [textView slk_insertText:symbol inRange:suffixRange]; - shouldChange = NO; - - break; // exit - } - } - } - - return shouldChange; - } - else if ([text isEqualToString:@"\n"]) { - //Detected break. Should insert new line break programatically instead. - [textView slk_insertNewLineBreak]; - - return NO; - } - else { - NSDictionary *userInfo = @{@"text": text, @"range": [NSValue valueWithRange:range]}; - [[NSNotificationCenter defaultCenter] postNotificationName:SLKTextViewTextWillChangeNotification object:self.textView userInfo:userInfo]; - - return YES; - } -} - -- (void)textViewDidChange:(SLKTextView *)textView -{ - // Keep to avoid unnecessary crashes. Was meant to be overriden in subclass while calling super. -} - -- (void)textViewDidChangeSelection:(SLKTextView *)textView -{ - // Keep to avoid unnecessary crashes. Was meant to be overriden in subclass while calling super. -} - -- (BOOL)textViewShouldBeginEditing:(SLKTextView *)textView -{ - return YES; -} - -- (BOOL)textViewShouldEndEditing:(SLKTextView *)textView -{ - return YES; -} - -- (void)textViewDidBeginEditing:(SLKTextView *)textView -{ - // No implementation here. Meant to be overriden in subclass. -} - -- (void)textViewDidEndEditing:(SLKTextView *)textView -{ - // No implementation here. Meant to be overriden in subclass. -} - - -#pragma mark - SLKTextViewDelegate Methods - -- (BOOL)textView:(SLKTextView *)textView shouldOfferFormattingForSymbol:(NSString *)symbol -{ - return YES; -} - -- (BOOL)textView:(SLKTextView *)textView shouldInsertSuffixForFormattingWithSymbol:(NSString *)symbol prefixRange:(NSRange)prefixRange -{ - if (prefixRange.location > 0) { - NSRange previousCharRange = NSMakeRange(prefixRange.location-1, 1); - NSString *previousCharacter = [self.textView.text substringWithRange:previousCharRange]; - - // Only insert a suffix if the character before the prefix was a whitespace or a line break - if ([previousCharacter rangeOfCharacterFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]].location != NSNotFound) { - return YES; - } - else { - return NO; - } - } - - return YES; -} - - -#pragma mark - UITableViewDataSource Methods - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section -{ - return 0; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath -{ - return nil; -} - - -#pragma mark - UICollectionViewDataSource Methods - -- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section; -{ - return 0; -} - -- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath -{ - return nil; -} - - -#pragma mark - UIScrollViewDelegate Methods - -- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView -{ - if (!self.scrollViewProxy.scrollsToTop || self.keyboardStatus == SLKKeyboardStatusWillShow) { - return NO; - } - - if (self.isInverted) { - [self.scrollViewProxy slk_scrollToBottomAnimated:YES]; - return NO; - } - else { - return YES; - } -} - -- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate -{ - self.movingKeyboard = NO; -} - -- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView -{ - self.movingKeyboard = NO; -} - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView -{ - if ([scrollView isEqual:self.autoCompletionView]) { - CGRect frame = self.autoCompletionHairline.frame; - frame.origin.y = scrollView.contentOffset.y; - self.autoCompletionHairline.frame = frame; - } - else { - if (!self.isMovingKeyboard) { - _scrollViewOffsetBeforeDragging = scrollView.contentOffset; - _keyboardHeightBeforeDragging = self.keyboardHC.constant; - } - } -} - - -#pragma mark - UIGestureRecognizerDelegate Methods - -- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gesture -{ - if ([gesture isEqual:self.singleTapGesture]) { - return [self.textView isFirstResponder] && ![self ignoreTextInputbarAdjustment]; - } - else if ([gesture isEqual:self.verticalPanGesture]) { - return self.keyboardPanningEnabled && ![self ignoreTextInputbarAdjustment]; - } - - return NO; -} - - -#pragma mark - UIAlertViewDelegate Methods - -- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex -{ - if (alertView.tag != kSLKAlertViewClearTextTag || buttonIndex == [alertView cancelButtonIndex] ) { - return; - } - - // Clears the text but doesn't clear the undo manager - if (self.shakeToClearEnabled) { - [self.textView slk_clearText:NO]; - } -} - - -#pragma mark - View Auto-Layout - -- (void)slk_setupViewConstraints -{ - NSDictionary *views = @{@"scrollView": self.scrollViewProxy, - @"autoCompletionView": self.autoCompletionView, - @"typingIndicatorView": self.typingIndicatorProxyView, - @"textInputbar": self.textInputbar, - }; - - [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView(0@750)][autoCompletionView(0@750)][typingIndicatorView(0)]-0@999-[textInputbar(0)]-0-|" options:0 metrics:nil views:views]]; - [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics:nil views:views]]; - [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[autoCompletionView]|" options:0 metrics:nil views:views]]; - [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[typingIndicatorView]|" options:0 metrics:nil views:views]]; - [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[textInputbar]|" options:0 metrics:nil views:views]]; - - self.scrollViewHC = [self.view slk_constraintForAttribute:NSLayoutAttributeHeight firstItem:self.scrollViewProxy secondItem:nil]; - self.autoCompletionViewHC = [self.view slk_constraintForAttribute:NSLayoutAttributeHeight firstItem:self.autoCompletionView secondItem:nil]; - self.typingIndicatorViewHC = [self.view slk_constraintForAttribute:NSLayoutAttributeHeight firstItem:self.typingIndicatorProxyView secondItem:nil]; - self.textInputbarHC = [self.view slk_constraintForAttribute:NSLayoutAttributeHeight firstItem:self.textInputbar secondItem:nil]; - self.keyboardHC = [self.view slk_constraintForAttribute:NSLayoutAttributeBottom firstItem:self.view secondItem:self.textInputbar]; - - [self slk_updateViewConstraints]; -} - -- (void)slk_updateViewConstraints -{ - self.textInputbarHC.constant = self.textInputbar.minimumInputbarHeight; - self.scrollViewHC.constant = [self slk_appropriateScrollViewHeight]; - self.keyboardHC.constant = [self slk_appropriateKeyboardHeightFromRect:CGRectNull]; - - if (self.textInputbar.isEditing) { - self.textInputbarHC.constant += self.textInputbar.editorContentViewHeight; - } - - [super updateViewConstraints]; -} - - -#pragma mark - External Keyboard Support - -- (NSArray *)keyCommands -{ - NSMutableArray *keyboardCommands = [NSMutableArray new]; - - [keyboardCommands addObject:[self slk_returnKeyCommand]]; - [keyboardCommands addObject:[self slk_escKeyCommand]]; - [keyboardCommands addObject:[self slk_arrowKeyCommand:UIKeyInputUpArrow]]; - [keyboardCommands addObject:[self slk_arrowKeyCommand:UIKeyInputDownArrow]]; - - return keyboardCommands; -} - -- (UIKeyCommand *)slk_returnKeyCommand -{ - UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:0 action:@selector(didPressReturnKey:)]; - -#ifdef __IPHONE_9_0 - if ([UIKeyCommand respondsToSelector:@selector(keyCommandWithInput:modifierFlags:action:discoverabilityTitle:)] ) { - // Only available since iOS 9 - if (self.textInputbar.isEditing) { - command.discoverabilityTitle = [self.textInputbar.editorRightButton titleForState:UIControlStateNormal] ? : NSLocalizedString(@"Commit Editing", nil); - } - else if (self.textView.text.length > 0) { - command.discoverabilityTitle = [self.rightButton titleForState:UIControlStateNormal] ? : NSLocalizedString(@"Send", nil); - } - } -#endif - - return command; -} - -- (UIKeyCommand *)slk_escKeyCommand -{ - UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:UIKeyInputEscape modifierFlags:0 action:@selector(didPressEscapeKey:)]; - -#ifdef __IPHONE_9_0 - if ([UIKeyCommand respondsToSelector:@selector(keyCommandWithInput:modifierFlags:action:discoverabilityTitle:)] ) { - // Only available since iOS 9 - if (self.isAutoCompleting) { - command.discoverabilityTitle = NSLocalizedString(@"Exit Auto-Completion", nil); - } - else if (self.textInputbar.isEditing) { - command.discoverabilityTitle = [self.textInputbar.editorRightButton titleForState:UIControlStateNormal] ? : NSLocalizedString(@"Exit Editing", nil); - } - else if (!self.isExternalKeyboardDetected && self.keyboardHC.constant != 0) { - command.discoverabilityTitle = NSLocalizedString(@"Hide Keyboard", nil); - } - } -#endif - - return command; -} - -- (UIKeyCommand *)slk_arrowKeyCommand:(NSString *)inputUpArrow -{ - UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:inputUpArrow modifierFlags:0 action:@selector(didPressArrowKey:)]; - -#ifdef __IPHONE_9_0 - // Only available since iOS 9 - if ([UIKeyCommand respondsToSelector:@selector(keyCommandWithInput:modifierFlags:action:discoverabilityTitle:)] ) { - if ([inputUpArrow isEqualToString:UIKeyInputUpArrow]) { - command.discoverabilityTitle = NSLocalizedString(@"Move Up", nil); - } - if ([inputUpArrow isEqualToString:UIKeyInputDownArrow]) { - command.discoverabilityTitle = NSLocalizedString(@"Move Down", nil); - } - } -#endif - - return command; -} - - -#pragma mark - NSNotificationCenter register/unregister - -- (void)slk_registerNotifications -{ - [self slk_unregisterNotifications]; - - // Keyboard notifications - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_willShowOrHideKeyboard:) name:UIKeyboardWillShowNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_willShowOrHideKeyboard:) name:UIKeyboardWillHideNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_didShowOrHideKeyboard:) name:UIKeyboardDidShowNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_didShowOrHideKeyboard:) name:UIKeyboardDidHideNotification object:nil]; - -#if SLK_KEYBOARD_NOTIFICATION_DEBUG - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_didPostSLKKeyboardNotification:) name:SLKKeyboardWillShowNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_didPostSLKKeyboardNotification:) name:SLKKeyboardDidShowNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_didPostSLKKeyboardNotification:) name:SLKKeyboardWillHideNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_didPostSLKKeyboardNotification:) name:SLKKeyboardDidHideNotification object:nil]; -#endif - - // TextView notifications - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_willChangeTextViewText:) name:SLKTextViewTextWillChangeNotification object:nil]; - [[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_didChangeTextViewSelectedRange:) name:SLKTextViewSelectedRangeDidChangeNotification object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_didChangeTextViewPasteboard:) name:SLKTextViewDidPasteItemNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_didShakeTextView:) name:SLKTextViewDidShakeNotification object:nil]; - - // Application notifications - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_willTerminateApplication:) name:UIApplicationWillTerminateNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_willTerminateApplication:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; -} - -- (void)slk_unregisterNotifications -{ - // Keyboard notifications - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidShowNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidHideNotification object:nil]; - -#if SLK_KEYBOARD_NOTIFICATION_DEBUG - [[NSNotificationCenter defaultCenter] removeObserver:self name:SLKKeyboardWillShowNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:SLKKeyboardDidShowNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:SLKKeyboardWillHideNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:SLKKeyboardDidHideNotification object:nil]; -#endif - - // TextView notifications - [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidBeginEditingNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidEndEditingNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:SLKTextViewTextWillChangeNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidChangeNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:SLKTextViewContentSizeDidChangeNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:SLKTextViewSelectedRangeDidChangeNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:SLKTextViewDidPasteItemNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:SLKTextViewDidShakeNotification object:nil]; - - // Application notifications - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; -} - - -#pragma mark - View Auto-Rotation - -#ifdef __IPHONE_8_0 -- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator -{ - [super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator]; -} - -- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator -{ - [self slk_prepareForInterfaceTransitionWithDuration:coordinator.transitionDuration]; - - [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; -} -#else -- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration -{ - if ([self respondsToSelector:@selector(viewWillTransitionToSize:withTransitionCoordinator:)]) { - return; - } - - [self slk_prepareForInterfaceTransitionWithDuration:duration]; -} -#endif - -#ifdef __IPHONE_9_0 -- (UIInterfaceOrientationMask)supportedInterfaceOrientations -#else -- (NSUInteger)supportedInterfaceOrientations -#endif -{ - return UIInterfaceOrientationMaskAll; -} - -- (BOOL)shouldAutorotate -{ - return YES; -} - - -#pragma mark - View lifeterm - -- (void)didReceiveMemoryWarning -{ - [super didReceiveMemoryWarning]; -} - -- (void)dealloc -{ - _tableView.delegate = nil; - _tableView.dataSource = nil; - _tableView = nil; - - _collectionView.delegate = nil; - _collectionView.dataSource = nil; - _collectionView = nil; - - _scrollView = nil; - - _autoCompletionView.delegate = nil; - _autoCompletionView.dataSource = nil; - _autoCompletionView = nil; - - _textInputbar.textView.delegate = nil; - _textInputbar = nil; - _textViewClass = nil; - - [_typingIndicatorProxyView removeObserver:self forKeyPath:@"visible"]; - _typingIndicatorProxyView = nil; - _typingIndicatorViewClass = nil; - - _registeredPrefixes = nil; - _singleTapGesture.delegate = nil; - _singleTapGesture = nil; - _verticalPanGesture.delegate = nil; - _verticalPanGesture = nil; - _scrollViewHC = nil; - _textInputbarHC = nil; - _typingIndicatorViewHC = nil; - _autoCompletionViewHC = nil; - _keyboardHC = nil; - - [self slk_unregisterNotifications]; -} - -@end
http://git-wip-us.apache.org/repos/asf/usergrid/blob/c638c774/sdks/swift/Samples/ActivityFeed/Pods/SlackTextViewController/Source/SLKTypingIndicatorProtocol.h ---------------------------------------------------------------------- diff --git a/sdks/swift/Samples/ActivityFeed/Pods/SlackTextViewController/Source/SLKTypingIndicatorProtocol.h b/sdks/swift/Samples/ActivityFeed/Pods/SlackTextViewController/Source/SLKTypingIndicatorProtocol.h deleted file mode 100644 index 7b2deae..0000000 --- a/sdks/swift/Samples/ActivityFeed/Pods/SlackTextViewController/Source/SLKTypingIndicatorProtocol.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// Copyright 2014 Slack Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#import <Foundation/Foundation.h> - -/** Generic protocol needed when customizing your own typing indicator view. */ -@protocol SLKTypingIndicatorProtocol <NSObject> -@required - -/** - Returns YES if the indicator is visible. - SLKTextViewController depends on this property internally, by observing its value changes to update the typing indicator view's constraints automatically. - You can simply @synthesize this property to make it KVO compliant, or override its setter method and wrap its implementation with -willChangeValueForKey: and -didChangeValueForKey: methods, for more complex KVO compliance. - */ -@property (nonatomic, getter = isVisible) BOOL visible; - -@optional - -/** - Dismisses the indicator view. - */ -- (void)dismissIndicator; - -@end \ No newline at end of file
