You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@usergrid.apache.org by sn...@apache.org on 2016/02/18 16:52:55 UTC

[47/89] [partial] usergrid git commit: Initial commit of the Swift SDK.

http://git-wip-us.apache.org/repos/asf/usergrid/blob/7442c881/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
new file mode 100644
index 0000000..5febfcb
--- /dev/null
+++ b/sdks/swift/Samples/ActivityFeed/Pods/SlackTextViewController/Source/SLKTextViewController.m
@@ -0,0 +1,2392 @@
+//
+//   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/7442c881/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
new file mode 100644
index 0000000..7b2deae
--- /dev/null
+++ b/sdks/swift/Samples/ActivityFeed/Pods/SlackTextViewController/Source/SLKTypingIndicatorProtocol.h
@@ -0,0 +1,37 @@
+//
+//   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