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

Commit

Permalink
Adds up/down caret movement with external keyboard. Moves text shortc…
Browse files Browse the repository at this point in the history
…uts to textView subclass. Minor code refactoring.
  • Loading branch information
dzenbot committed Oct 11, 2014
1 parent 940eede commit 764aa2e
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 61 deletions.
2 changes: 1 addition & 1 deletion Source/Additions/UITextView+SLKAdditions.m
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ - (void)slk_insertNewLineBreak
}

//Detected break. Should scroll to bottom if needed.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.0025 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.0125 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self slk_scrollToBottomAnimated:animated];
});
}
Expand Down
183 changes: 177 additions & 6 deletions Source/Classes/SLKTextView.m
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,21 @@
NSString * const SLKTextViewDidShakeNotification = @"com.slack.TextViewController.TextView.DidShake";

@interface SLKTextView ()
{
BOOL _didFlashScrollIndicators;
}

// The label used as placeholder
@property (nonatomic, strong) UILabel *placeholderLabel;

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

// Used for moving the care up/down
@property (nonatomic) UITextLayoutDirection verticalMoveDirection;
@property (nonatomic) CGRect verticalMoveStartCaretRect;
@property (nonatomic) CGRect verticalMoveLastCaretRect;

// Used for detecting if the scroll indicator was previously flashed
@property (nonatomic) BOOL didFlashScrollIndicators;

@end

@implementation SLKTextView
Expand Down Expand Up @@ -225,6 +236,11 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
return NO;
}

if ((action == @selector(copy:) || action == @selector(cut:))
&& self.selectedRange.length > 0) {
return YES;
}

if (action == @selector(paste:) && [self pasteboardItem]) {
return YES;
}
Expand Down Expand Up @@ -362,13 +378,168 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N
}


#pragma mark - Motion
#pragma mark - Motion Events

- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (event.type == UIEventTypeMotion && event.subtype == UIEventSubtypeMotionShake) {
[[NSNotificationCenter defaultCenter] postNotificationName:SLKTextViewDidShakeNotification object:self];
}
[[NSNotificationCenter defaultCenter] postNotificationName:SLKTextViewDidShakeNotification object:self];
}
}


#pragma mark - External Keyboard Support

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

_keyboardCommands = @[
// Return
[UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:UIKeyModifierShift action:@selector(didPressLineBreakKeys:)],
[UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:UIKeyModifierAlternate action:@selector(didPressLineBreakKeys:)],
[UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:UIKeyModifierControl action:@selector(didPressLineBreakKeys:)],

// Undo/Redo
[UIKeyCommand keyCommandWithInput:@"z" modifierFlags:UIKeyModifierCommand action:@selector(didPressCommandZKeys:)],
[UIKeyCommand keyCommandWithInput:@"z" modifierFlags:UIKeyModifierShift|UIKeyModifierCommand action:@selector(didPressCommandZKeys:)],

// Up/Down
[UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow modifierFlags:0 action:@selector(didPressArrowKey:)],
[UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow modifierFlags:0 action:@selector(didPressArrowKey:)]
];

return _keyboardCommands;
}


#pragma mark Line Break

- (void)didPressLineBreakKeys:(id)sender
{
[self slk_insertNewLineBreak];
}

#pragma mark Undo/Redo Text

- (void)didPressCommandZKeys:(id)sender
{
UIKeyCommand *keyCommand = (UIKeyCommand *)sender;

if ((keyCommand.modifierFlags & UIKeyModifierShift) > 0) {

if ([self.undoManager canRedo]) {
[self.undoManager redo];
}
}
else if ([self.undoManager canUndo]) {
[self.undoManager undo];
}
}

#pragma mark Up/Down Cursor Movement

- (void)didPressArrowKey:(id)sender
{
if (self.text.length == 0 || self.numberOfLines < 2) {
return;
}

UIKeyCommand *keyCommand = (UIKeyCommand *)sender;

if ([keyCommand.input isEqualToString:UIKeyInputUpArrow]) {
[self moveCursorTodirection:UITextLayoutDirectionUp];
}
else {
[self moveCursorTodirection:UITextLayoutDirectionDown];
}
}

// Based on code from Ruben Cabaco
// https://gist.github.com/rcabaco/6765778
//UITextPosition *p0 = (direction = UITextLayoutDirectionUp) ? self.selectedTextRange.start : self.selectedTextRange.end;

- (void)moveCursorTodirection:(UITextLayoutDirection)direction
{
UITextPosition *start = (direction == UITextLayoutDirectionUp) ? self.selectedTextRange.start : self.selectedTextRange.end;

if ([self isNewVerticalMovementForPosition:start inDirection:direction]) {
self.verticalMoveDirection = direction;
self.verticalMoveStartCaretRect = [self caretRectForPosition:start];
}

if (start) {

UITextPosition *end = [self closestPositionToPosition:start inDirection:direction];

if (end) {
self.verticalMoveLastCaretRect = [self caretRectForPosition:end];
self.selectedTextRange = [self textRangeFromPosition:end toPosition:end];

[self slk_scrollToCaretPositonAnimated:NO];
}
}
}

