diff --git a/Examples/Messenger-Shared/Images.xcassets/Icons/icn_append.pdf b/Examples/Messenger-Shared/Images.xcassets/Icons/icn_append.pdf deleted file mode 100644 index 5089697a..00000000 Binary files a/Examples/Messenger-Shared/Images.xcassets/Icons/icn_append.pdf and /dev/null differ diff --git a/Examples/Messenger-Shared/Images.xcassets/Icons/icn_arrow_down.imageset/Contents.json b/Examples/Messenger-Shared/Images.xcassets/Icons/icn_arrow_down.imageset/Contents.json new file mode 100644 index 00000000..b9e07580 --- /dev/null +++ b/Examples/Messenger-Shared/Images.xcassets/Icons/icn_arrow_down.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "icn_arrow_down.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Examples/Messenger-Shared/Images.xcassets/Icons/icn_arrow_down.imageset/icn_arrow_down.pdf b/Examples/Messenger-Shared/Images.xcassets/Icons/icn_arrow_down.imageset/icn_arrow_down.pdf new file mode 100644 index 00000000..97d70502 Binary files /dev/null and b/Examples/Messenger-Shared/Images.xcassets/Icons/icn_arrow_down.imageset/icn_arrow_down.pdf differ diff --git a/Examples/Messenger-Shared/Images.xcassets/Icons/icn_arrow_up.imageset/Contents.json b/Examples/Messenger-Shared/Images.xcassets/Icons/icn_arrow_up.imageset/Contents.json new file mode 100644 index 00000000..c3ddcb6a --- /dev/null +++ b/Examples/Messenger-Shared/Images.xcassets/Icons/icn_arrow_up.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "icn_arrow_up.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Examples/Messenger-Shared/Images.xcassets/Icons/icn_arrow_up.imageset/icn_arrow_up.pdf b/Examples/Messenger-Shared/Images.xcassets/Icons/icn_arrow_up.imageset/icn_arrow_up.pdf new file mode 100644 index 00000000..8d173f10 Binary files /dev/null and b/Examples/Messenger-Shared/Images.xcassets/Icons/icn_arrow_up.imageset/icn_arrow_up.pdf differ diff --git a/Examples/Messenger-Shared/Images.xcassets/Icons/icn_editing.pdf b/Examples/Messenger-Shared/Images.xcassets/Icons/icn_editing.pdf deleted file mode 100644 index a34c4cdf..00000000 Binary files a/Examples/Messenger-Shared/Images.xcassets/Icons/icn_editing.pdf and /dev/null differ diff --git a/Examples/Messenger-Shared/Images.xcassets/Icons/icn_pic.pdf b/Examples/Messenger-Shared/Images.xcassets/Icons/icn_pic.pdf deleted file mode 100644 index 5e7d5f48..00000000 Binary files a/Examples/Messenger-Shared/Images.xcassets/Icons/icn_pic.pdf and /dev/null differ diff --git a/Examples/Messenger-Shared/Images.xcassets/Icons/icn_typing.pdf b/Examples/Messenger-Shared/Images.xcassets/Icons/icn_typing.pdf deleted file mode 100644 index d441e398..00000000 Binary files a/Examples/Messenger-Shared/Images.xcassets/Icons/icn_typing.pdf and /dev/null differ diff --git a/Examples/Messenger-Shared/Images.xcassets/Icons/icn_upload.pdf b/Examples/Messenger-Shared/Images.xcassets/Icons/icn_upload.pdf deleted file mode 100644 index 10ebdd9d..00000000 Binary files a/Examples/Messenger-Shared/Images.xcassets/Icons/icn_upload.pdf and /dev/null differ diff --git a/Examples/Messenger-Shared/MessageViewController.m b/Examples/Messenger-Shared/MessageViewController.m index 671f55ee..926df400 100644 --- a/Examples/Messenger-Shared/MessageViewController.m +++ b/Examples/Messenger-Shared/MessageViewController.m @@ -158,6 +158,11 @@ - (void)configureDataSource - (void)configureActionItems { + UIBarButtonItem *arrowItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"icn_arrow_down"] + style:UIBarButtonItemStylePlain + target:self + action:@selector(hideOrShowTextInputbar:)]; + UIBarButtonItem *editItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"icn_editing"] style:UIBarButtonItemStylePlain target:self @@ -178,12 +183,23 @@ - (void)configureActionItems target:self action:@selector(togglePIPWindow:)]; - self.navigationItem.rightBarButtonItems = @[pipItem, editItem, appendItem, typeItem]; + self.navigationItem.rightBarButtonItems = @[arrowItem, pipItem, editItem, appendItem, typeItem]; } #pragma mark - Action Methods +- (void)hideOrShowTextInputbar:(id)sender +{ + BOOL hide = !self.textInputbarHidden; + + UIImage *image = hide ? [UIImage imageNamed:@"icn_arrow_up"] : [UIImage imageNamed:@"icn_arrow_down"]; + UIBarButtonItem *buttonItem = (UIBarButtonItem *)sender; + + [self setTextInputbarHidden:hide animated:YES]; + [buttonItem setImage:image]; +} + - (void)fillWithText:(id)sender { if (self.textView.text.length == 0) @@ -406,15 +422,14 @@ - (void)didPressRightButton:(id)sender [super didPressRightButton:sender]; } -- (void)didPressArrowKey:(id)sender +- (void)didPressArrowKey:(UIKeyCommand *)keyCommand { - [super didPressArrowKey:sender]; - - UIKeyCommand *keyCommand = (UIKeyCommand *)sender; - - if ([keyCommand.input isEqualToString:UIKeyInputUpArrow]) { + if ([keyCommand.input isEqualToString:UIKeyInputUpArrow] && self.textView.text.length == 0) { [self editLastMessage:nil]; } + else { + [super didPressArrowKey:keyCommand]; + } } - (NSString *)keyForTextCaching diff --git a/Source/SLKTextInputbar.h b/Source/SLKTextInputbar.h index 3c1de11b..1e27ac94 100644 --- a/Source/SLKTextInputbar.h +++ b/Source/SLKTextInputbar.h @@ -16,7 +16,6 @@ #import -@class SLKTextViewController; @class SLKTextView; @class SLKInputAccessoryView; @@ -35,9 +34,6 @@ typedef NS_ENUM(NSUInteger, SLKCounterPosition) { /** @name A custom tool bar encapsulating messaging controls. */ @interface SLKTextInputbar : UIToolbar -/** A weak reference to the core view controller. */ -@property (nonatomic, weak) SLKTextViewController *controller; - /** The centered text input view. The maximum number of lines is configured by default, to best fit each devices dimensions. For iPhone 4 (<=480pts): 4 lines @@ -58,6 +54,9 @@ typedef NS_ENUM(NSUInteger, SLKCounterPosition) { /** YES if the right button should be hidden animatedly in case the text view has no text in it. Default is YES. */ @property (nonatomic, readwrite) BOOL autoHideRightButton; +/** YES if animations should have bouncy effects. Default is YES. */ +@property (nonatomic, assign) BOOL bounces; + /** The inner padding to use when laying out content in the view. Default is {5, 8, 5, 8}. */ @property (nonatomic, assign) UIEdgeInsets contentInset; diff --git a/Source/SLKTextInputbar.m b/Source/SLKTextInputbar.m index 02f422bf..479296e3 100644 --- a/Source/SLKTextInputbar.m +++ b/Source/SLKTextInputbar.m @@ -15,7 +15,6 @@ // #import "SLKTextInputbar.h" -#import "SLKTextViewController.h" #import "SLKTextView.h" #import "SLKInputAccessoryView.h" @@ -45,9 +44,12 @@ @interface SLKTextInputbar () @property (nonatomic, strong) Class textViewClass; +@property (nonatomic, getter=isHidden) BOOL hidden; // Required override + @end @implementation SLKTextInputbar +@synthesize hidden = _hidden; #pragma mark - Initialization @@ -281,6 +283,11 @@ - (UILabel *)charCountLabel return _charCountLabel; } +- (BOOL)isHidden +{ + return _hidden; +} + - (CGFloat)minimumInputbarHeight { CGFloat minimumTextViewHeight = self.textView.intrinsicContentSize.height; @@ -425,6 +432,14 @@ - (void)setEditing:(BOOL)editing _editorContentView.hidden = !editing; } +- (void)setHidden:(BOOL)hidden +{ + // We don't call super here, since we want to avoid to visually hide the view. + // The hidden render state is handled by the view controller. + + _hidden = hidden; +} + - (void)setCounterPosition:(SLKCounterPosition)counterPosition { if (self.counterPosition == counterPosition && self.charCountLabelVCs) { @@ -463,7 +478,7 @@ - (void)setCounterPosition:(SLKCounterPosition)counterPosition - (BOOL)canEditText:(NSString *)text { - if ((self.isEditing && [self.textView.text isEqualToString:text]) || self.controller.isTextInputbarHidden) { + if ((self.isEditing && [self.textView.text isEqualToString:text]) || self.isHidden) { return NO; } @@ -472,7 +487,7 @@ - (BOOL)canEditText:(NSString *)text - (void)beginTextEditing { - if (self.isEditing || self.controller.isTextInputbarHidden) { + if (self.isEditing || self.isHidden) { return; } @@ -487,7 +502,7 @@ - (void)beginTextEditing - (void)endTextEdition { - if (!self.isEditing || self.controller.isTextInputbarHidden) { + if (!self.isEditing || self.isHidden) { return; } @@ -552,7 +567,7 @@ - (void)slk_didChangeTextViewText:(NSNotification *)notification self.rightMarginWC.constant = [self slk_appropriateRightButtonMargin]; [self.rightButton layoutIfNeeded]; // Avoids the right button to stretch when animating the constraint changes - BOOL bounces = self.controller.bounces && [self.textView isFirstResponder]; + BOOL bounces = self.bounces && [self.textView isFirstResponder]; if (self.window) { [self slk_animateLayoutIfNeededWithBounce:bounces @@ -704,7 +719,7 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N } -#pragma mark - NSNotificationCenter register/unregister +#pragma mark - NSNotificationCenter registration - (void)slk_registerNotifications { diff --git a/Source/SLKTextView.h b/Source/SLKTextView.h index 77d3e2b3..962c53e6 100644 --- a/Source/SLKTextView.h +++ b/Source/SLKTextView.h @@ -99,7 +99,7 @@ typedef NS_OPTIONS(NSUInteger, SLKPastableMediaType) { /** Notifies the text view that the user pressed any arrow key. This is used to move the cursor up and down while having multiple lines. */ -- (void)didPressAnyArrowKey:(id)sender; +- (void)didPressArrowKey:(UIKeyCommand *)keyCommand; #pragma mark - Markdown Formatting @@ -121,6 +121,20 @@ typedef NS_OPTIONS(NSUInteger, SLKPastableMediaType) { */ - (void)registerMarkdownFormattingSymbol:(NSString *)symbol withTitle:(NSString *)title; + +#pragma mark - External Keyboard Support + +/** + Registers and observes key commands' updates, when the text view is first responder. + Instead of typically overriding UIResponder's -keyCommands method, it is better to use this API for easier and safer implementation of key input detection. + + @param input The keys that must be pressed by the user. Required. + @param modifiers The bit mask of modifier keys that must be pressed. Use 0 if none. + @param title The title to display to the user. Optional. + @param completion A completion block called whenever the key combination is detected. Required. + */ +- (void)observeKeyInput:(NSString *)input modifiers:(UIKeyModifierFlags)modifiers title:(NSString *)title completion:(void (^)(UIKeyCommand *keyCommand))completion; + @end diff --git a/Source/SLKTextView.m b/Source/SLKTextView.m index 7ea5ea9b..6a96520d 100644 --- a/Source/SLKTextView.m +++ b/Source/SLKTextView.m @@ -40,9 +40,6 @@ @interface SLKTextView () // The initial font point size, used for dynamic type calculations @property (nonatomic) CGFloat initialFontSize; -// The keyboard commands available for external keyboards -@property (nonatomic, strong) NSArray *keyboardCommands; - // Used for moving the caret up/down @property (nonatomic) UITextLayoutDirection verticalMoveDirection; @property (nonatomic) CGRect verticalMoveStartCaretRect; @@ -55,6 +52,10 @@ @interface SLKTextView () @property (nonatomic, strong) NSMutableArray *registeredFormattingSymbols; @property (nonatomic, getter=isFormatting) BOOL formatting; +// The keyboard commands available for external keyboards +@property (nonatomic, strong) NSMutableDictionary *registeredKeyCommands; +@property (nonatomic, strong) NSMutableDictionary *registeredKeyCallbacks; + @end @implementation SLKTextView @@ -227,7 +228,7 @@ - (BOOL)isTypingSuggestionEnabled - (BOOL)isFormattingEnabled { - return (_registeredFormattingSymbols.count > 0) ? YES : NO; + return (self.registeredFormattingSymbols.count > 0) ? YES : NO; } // Returns only a supported pasted item @@ -515,6 +516,7 @@ - (void)setTextAlignment:(NSTextAlignment)textAlignment #pragma mark - UITextInput Overrides +#ifdef __IPHONE_9_0 - (void)beginFloatingCursorAtPoint:(CGPoint)point { [super beginFloatingCursorAtPoint:point]; @@ -540,7 +542,7 @@ - (void)endFloatingCursor [[NSNotificationCenter defaultCenter] postNotificationName:SLKTextViewSelectedRangeDidChangeNotification object:self userInfo:nil]; } - +#endif #pragma mark - UIResponder Overrides @@ -916,68 +918,70 @@ - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event #pragma mark - External Keyboard Support -- (NSArray *)keyCommands +typedef void (^SLKKeyCommandHandler)(UIKeyCommand *keyCommand); + +- (void)observeKeyInput:(NSString *)input modifiers:(UIKeyModifierFlags)modifiers title:(NSString *)title completion:(SLKKeyCommandHandler)completion; { - if (_keyboardCommands) { - return _keyboardCommands; + NSAssert([input isKindOfClass:[NSString class]], @"You must provide a string with one or more characters corresponding to the keys to observe."); + NSAssert(completion != nil, @"You must provide a non-nil completion block."); + + if (!input || !completion) { + return; } - _keyboardCommands = @[ - // Return - [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:UIKeyModifierShift action:@selector(slk_didPressLineBreakKeys:)], - [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:UIKeyModifierAlternate action:@selector(slk_didPressLineBreakKeys:)], - [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:UIKeyModifierControl action:@selector(slk_didPressLineBreakKeys:)], - - // Undo/Redo - [UIKeyCommand keyCommandWithInput:@"z" modifierFlags:UIKeyModifierCommand action:@selector(slk_didPressCommandZKeys:)], - [UIKeyCommand keyCommandWithInput:@"z" modifierFlags:UIKeyModifierShift|UIKeyModifierCommand action:@selector(slk_didPressCommandZKeys:)], - ]; + UIKeyCommand *keyCommand = [UIKeyCommand keyCommandWithInput:input modifierFlags:modifiers action:@selector(didDetectKeyCommand:)]; - return _keyboardCommands; +#ifdef __IPHONE_9_0 + if ([UIKeyCommand respondsToSelector:@selector(keyCommandWithInput:modifierFlags:action:discoverabilityTitle:)] ) { + keyCommand.discoverabilityTitle = title; + } +#endif + + if (!_registeredKeyCommands) { + _registeredKeyCommands = [NSMutableDictionary new]; + _registeredKeyCallbacks = [NSMutableDictionary new]; + } + + NSString *key = [self keyForKeyCommand:keyCommand]; + + self.registeredKeyCommands[key] = keyCommand; + self.registeredKeyCallbacks[key] = completion; } - -#pragma mark Line Break - -- (void)slk_didPressLineBreakKeys:(id)sender +- (void)didDetectKeyCommand:(UIKeyCommand *)keyCommand { - [self slk_insertNewLineBreak]; + NSString *key = [self keyForKeyCommand:keyCommand]; + + SLKKeyCommandHandler completion = self.registeredKeyCallbacks[key]; + + if (completion) { + completion(keyCommand); + } } +- (NSString *)keyForKeyCommand:(UIKeyCommand *)keyCommand +{ + return [NSString stringWithFormat:@"%@_%ld", keyCommand.input, (long)keyCommand.modifierFlags]; +} -#pragma mark Undo/Redo Text - -- (void)slk_didPressCommandZKeys:(id)sender +- (NSArray *)keyCommands { - if (!self.undoManagerEnabled) { - return; + if (self.registeredKeyCommands) { + return [self.registeredKeyCommands allValues]; } - UIKeyCommand *keyCommand = (UIKeyCommand *)sender; - - if ((keyCommand.modifierFlags & UIKeyModifierShift) > 0) { - - if ([self.undoManager canRedo]) { - [self.undoManager redo]; - } - } - else { - if ([self.undoManager canUndo]) { - [self.undoManager undo]; - } - } + return nil; } + #pragma mark Up/Down Cursor Movement -- (void)didPressAnyArrowKey:(id)sender +- (void)didPressArrowKey:(UIKeyCommand *)keyCommand { - if (self.text.length == 0 || self.numberOfLines < 2) { + if (![keyCommand isKindOfClass:[UIKeyCommand class]] || self.text.length == 0 || self.numberOfLines < 2) { return; } - UIKeyCommand *keyCommand = (UIKeyCommand *)sender; - if ([keyCommand.input isEqualToString:UIKeyInputUpArrow]) { [self slk_moveCursorTodirection:UITextLayoutDirectionUp]; } @@ -1070,7 +1074,7 @@ - (BOOL)slk_isNewVerticalMovementForPosition:(UITextPosition *)position inDirect } -#pragma mark - NSNotificationCenter register/unregister +#pragma mark - NSNotificationCenter registration - (void)slk_registerNotifications { @@ -1104,6 +1108,11 @@ - (void)dealloc [self removeObserver:self forKeyPath:NSStringFromSelector(@selector(contentSize))]; _placeholderLabel = nil; + + _registeredFormattingTitles = nil; + _registeredFormattingSymbols = nil; + _registeredKeyCommands = nil; + _registeredKeyCallbacks = nil; } @end diff --git a/Source/SLKTextViewController.h b/Source/SLKTextViewController.h index 18a4fefa..08ec917c 100644 --- a/Source/SLKTextViewController.h +++ b/Source/SLKTextViewController.h @@ -83,7 +83,7 @@ NS_CLASS_AVAILABLE_IOS(7_0) @interface SLKTextViewController : UIViewController /** A vertical pan gesture used for bringing the keyboard from the bottom. SLKTextViewController is its delegate. */ @property (nonatomic, readonly) UIPanGestureRecognizer *verticalPanGesture; -/** YES if control's animation should have bouncy effects. Default is YES. */ +/** YES if animations should have bouncy effects. Default is YES. */ @property (nonatomic, assign) BOOL bounces; /** YES if text view's content can be cleaned with a shake gesture. Default is NO. */ @@ -321,21 +321,21 @@ NS_CLASS_AVAILABLE_IOS(7_0) @interface SLKTextViewController : UIViewController You can override this method to perform additional tasks. You MUST call super at some point in your implementation. */ -- (void)didPressReturnKey:(id)sender NS_REQUIRES_SUPER; +- (void)didPressReturnKey:(UIKeyCommand *)keyCommand NS_REQUIRES_SUPER; /** Notifies the view controller when the user has pressed the Escape key (Esc) with an external keyboard. You can override this method to perform additional tasks. You MUST call super at some point in your implementation. */ -- (void)didPressEscapeKey:(id)sender NS_REQUIRES_SUPER; +- (void)didPressEscapeKey:(UIKeyCommand *)keyCommand NS_REQUIRES_SUPER; /** Notifies the view controller when the user has pressed the arrow key with an external keyboard. You can override this method to perform additional tasks. You MUST call super at some point in your implementation. */ -- (void)didPressArrowKey:(id)sender NS_REQUIRES_SUPER; +- (void)didPressArrowKey:(UIKeyCommand *)keyCommand NS_REQUIRES_SUPER; #pragma mark - Text Input Bar Adjustment diff --git a/Source/SLKTextViewController.m b/Source/SLKTextViewController.m index 60264687..fb67ac05 100644 --- a/Source/SLKTextViewController.m +++ b/Source/SLKTextViewController.m @@ -184,6 +184,8 @@ - (void)viewDidLoad [self.view addSubview:self.textInputbar]; [self slk_setupViewConstraints]; + + [self slk_registerKeyCommands]; } - (void)viewWillAppear:(BOOL)animated @@ -311,7 +313,6 @@ - (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]; @@ -355,19 +356,24 @@ - (BOOL)isPresentedInPopover return _presentedInPopover && SLK_IS_IPAD; } +- (BOOL)isTextInputbarHidden +{ + return _textInputbar.hidden; +} + - (SLKTextView *)textView { - return self.textInputbar.textView; + return _textInputbar.textView; } - (UIButton *)leftButton { - return self.textInputbar.leftButton; + return _textInputbar.leftButton; } - (UIButton *)rightButton { - return self.textInputbar.rightButton; + return _textInputbar.rightButton; } - (UIModalPresentationStyle)modalPresentationStyle @@ -563,6 +569,12 @@ - (void)setInverted:(BOOL)inverted self.scrollViewProxy.transform = inverted ? CGAffineTransformMake(1, 0, 0, -1, 0, 0) : CGAffineTransformIdentity; } +- (void)setBounces:(BOOL)bounces +{ + _bounces = bounces; + _textInputbar.bounces = bounces; +} + - (BOOL)slk_updateKeyboardStatus:(SLKKeyboardStatus)status { // Skips if trying to update the same status @@ -651,14 +663,14 @@ - (void)textWillUpdate - (void)textDidUpdate:(BOOL)animated { - if (self.textInputbarHidden) { + if (self.isTextInputbarHidden) { return; } - CGFloat inputbarHeight = self.textInputbar.appropriateHeight; + CGFloat inputbarHeight = _textInputbar.appropriateHeight; - self.textInputbar.rightButton.enabled = [self canPressRightButton]; - self.textInputbar.editorRightButton.enabled = [self canPressRightButton]; + _textInputbar.rightButton.enabled = [self canPressRightButton]; + _textInputbar.editorRightButton.enabled = [self canPressRightButton]; if (inputbarHeight != self.textInputbarHC.constant) { @@ -669,11 +681,13 @@ - (void)textDidUpdate:(BOOL)animated BOOL bounces = self.bounces && [self.textView isFirstResponder]; + __weak typeof(self) weakSelf = self; + [self.view slk_animateLayoutIfNeededWithBounce:bounces options:UIViewAnimationOptionCurveEaseInOut|UIViewAnimationOptionLayoutSubviews|UIViewAnimationOptionBeginFromCurrentState animations:^{ - if (self.textInputbar.isEditing) { - [self.textView slk_scrollToCaretPositonAnimated:NO]; + if (weakSelf.textInputbar.isEditing) { + [weakSelf.textView slk_scrollToCaretPositonAnimated:NO]; } }]; } @@ -713,7 +727,7 @@ - (BOOL)canPressRightButton { NSString *text = [self.textView.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - if (text.length > 0 && ![self.textInputbar limitExceeded]) { + if (text.length > 0 && ![_textInputbar limitExceeded]) { return YES; } @@ -738,14 +752,14 @@ - (void)didPressRightButton:(id)sender - (void)editText:(NSString *)text { - if (![self.textInputbar canEditText:text]) { + if (![_textInputbar canEditText:text]) { return; } // Caches the current text, in case the user cancels the edition [self slk_cacheTextToDisk:self.textView.text]; - [self.textInputbar beginTextEditing]; + [_textInputbar beginTextEditing]; // Setting the text after calling -beginTextEditing is safer [self.textView setText:text]; @@ -758,11 +772,11 @@ - (void)editText:(NSString *)text - (void)didCommitTextEditing:(id)sender { - if (!self.textInputbar.isEditing) { + if (!_textInputbar.isEditing) { return; } - [self.textInputbar endTextEdition]; + [_textInputbar endTextEdition]; // Clears the text and but not the undo manager [self.textView slk_clearText:NO]; @@ -770,11 +784,11 @@ - (void)didCommitTextEditing:(id)sender - (void)didCancelTextEditing:(id)sender { - if (!self.textInputbar.isEditing) { + if (!_textInputbar.isEditing) { return; } - [self.textInputbar endTextEdition]; + [_textInputbar endTextEdition]; // Clears the text and but not the undo manager [self.textView slk_clearText:NO]; @@ -786,7 +800,7 @@ - (void)didCancelTextEditing:(id)sender - (BOOL)canShowTypingIndicator { // Don't show if the text is being edited or auto-completed. - if (self.textInputbar.isEditing || self.isAutoCompleting) { + if (_textInputbar.isEditing || self.isAutoCompleting) { return NO; } @@ -866,7 +880,7 @@ - (void)setTextInputbarHidden:(BOOL)hidden animated:(BOOL)animated return; } - _textInputbarHidden = hidden; + _textInputbar.hidden = hidden; __weak typeof(self) weakSelf = self; @@ -916,7 +930,7 @@ - (void)slk_handlePanGestureRecognizer:(UIPanGestureRecognizer *)gesture static BOOL dragging = NO; static BOOL presenting = NO; - __block UIView *keyboardView = [self.textInputbar.inputAccessoryView keyboardViewProxy]; + __block UIView *keyboardView = [_textInputbar.inputAccessoryView keyboardViewProxy]; // When no keyboard view has been detecting, let's skip any handling. if (!keyboardView) { @@ -947,7 +961,7 @@ - (void)slk_handlePanGestureRecognizer:(UIPanGestureRecognizer *)gesture presenting = YES; #else - if ([gesture.view isEqual:self.textInputbar] && gestureVelocity.y < 0) { + if ([gesture.view isEqual:_textInputbar] && gestureVelocity.y < 0) { [self presentKeyboard:YES]; } return; @@ -965,7 +979,7 @@ - (void)slk_handlePanGestureRecognizer:(UIPanGestureRecognizer *)gesture [self presentKeyboard:NO]; // So we can capture the keyboard's view - keyboardView = [self.textInputbar.inputAccessoryView keyboardViewProxy]; + keyboardView = [_textInputbar.inputAccessoryView keyboardViewProxy]; originalFrame = keyboardView.frame; originalFrame.origin.y = CGRectGetMaxY(self.view.frame); @@ -979,7 +993,7 @@ - (void)slk_handlePanGestureRecognizer:(UIPanGestureRecognizer *)gesture } case UIGestureRecognizerStateChanged: { - if (CGRectContainsPoint(self.textInputbar.frame, gestureLocation) || dragging || presenting){ + if (CGRectContainsPoint(_textInputbar.frame, gestureLocation) || dragging || presenting){ if (CGPointEqualToPoint(startPoint, CGPointZero)) { startPoint = gestureLocation; @@ -1269,23 +1283,23 @@ - (void)slk_prepareForInterfaceTransitionWithDuration:(NSTimeInterval)duration #pragma mark - Keyboard Events -- (void)didPressReturnKey:(id)sender +- (void)didPressReturnKey:(UIKeyCommand *)keyCommand { - if (self.textInputbar.isEditing) { - [self didCommitTextEditing:sender]; + if (_textInputbar.isEditing) { + [self didCommitTextEditing:keyCommand]; } else { [self slk_performRightAction]; } } -- (void)didPressEscapeKey:(id)sender +- (void)didPressEscapeKey:(UIKeyCommand *)keyCommand { if (self.isAutoCompleting) { [self cancelAutoCompletion]; } - else if (self.textInputbar.isEditing) { - [self didCancelTextEditing:sender]; + else if (_textInputbar.isEditing) { + [self didCancelTextEditing:keyCommand]; } if ([self ignoreTextInputbarAdjustment] || ([self.textView isFirstResponder] && self.keyboardHC.constant == 0)) { @@ -1295,9 +1309,9 @@ - (void)didPressEscapeKey:(id)sender [self dismissKeyboard:YES]; } -- (void)didPressArrowKey:(id)sender +- (void)didPressArrowKey:(UIKeyCommand *)keyCommand { - [self.textView didPressAnyArrowKey:sender]; + [self.textView didPressArrowKey:keyCommand]; } @@ -1611,7 +1625,7 @@ - (void)showAutoCompletionView:(BOOL)show { // Reloads the tableview before showing/hiding if (show) { - [self.autoCompletionView reloadData]; + [_autoCompletionView reloadData]; } self.autoCompleting = show; @@ -1762,7 +1776,7 @@ - (void)slk_invalidateAutoCompletion _foundWord = nil; _foundPrefixRange = NSMakeRange(0, 0); - [self.autoCompletionView setContentOffset:CGPointZero]; + [_autoCompletionView setContentOffset:CGPointZero]; } - (void)slk_hideAutoCompletionViewIfNeeded @@ -2102,7 +2116,7 @@ - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView - (void)scrollViewDidScroll:(UIScrollView *)scrollView { - if ([scrollView isEqual:self.autoCompletionView]) { + if ([scrollView isEqual:_autoCompletionView]) { CGRect frame = self.autoCompletionHairline.frame; frame.origin.y = scrollView.contentOffset.y; self.autoCompletionHairline.frame = frame; @@ -2180,7 +2194,7 @@ - (void)slk_updateViewConstraints self.scrollViewHC.constant = [self slk_appropriateScrollViewHeight]; self.keyboardHC.constant = [self slk_appropriateKeyboardHeightFromRect:CGRectNull]; - if (self.textInputbar.isEditing) { + if (_textInputbar.isEditing) { self.textInputbarHC.constant += self.textInputbar.editorContentViewHeight; } @@ -2188,82 +2202,41 @@ - (void)slk_updateViewConstraints } -#pragma mark - External Keyboard Support +#pragma mark - Keyboard Command registration -- (NSArray *)keyCommands +- (void)slk_registerKeyCommands { - 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; -} + __weak typeof(self) weakSelf = self; -- (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 + // Enter Key + [self.textView observeKeyInput:@"\r" modifiers:0 title:NSLocalizedString(@"Send/Accept", nil) completion:^(UIKeyCommand *keyCommand) { + [weakSelf didPressReturnKey:keyCommand]; + }]; - return command; -} - -- (UIKeyCommand *)slk_escKeyCommand -{ - UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:UIKeyInputEscape modifierFlags:0 action:@selector(didPressEscapeKey:)]; + // Esc Key + [self.textView observeKeyInput:UIKeyInputEscape modifiers:0 title:NSLocalizedString(@"Dismiss", nil) completion:^(UIKeyCommand *keyCommand) { + [weakSelf didPressEscapeKey:keyCommand]; + }]; -#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 + // Up Arrow + [self.textView observeKeyInput:UIKeyInputUpArrow modifiers:0 title:nil completion:^(UIKeyCommand *keyCommand) { + [weakSelf didPressArrowKey:keyCommand]; + }]; - return command; + // Down Arrow + [self.textView observeKeyInput:UIKeyInputDownArrow modifiers:0 title:nil completion:^(UIKeyCommand *keyCommand) { + [weakSelf didPressArrowKey:keyCommand]; + }]; } -- (UIKeyCommand *)slk_arrowKeyCommand:(NSString *)inputUpArrow +- (NSArray *)keyCommands { - 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; + // Important to keep this in, for backwards compatibility. + return @[]; } -#pragma mark - NSNotificationCenter register/unregister +#pragma mark - NSNotificationCenter registration - (void)slk_registerNotifications { @@ -2376,6 +2349,8 @@ - (void)didReceiveMemoryWarning - (void)dealloc { + [self slk_unregisterNotifications]; + _tableView.delegate = nil; _tableView.dataSource = nil; _tableView = nil; @@ -2390,7 +2365,6 @@ - (void)dealloc _autoCompletionView.dataSource = nil; _autoCompletionView = nil; - _textInputbar.textView.delegate = nil; _textInputbar = nil; _textViewClass = nil; @@ -2408,8 +2382,6 @@ - (void)dealloc _typingIndicatorViewHC = nil; _autoCompletionViewHC = nil; _keyboardHC = nil; - - [self slk_unregisterNotifications]; } @end