Skip to content
This repository has been archived by the owner on Oct 30, 2018. It is now read-only.

Commit

Permalink
Moves all key command implementation back to SLKTextView, introducing…
Browse files Browse the repository at this point in the history
… a totally new approach for better key input observation using completion blocks.

This finally fixes a strange retain cycle caused by interacting with key commands in SLKTextViewController.
  • Loading branch information
Ignacio Romero Zurbuchen committed Feb 13, 2016
1 parent 80281f6 commit 5c27485
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 110 deletions.
10 changes: 5 additions & 5 deletions Examples/Messenger-Shared/MessageViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,7 @@ - (void)configureActionItems
target:self
action:@selector(togglePIPWindow:)];

self.navigationItem.leftBarButtonItems = @[arrowItem];
self.navigationItem.rightBarButtonItems = @[pipItem, editItem, appendItem, typeItem];
self.navigationItem.rightBarButtonItems = @[arrowItem, pipItem, editItem, appendItem, typeItem];
}


Expand Down Expand Up @@ -425,13 +424,14 @@ - (void)didPressRightButton:(id)sender

- (void)didPressArrowKey:(id)sender
{
[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:sender];
}
}

- (NSString *)keyForTextCaching
Expand Down
2 changes: 1 addition & 1 deletion Source/SLKTextInputbar.m
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,7 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N
}


#pragma mark - NSNotificationCenter register/unregister
#pragma mark - NSNotificationCenter registration

- (void)slk_registerNotifications
{
Expand Down
16 changes: 15 additions & 1 deletion Source/SLKTextView.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)didPressAnyArrowKey:(UIKeyCommand *)keyCommand;


#pragma mark - Markdown Formatting
Expand All @@ -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


Expand Down
100 changes: 54 additions & 46 deletions Source/SLKTextView.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -916,68 +917,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)didPressAnyArrowKey:(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];
}
Expand Down Expand Up @@ -1070,7 +1073,7 @@ - (BOOL)slk_isNewVerticalMovementForPosition:(UITextPosition *)position inDirect
}


#pragma mark - NSNotificationCenter register/unregister
#pragma mark - NSNotificationCenter registration

- (void)slk_registerNotifications
{
Expand Down Expand Up @@ -1104,6 +1107,11 @@ - (void)dealloc
[self removeObserver:self forKeyPath:NSStringFromSelector(@selector(contentSize))];

_placeholderLabel = nil;

_registeredFormattingTitles = nil;
_registeredFormattingSymbols = nil;
_registeredKeyCommands = nil;
_registeredKeyCallbacks = nil;
}

@end
82 changes: 25 additions & 57 deletions Source/SLKTextViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,6 @@ @interface SLKTextViewController ()
// 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;

// The keyboard commands available for external keyboards
@property (nonatomic, strong) NSArray *keyboardCommands;

// Optional classes to be used instead of the default ones.
@property (nonatomic, strong) Class textViewClass;
@property (nonatomic, strong) Class typingIndicatorViewClass;
Expand Down Expand Up @@ -187,6 +184,8 @@ - (void)viewDidLoad
[self.view addSubview:self.textInputbar];

[self slk_setupViewConstraints];

[self slk_registerKeyCommands];
}

- (void)viewWillAppear:(BOOL)animated
Expand Down Expand Up @@ -2202,72 +2201,41 @@ - (void)slk_updateViewConstraints
}


#pragma mark - External Keyboard Support
#pragma mark - Keyboard Command registration

- (NSArray *)keyCommands
- (void)slk_registerKeyCommands
{
if (_keyboardCommands) {
return _keyboardCommands;
}

_keyboardCommands = @[[self slk_returnKeyCommand],
[self slk_escKeyCommand],
[self slk_arrowKeyCommand:UIKeyInputUpArrow],
[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
command.discoverabilityTitle = [self.rightButton titleForState:UIControlStateNormal] ? : NSLocalizedString(@"Send/Accept", 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
command.discoverabilityTitle = NSLocalizedString(@"Dismiss", 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
{
Expand Down

0 comments on commit 5c27485

Please sign in to comment.