¿Cómo deslizar automáticamente la ventana desde detrás del teclado cuando TextInput tiene el foco?

90

He visto este truco para que las aplicaciones nativas se desplacen automáticamente por la ventana, pero me pregunto cuál es la mejor manera de hacerlo en React Native ... Cuando un <TextInput>campo se enfoca y se posiciona bajo en la vista, el teclado cubrirá el campo de texto.

Puede ver este problema en la TextInputExample.jsvista de ejemplo de UIExplorer .

¿Alguien tiene una buena solución?

McG
fuente
3
Sugeriría agregar esto como un problema en el rastreador de Github y ver si surge algo, ya que esta será una queja muy común.
Colin Ramsay

Respuestas:

83

2017 respuesta

El KeyboardAvoidingViewes probablemente el mejor camino a seguir ahora. Consulte los documentos aquí . Es realmente simple en comparación con el Keyboardmódulo que le da al desarrollador más control para realizar animaciones. Spencer Carli demostró todas las formas posibles en su blog de medio .

Respuesta 2015

La forma correcta de hacer esto react-nativeno requiere bibliotecas externas, aprovecha el código nativo e incluye animaciones.

Primero defina una función que manejará el onFocusevento para cada uno TextInput(o cualquier otro componente al que le gustaría desplazarse):

// Scroll a component into view. Just pass the component ref string.
inputFocused (refName) {
  setTimeout(() => {
    let scrollResponder = this.refs.scrollView.getScrollResponder();
    scrollResponder.scrollResponderScrollNativeHandleToKeyboard(
      React.findNodeHandle(this.refs[refName]),
      110, //additionalOffset
      true
    );
  }, 50);
}

Luego, en tu función de render:

render () {
  return (
    <ScrollView ref='scrollView'>
        <TextInput ref='username' 
                   onFocus={this.inputFocused.bind(this, 'username')}
    </ScrollView>
  )
}

Esto usa RCTDeviceEventEmitterpara eventos de teclado y dimensionamiento, mide la posición del componente usando RCTUIManager.measureLayouty calcula el movimiento de desplazamiento exacto requerido en scrollResponderInputMeasureAndScrollToKeyboard.

Es posible que desee jugar con el additionalOffsetparámetro para adaptarse a las necesidades de su diseño de interfaz de usuario específico.

Sherlock
fuente
5
Este es un buen hallazgo, pero para mí no fue suficiente, porque mientras ScrollView se asegurará de que TextInput esté en la pantalla, ScrollView todavía mostraba contenido debajo del teclado al que el usuario no podía desplazarse. Establecer la propiedad ScrollView "keyboardDismissMode = on-drag" permite al usuario descartar el teclado, pero si no hay suficiente contenido de desplazamiento debajo del teclado, la experiencia es un poco discordante. Si ScrollView solo tiene que desplazarse debido al teclado en primer lugar, y deshabilita el rebote, parece que no hay forma de descartar el teclado y mostrar el contenido a continuación
miracle2k
2
@ miracle2k - Tengo una función que restablece la posición de la vista de desplazamiento cuando una entrada está borrosa, es decir, cuando se cierra el teclado. ¿Quizás esto podría ayudar en tu caso?
Sherlock
2
@Sherlock ¿Cómo se ve esa función de restablecimiento de vista de desplazamiento borrosa? Por cierto, una solución impresionante :)
Ryan McDermott
8
En las versiones más recientes de React Native, deberá llamar a: * importar ReactNative desde 'react-native'; * antes de llamar * ReactNative.findNodeHandle () * De lo contrario, la aplicación se bloqueará
amirfl
6
Ahora import {findNodeHandle} from 'react-native' stackoverflow.com/questions/37626851/…
antoine129
26

Facebook de código abierto KeyboardAvoidingView en react native 0.29 para resolver este problema. Puede encontrar ejemplos de documentación y uso aquí .

farwayer
fuente
32
Tenga cuidado con KeyboardAvoidingView, simplemente no es fácil de usar. No siempre se comporta como esperas. La documentación es prácticamente inexistente.
Renato Back
doc y el comportamiento están mejorando ahora
antoine129
El problema que tengo es que KeyboardAvoidingView mide la altura del teclado como 65 en mi simulador de iPhone 6, por lo que mi vista sigue oculta detrás del teclado.
Marc
La única forma en que pude manejarlo fue a través de un enfoque de acolchado inferior activado porDeviceEventEmitter.addListener('keyboardDidShow', this.keyboardDidShow.bind(this));
Marc
12

Combinamos parte del formato de código react-native-keyboard-spacer y el código de @Sherlock para crear un componente KeyboardHandler que se puede envolver alrededor de cualquier Vista con elementos TextInput. ¡Funciona de maravilla! :-)

