DantheMan
DantheMan

Reputation: 7417

Getting Cursor Position on TextInput React-Native

Does anyone have any ideas on how to get the caret position of a TextInput? I tried onSelectionChange and creating an event emitter from DocumentSelectionState but neither appear to be working (they don't fire anything, no matter what I select).

For Example: https://rnplay.org/apps/eZnvIA

Upvotes: 20

Views: 20473

Answers (5)

Nick Grealy
Nick Grealy

Reputation: 25864

If you're willing to get semi-adventurous, you can update the react-native library to allow retrieving the cursor (x/y) coordinates (relative to the TextInput), in the onSelectionChange event parameter.

Full instructions for IOS AND Android. (Disclaimer: I did not come up with this solution - @Palisand did)

TLDR - IOS + ReactNative 0.49.1

node_modules/react-native/Libraries/Text/RCTTextSelection.h

/**
 * Object containing information about a TextInput's selection.
 */
@interface RCTTextSelection : NSObject

@property (nonatomic, assign, readonly) NSInteger start;
@property (nonatomic, assign, readonly) NSInteger end;
/* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */
@property (nonatomic, assign, readonly) CGPoint cursorPosition;

- (instancetype)initWithStart:(NSInteger)start end:(NSInteger)end cursorPosition:(CGPoint)cursorPosition;
/* <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */

@end

node_modules/react-native/Libraries/Text/RCTTextSelection.m

@implementation RCTTextSelection
  
    /* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */
- (instancetype)initWithStart:(NSInteger)start end:(NSInteger)end cursorPosition:(CGPoint)cursorPosition
    /* <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */
{
  if (self = [super init]) {
    _start = start;
    _end = end;
    /* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */
    _cursorPosition = cursorPosition;
    /* <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */
  }
  return self;
}

@end

@implementation RCTConvert (RCTTextSelection)

+ (RCTTextSelection *)RCTTextSelection:(id)json
{
  if ([json isKindOfClass:[NSDictionary class]]) {
    NSInteger start = [self NSInteger:json[@"start"]];
    NSInteger end = [self NSInteger:json[@"end"]];
    /* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */
    CGPoint cursorPosition = CGPointMake(
      [self CGFloat:json[@"cursorPositionX"]],
      [self CGFloat:json[@"cursorPositionY"]]
    );
    return [[RCTTextSelection alloc] initWithStart:start
                                               end:end 
                                    cursorPosition:cursorPosition];
    /* <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */
  }

  return nil;
}

@end

node_modules/react-native/Libraries/Text/RCTTextInput.m

- (RCTTextSelection *)selection
{
  id<RCTBackedTextInputViewProtocol> backedTextInput = self.backedTextInputView;

  UITextRange *selectedTextRange = backedTextInput.selectedTextRange;
  return [[RCTTextSelection new] initWithStart:[backedTextInput offsetFromPosition:backedTextInput.beginningOfDocument toPosition:selectedTextRange.start]
                                           end:[backedTextInput offsetFromPosition:backedTextInput.beginningOfDocument toPosition:selectedTextRange.end]
      /* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */
                                cursorPosition:[backedTextInput caretRectForPosition:selectedTextRange.start].origin];
      /* <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */
}

...
  
- (void)textInputDidChangeSelection
{
  if (!_onSelectionChange) {
    return;
  }

  RCTTextSelection *selection = self.selection;
  _onSelectionChange(@{
    @"selection": @{
      @"start": @(selection.start),
      @"end": @(selection.end),
      /* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */
      @"cursorPositionX": @(selection.cursorPosition.x),
      @"cursorPositionY": @(selection.cursorPosition.y)
      /* <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */
    },
  });
}

Upvotes: 1

Andrei Bacescu
Andrei Bacescu

Reputation: 649

In order to get the position of the cursor the only thing you need is onSelectionChange https://reactnative.dev/docs/textinput#onselectionchange. The function will return the start and the end values.

Upvotes: 2

Esmaeil
Esmaeil

Reputation: 658

In react-native <=v0.56 following code get the selection as an object of {state: number, end: number}. The text positions start from 0. For example selection like {start: 0, end: 0} can happen and it means your curser is placed before first character of you text and nothing selected. Another selection type happens when start and end is not equal, like {start: 0, end: 10}, this means you select first 10 character of your text.

By the way as following issue if you use react native v0.57.0 or v0.57.1 in the textInput must be add multiline={true} to everything work properly.

import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View, TextInput} from 'react-native';

type Props = {};
export default class App extends Component<Props> {

    constructor(props) {
        super(props)
        this.state = {
        selection: {start: 0, end: 0},
        text: 'This is a sample text',
    }
}
render() {
 return (
    <View style={styles.container}>
      <Text>{this.state.selection.start}, {this.state.selection.end}</Text>
      <TextInput
        style={{ width: 300, borderColor: 'gray', borderWidth: 1 }}
        onSelectionChange={({ nativeEvent: { selection } }) => {
          this.setState({ selection })
        }}
        onChangeText={(text) => this.setState({ text })}
        value={this.state.text}
      />
    </View>
    );
    }
}

The result is:

enter image description here

Upvotes: 5

Maksim
Maksim

Reputation: 2466

onSelectionChange={(event) => console.log(event.nativeEvent.selection)}

Upvotes: 31

NICCAI
NICCAI

Reputation: 3238

As a start, you probably want to use the onChange or even onChangeText event instead. That will get your alert firing.

Upvotes: -5

Related Questions