- (UITextPosition *)closestPositionToPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction
{
// Currently only up and down are implemented.
NSParameterAssert(direction == UITextLayoutDirectionUp || direction == UITextLayoutDirectionDown);

// Translate the vertical direction to a horizontal direction.
UITextLayoutDirection lookupDirection = (direction == UITextLayoutDirectionUp) ? UITextLayoutDirectionLeft : UITextLayoutDirectionRight;

// Walk one character at a time in `lookupDirection` until the next line is reached.
UITextPosition *checkPosition = position;
UITextPosition *closestPosition = position;
CGRect startingCaretRect = [self caretRectForPosition:position];
CGRect nextLineCaretRect;
BOOL isInNextLine = NO;

while (YES) {
UITextPosition *nextPosition = [self positionFromPosition:checkPosition inDirection:lookupDirection offset:1];

// End of line.
if (!nextPosition || [self comparePosition:checkPosition toPosition:nextPosition] == NSOrderedSame) {
break;
}

checkPosition = nextPosition;
CGRect checkRect = [self caretRectForPosition:checkPosition];
if (CGRectGetMidY(startingCaretRect) != CGRectGetMidY(checkRect)) {
// While on the next line stop just above/below the starting position.
if (lookupDirection == UITextLayoutDirectionLeft && CGRectGetMidX(checkRect) <= CGRectGetMidX(self.verticalMoveStartCaretRect)) {
closestPosition = checkPosition;
break;
}
if (lookupDirection == UITextLayoutDirectionRight && CGRectGetMidX(checkRect) >= CGRectGetMidX(self.verticalMoveStartCaretRect)) {
closestPosition = checkPosition;
break;
}
// But don't skip lines.
if (isInNextLine && CGRectGetMidY(checkRect) != CGRectGetMidY(nextLineCaretRect)) {
break;
}

isInNextLine = YES;
nextLineCaretRect = checkRect;
closestPosition = checkPosition;
}
}
return closestPosition;
}

- (BOOL)isNewVerticalMovementForPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction
{
CGRect caretRect = [self caretRectForPosition:position];
BOOL noPreviousStartPosition = CGRectEqualToRect(self.verticalMoveStartCaretRect, CGRectZero);
BOOL caretMovedSinceLastPosition = !CGRectEqualToRect(caretRect, self.verticalMoveLastCaretRect);
BOOL directionChanged = self.verticalMoveDirection != direction;

BOOL newMovement = noPreviousStartPosition || caretMovedSinceLastPosition || directionChanged;
return newMovement;
}


Expand Down
64 changes: 10 additions & 54 deletions Source/Classes/SLKTextViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -781,11 +781,6 @@ - (void)performRightAction
}
}

- (void)insertNewLineBreak
{
[self.textView slk_insertNewLineBreak];
}

- (void)prepareForInterfaceRotation
{
[self.view layoutIfNeeded];
Expand Down Expand Up @@ -826,21 +821,6 @@ - (void)didPressEscapeKey:(id)sender
[self dismissKeyboard:YES];
}

- (void)didPressCommandZKeys:(id)sender
{
UIKeyCommand *keyComamnd = (UIKeyCommand *)sender;

if ((keyComamnd.modifierFlags & UIKeyModifierShift) > 0) {

if ([self.textView.undoManager canRedo]) {
[self.textView.undoManager redo];
}
}
else if ([self.textView.undoManager canUndo]) {
[self.textView.undoManager undo];
}
}


#pragma mark - Notification Events

Expand Down Expand Up @@ -1292,44 +1272,20 @@ - (void)setupViewConstraints

- (NSArray *)keyCommands
{
if (!_keyboardCommands) {
_keyboardCommands = [[NSArray alloc] initWithArray:[self keySet]];
if (_keyboardCommands) {
return _keyboardCommands;
}

_keyboardCommands = @[
// Pressing Return key
[UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:0 action:@selector(didPressReturnKey:)],
// Pressing Esc key
[UIKeyCommand keyCommandWithInput:UIKeyInputEscape modifierFlags:0 action:@selector(didPressEscapeKey:)]
];

return _keyboardCommands;
}

- (NSArray *)keySet
{
return @[
// Pressing Return key
[UIKeyCommand keyCommandWithInput:@"\r"
modifierFlags:0
action:@selector(didPressReturnKey:)],
[UIKeyCommand keyCommandWithInput:@"\r"
modifierFlags:UIKeyModifierShift
action:@selector(insertNewLineBreak)],
[UIKeyCommand keyCommandWithInput:@"\r"
modifierFlags:UIKeyModifierAlternate
action:@selector(insertNewLineBreak)],
[UIKeyCommand keyCommandWithInput:@"\r"
modifierFlags:UIKeyModifierControl
action:@selector(insertNewLineBreak)],

// Undo/Redo
[UIKeyCommand keyCommandWithInput:@"z"
modifierFlags:UIKeyModifierCommand
action:@selector(didPressCommandZKeys:)],
[UIKeyCommand keyCommandWithInput:@"z"
modifierFlags:UIKeyModifierShift|UIKeyModifierCommand
action:@selector(didPressCommandZKeys:)],

// Pressing Esc key
[UIKeyCommand keyCommandWithInput:UIKeyInputEscape
modifierFlags:0
action:@selector(didPressEscapeKey:)]
];
}


#pragma mark - NSNotificationCenter register/unregister

Expand Down

0 comments on commit 764aa2e

Please sign in to comment.