/**
 * Handle resizing enclosed View and scrolling to input
 * Usage:
 *    <KeyboardHandler ref='kh' offset={50}>
 *      <View>
 *        ...
 *        <TextInput ref='username'
 *          onFocus={()=>this.refs.kh.inputFocused(this,'username')}/>
 *        ...
 *      </View>
 *    </KeyboardHandler>
 * 
 *  offset is optional and defaults to 34
 *  Any other specified props will be passed on to ScrollView
 */
'use strict';

var React=require('react-native');
var {
  ScrollView,
  View,
  DeviceEventEmitter,
}=React;


var myprops={ 
  offset:34,
}
var KeyboardHandler=React.createClass({
  propTypes:{
    offset: React.PropTypes.number,
  },
  getDefaultProps(){
    return myprops;
  },
  getInitialState(){
    DeviceEventEmitter.addListener('keyboardDidShow',(frames)=>{
      if (!frames.endCoordinates) return;
      this.setState({keyboardSpace: frames.endCoordinates.height});
    });
    DeviceEventEmitter.addListener('keyboardWillHide',(frames)=>{
      this.setState({keyboardSpace:0});
    });

    this.scrollviewProps={
      automaticallyAdjustContentInsets:true,
      scrollEventThrottle:200,
    };
    // pass on any props we don't own to ScrollView
    Object.keys(this.props).filter((n)=>{return n!='children'})
    .forEach((e)=>{if(!myprops[e])this.scrollviewProps[e]=this.props[e]});

    return {
      keyboardSpace:0,
    };
  },
  render(){
    return (
      <ScrollView ref='scrollView' {...this.scrollviewProps}>
        {this.props.children}
        <View style={{height:this.state.keyboardSpace}}></View>
      </ScrollView>
    );
  },
  inputFocused(_this,refName){
    setTimeout(()=>{
      let scrollResponder=this.refs.scrollView.getScrollResponder();
      scrollResponder.scrollResponderScrollNativeHandleToKeyboard(
        React.findNodeHandle(_this.refs[refName]),
        this.props.offset, //additionalOffset
        true
      );
    }, 50);
  }
}) // KeyboardHandler

module.exports=KeyboardHandler;
John kendall
fuente
¿Algo fácil / obvio que impida que el teclado se muestre en un simulador de iOS?
Seigel
1
¿Probaste Command + K (Hardware-> Keyboard-> Toggle Software Keboard)?
John kendall
Pruebe la versión modificada de esto aquí: gist.github.com/dbasedow/f5713763802e27fbde3fc57a600adcd3 Creo que esto es mejor porque no depende de ningún tiempo de espera que creo que es frágil en mi opinión.
CoderDave
10

Primero debe instalar react-native-keyboardevents .

  1. En XCode, en el navegador de proyectos, haga clic derecho en Bibliotecas ➜ Agregar archivos a [el nombre de su proyecto] Vaya a node_modules ➜ react-native-keyboardevents y agregue el archivo .xcodeproj
  2. En XCode, en el navegador de proyectos, seleccione su proyecto. Agregue el archivo lib * .a del proyecto keyboardevents a las Fases de compilación de su proyecto ➜ Vincular binario con bibliotecas Haga clic en el archivo .xcodeproj que agregó antes en el navegador del proyecto y vaya a la pestaña Configuración de compilación. Asegúrese de que 'Todo' esté activado (en lugar de 'Básico'). Busque rutas de búsqueda de encabezado y asegúrese de que contenga $ (SRCROOT) /../ react-native / React y $ (SRCROOT) /../../ React: marque ambos como recursivos.
  3. Ejecute su proyecto (Cmd + R)

Luego, de vuelta en la tierra de JavaScript:

Necesita importar los eventos react-native-keyboar.

var KeyboardEvents = require('react-native-keyboardevents');
var KeyboardEventEmitter = KeyboardEvents.Emitter;

Luego, en su vista, agregue algún estado para el espacio del teclado y actualice escuchando los eventos del teclado.

  getInitialState: function() {
    KeyboardEventEmitter.on(KeyboardEvents.KeyboardDidShowEvent, (frames) => {
      this.setState({keyboardSpace: frames.end.height});
    });
    KeyboardEventEmitter.on(KeyboardEvents.KeyboardWillHideEvent, (frames) => {
      this.setState({keyboardSpace: 0});
    });

    return {
      keyboardSpace: 0,
    };
  },

Finalmente, agregue un espaciador a su función de renderizado debajo de todo para que cuando aumente el tamaño, resalte sus cosas.

<View style={{height: this.state.keyboardSpace}}></View>

