[update: 2015/11/02]
6Sで追加されたForceTouchが使いやすいので自分は以下のコードは捨ててしまいましたが、備忘録として残しておきます。
テキストエディタ類を実装する際、ユーザーテストと称してUITextView内で入力作業を長く続けてみると、カーソル位置をタップ→ドラッグ操作で移動させるのが地味にツラいと感じた。
物理キーボードのように矢印キーで操作したい。
しかしiOSには文字以外のキー入力操作コマンドを送信するAPIがない。1
そこでinputAccessoryViewに上下左右の矢印キーボタンを追加してみた。
以下でUITextView内のキャレット位置を計算して移動させる実装を行っている。
- 左右はUITextPositionを1文字前後させればOK。
- 上下で若干悩んだが、キャレット位置での座標とUIFontを取得し、lineheightプロパティ分だけ垂直方向に移動させた座標から
closestPositionToPoint:
でUITextPositionを得る。
UIViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
//...
[self setUpCursorKeyboard];
}
- (void)setUpCursorKeyboard
{
UIToolbar *toolBar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
UIBarButtonItem *left = [[UIBarButtonItem alloc] initWithTitle:@" ← "
style:UIBarButtonItemStylePlain
target:self
action:@selector(simulateLeftKey)];
UIBarButtonItem *right = [[UIBarButtonItem alloc] initWithTitle:@" → "
style:UIBarButtonItemStylePlain
target:self
action:@selector(simulateRightKey)];
UIBarButtonItem *down = [[UIBarButtonItem alloc] initWithTitle:@" ↓ "
style:UIBarButtonItemStylePlain
target:self
action:@selector(simulateDownKey)];
UIBarButtonItem *up = [[UIBarButtonItem alloc] initWithTitle:@" ↑ "
style:UIBarButtonItemStylePlain
target:self
action:@selector(simulateUpKey)];
[toolBar setItems:@[left, up, down, right] animated:YES];
self.textView.inputAccessoryView = toolBar;
}
- (void)simulateLeftKey
{
UITextRange *currentRange = self.textView.selectedTextRange;
if (![currentRange.start isEqual:self.textView.beginningOfDocument]) {
UITextPosition *newPosition = [self.textView positionFromPosition:currentRange.start
offset:-1];
self.textView.selectedTextRange = [self.textView textRangeFromPosition:newPosition
toPosition:newPosition];
}
}
- (void)simulateRightKey
{
UITextRange *currentRange = self.textView.selectedTextRange;
if (![currentRange.end isEqual:self.textView.endOfDocument]) {
UITextPosition *newPosition = [self.textView positionFromPosition:currentRange.end
offset:1];
self.textView.selectedTextRange = [self.textView textRangeFromPosition:newPosition
toPosition:newPosition];
}
}
- (void)simulateUpKey
{
UITextRange *currentRange = self.textView.selectedTextRange;
UITextRange *allRange = [self.textView textRangeFromPosition:self.textView.beginningOfDocument
toPosition:self.textView.endOfDocument];
CGRect caretRect = [self.textView caretRectForPosition:currentRange.start];
NSDictionary *att = [self.textView textStylingAtPosition:currentRange.start
inDirection:UITextStorageDirectionForward];
UIFont *currentFont = att[NSFontAttributeName];
CGFloat nextY = caretRect.origin.y + (caretRect.size.height/2) - currentFont.lineHeight;
CGPoint nextCaretPoint = CGPointMake(caretRect.origin.x, nextY);
UITextPosition *newPosition = [self.textView closestPositionToPoint:nextCaretPoint
withinRange:allRange];
self.textView.selectedTextRange = [self.textView textRangeFromPosition:newPosition
toPosition:newPosition];
}
- (void)simulateDownKey
{
UITextRange *currentRange = self.textView.selectedTextRange;
UITextRange *allRange = [self.textView textRangeFromPosition:self.textView.beginningOfDocument
toPosition:self.textView.endOfDocument];
CGRect caretRect = [self.textView caretRectForPosition:currentRange.end];
NSDictionary *att = [self.textView textStylingAtPosition:currentRange.end
inDirection:UITextStorageDirectionForward];
UIFont *currentFont = att[NSFontAttributeName];
CGFloat nextY = caretRect.origin.y + (caretRect.size.height/2) + currentFont.lineHeight;
CGPoint nextCaretPoint = CGPointMake(caretRect.origin.x, nextY);
UITextPosition *newPosition = [self.textView closestPositionToPoint:nextCaretPoint
withinRange:allRange];
self.textView.selectedTextRange = [self.textView textRangeFromPosition:newPosition
toPosition:newPosition];
}
キャレットが範囲選択状態のときの挙動は人によって好みがあると思うので
if (!self.textView.selectedTextRange.empty) {}
の条件分岐を設けるなどして別処理にすると良いと思います。
-
OSXはNSEventでキー入力イベントを生成可能らしい ↩