También es posible usar la API de animación, pero por simplicidad simplemente ajustamos después de la animación.

Brysgo
fuente
1
Sería increíble ver un ejemplo de código / más información sobre cómo hacer la animación. El salto es bastante disparatado, y trabajando solo con los métodos "mostrará" y "mostró", no puedo imaginar cómo adivinar cuánto tiempo será la animación del teclado o qué tan alto es desde "mostrar".
Stephen
2
[email protected] ahora envía eventos de teclado (por ejemplo, "keyboardWillShow") a través del DeviceEventEmitter, para que pueda registrar oyentes para estos eventos. Sin embargo, al tratar con ListView, descubrí que llamar a scrollTo () en la vista de desplazamiento de ListView funcionaba mejor: this.listView.getScrollResponder().scrollTo(rowID * rowHeight); esto se llama en TextInput de una fila cuando recibe un evento onFocus.
Jed Lau
4
Esta respuesta ya no es válida, ya que RCTDeviceEventEmitter hace el trabajo.
Sherlock
6

Prueba esto:

import React, {
  DeviceEventEmitter,
  Dimensions
} from 'react-native';

...

getInitialState: function() {
  return {
    visibleHeight: Dimensions.get('window').height
  }
},

...

componentDidMount: function() {
  let self = this;

  DeviceEventEmitter.addListener('keyboardWillShow', function(e: Event) {
    self.keyboardWillShow(e);
  });

  DeviceEventEmitter.addListener('keyboardWillHide', function(e: Event) {
      self.keyboardWillHide(e);
  });
}

...

keyboardWillShow (e) {
  let newSize = Dimensions.get('window').height - e.endCoordinates.height;
  this.setState({visibleHeight: newSize});
},

keyboardWillHide (e) {
  this.setState({visibleHeight: Dimensions.get('window').height});
},

...

render: function() {
  return (<View style={{height: this.state.visibleHeight}}>your view code here...</View>);
}

...

Funcionó para mí. La vista básicamente se reduce cuando se muestra el teclado y vuelve a crecer cuando está oculto.

pomo
fuente
Además, esta solución funciona bien (RN 0.21.0) stackoverflow.com/a/35874233/3346628
pomo
use this.keyboardWillHide.bind (this) en lugar de self
animekun
Utilice el teclado en lugar de DeviceEventEmitter
Madura Pradeep
4

Quizás sea demasiado tarde, pero la mejor solución es usar una biblioteca nativa, IQKeyboardManager

Simplemente arrastre y suelte el directorio IQKeyboardManager desde el proyecto de demostración a su proyecto de iOS. Eso es. También puede configurar algunos valores, ya que está habilitada la barra de herramientas, o el espacio entre la entrada de texto y el teclado en el archivo AppDelegate.m. Más detalles sobre la personalización se encuentran en el enlace de la página de GitHub que agregué.

Adrian Zghibarta
fuente
1
Esta es una excelente opción. Consulte también github.com/douglasjunior/react-native-keyboard-manager para obtener una versión envuelta para ReactNative: es fácil de instalar.
loevborg
3

Usé TextInput.onFocus y ScrollView.scrollTo.

...
<ScrollView ref="scrollView">
...
<TextInput onFocus={this.scrolldown}>
...
scrolldown: function(){
  this.refs.scrollView.scrollTo(width*2/3);
},
shohey1226
fuente
2

@Stephen

Si no le importa que la altura no se anime exactamente a la misma velocidad a la que aparece el teclado, puede usar LayoutAnimation, para que al menos la altura no salte a su lugar. p.ej

importa LayoutAnimation desde react-native y agrega los siguientes métodos a tu componente.

getInitialState: function() {
    return {keyboardSpace: 0};
  },
   updateKeyboardSpace: function(frames) {
    LayoutAnimation.configureNext(animations.layout.spring);
    this.setState({keyboardSpace: frames.end.height});
  },

  resetKeyboardSpace: function() {
    LayoutAnimation.configureNext(animations.layout.spring);
    this.setState({keyboardSpace: 0});
  },

  componentDidMount: function() {
    KeyboardEventEmitter.on(KeyboardEvents.KeyboardDidShowEvent, this.updateKeyboardSpace);
    KeyboardEventEmitter.on(KeyboardEvents.KeyboardWillHideEvent, this.resetKeyboardSpace);
  },

  componentWillUnmount: function() {
    KeyboardEventEmitter.off(KeyboardEvents.KeyboardDidShowEvent, this.updateKeyboardSpace);
    KeyboardEventEmitter.off(KeyboardEvents.KeyboardWillHideEvent, this.resetKeyboardSpace);
  },

Algunas animaciones de ejemplo son (estoy usando la primavera de arriba):

var animations = {
  layout: {
    spring: {
      duration: 400,
      create: {
        duration: 300,
        type: LayoutAnimation.Types.easeInEaseOut,
        property: LayoutAnimation.Properties.opacity,
      },
      update: {
        type: LayoutAnimation.Types.spring,
        springDamping: 400,
      },
    },
    easeInEaseOut: {
      duration: 400,
      create: {
        type: LayoutAnimation.Types.easeInEaseOut,
        property: LayoutAnimation.Properties.scaleXY,
      },
      update: {
        type: LayoutAnimation.Types.easeInEaseOut,
      },
    },
  },
};

ACTUALIZAR:

Vea la respuesta de @ sherlock a continuación, a partir de react-native 0.11, el cambio de tamaño del teclado se puede resolver utilizando la funcionalidad incorporada.

Deanmcpherson
fuente
2

Puede combinar algunos de los métodos en algo un poco más simple.

Adjunte un oyente onFocus a sus entradas

<TextInput ref="password" secureTextEntry={true} 
           onFocus={this.scrolldown.bind(this,'password')}
/>

Nuestro método de desplazamiento hacia abajo se parece a:

scrolldown(ref) {
    const self = this;
    this.refs[ref].measure((ox, oy, width, height, px, py) => {
        self.refs.scrollView.scrollTo({y: oy - 200});
    });
}

Esto le dice a nuestra vista de desplazamiento (recuerde agregar una referencia) que se desplace hacia abajo hasta la posición de nuestra entrada enfocada - 200 (es aproximadamente del tamaño del teclado)

componentWillMount() {
    this.keyboardDidHideListener = Keyboard.addListener(
      'keyboardWillHide', 
      this.keyboardDidHide.bind(this)
    )
}

componentWillUnmount() {
    this.keyboardDidHideListener.remove()
}

keyboardDidHide(e) {
    this.refs.scrollView.scrollTo({y: 0});
}

Aquí reiniciamos nuestra vista de desplazamiento hacia arriba,

ingrese la descripción de la imagen aquí

Nath
fuente
2
@ ¿Podría proporcionar su método render ()?
valerybodak
0

Estoy usando un método más simple, pero aún no está animado. Tengo un estado de componente llamado "bumpedUp" que por defecto es 0, pero lo establezco en 1 cuando el textInput se enfoca, así:

En mi entrada de texto:

onFocus={() => this.setState({bumpedUp: 1})}
onEndEditing={() => this.setState({bumpedUp: 0})}

También tengo un estilo que le da al contenedor de envoltura de todo en esa pantalla un margen inferior y un margen superior negativo, como este:

mythingscontainer: {
  flex: 1,
  justifyContent: "center",
  alignItems: "center",
  flexDirection: "column",
},
bumpedcontainer: {
  marginBottom: 210,
  marginTop: -210,
},

Y luego, en el contenedor de envoltura, configuro los estilos de esta manera:

<View style={[styles.mythingscontainer, this.state.bumpedUp && styles.bumpedcontainer]}>

Por lo tanto, cuando el estado "bumpedUp" se establece en 1, el estilo bumpedcontainer se activa y mueve el contenido hacia arriba.

Un poco hacky y los márgenes están codificados, pero funciona :)

Stirman
fuente
0

Utilizo la respuesta brysgo para subir la parte inferior de mi vista de desplazamiento. Luego utilizo onScroll para actualizar la posición actual de la vista de desplazamiento. Luego encontré este React Native: Obtener la posición de un elemento para obtener la posición del texto de entrada. Luego hago algunas matemáticas simples para averiguar si la entrada está en la vista actual. Luego uso scrollTo para mover la cantidad mínima más un margen. Es bastante suave. Aquí está el código para la parte de desplazamiento:

            focusOn: function(target) {
                return () => {
                    var handle = React.findNodeHandle(this.refs[target]);
                    UIManager.measureLayoutRelativeToParent( handle, 
                        (e) => {console.error(e)}, 
                        (x,y,w,h) => {
                            var offs = this.scrollPosition + 250;
                            var subHeaderHeight = (Sizes.width > 320) ? Sizes.height * 0.067 : Sizes.height * 0.077;
                            var headerHeight = Sizes.height / 9;
                            var largeSpace = (Sizes.height - (subHeaderHeight + headerHeight));
                            var shortSpace = largeSpace - this.keyboardOffset;
                            if(y+h >= this.scrollPosition + shortSpace) {
                                this.refs.sv.scrollTo(y+h - shortSpace + 20);
                            }
                            if(y < this.scrollPosition) this.refs.sv.scrollTo(this.scrollPosition - (this.scrollPosition-y) - 20 );
                        }
                     );
                };
            },
no más
fuente