Cómo descartar el teclado con react-navigation en las aplicaciones React Native

Mostrar y descartar el teclado parece algo trivial en las aplicaciones móviles, pero puede ser complicado descartarlo automáticamente cuando se combina con la navegación por reacción y la presentación modal. Al menos eso está de acuerdo con mi suposición inicial. Este artículo tiene como objetivo detallar lo que he aprendido sobre el manejo del teclado y cómo evitar un toque adicional cuando se trata de TextInput. También habrá una gran cantidad de espeleología de código, gracias a que todas las bibliotecas son de código abierto. La versión de React Native que estoy usando al momento de escribir es 0.57.5

El componente incorporado TextInput

React Native viene con un montón de componentes básicos, uno de ellos es TextInput para ingresar texto en la aplicación a través de un teclado.

import React, {Component} de 'react';
importar {AppRegistry, TextInput} desde 'react-native';
exportar clase predeterminada UselessTextInput extiende Componente {
  constructor (accesorios) {
    super (accesorios);
    this.state = {text: 'Marcador de posición inútil'};
  }
render () {
    regreso (
       this.setState ({text})}
        valor = {this.state.text}
      />
    );
  }
}

Eso es todo, cada vez que hacemos clic en la entrada de texto, aparece un teclado que nos permite ingresar valores. Para descartar el teclado presionando en cualquier lugar de la pantalla, la solución fácil es TouchableWithoutFeedback junto con Keyboard. Esto es similar a tener UITapGestureRecognizer en iOS UIView y llamar a view.endEditing

importar {Teclado} desde 'react-native'

Keyboard.dismiss ()

Entrada de texto dentro de ScrollView

Normalmente deberíamos tener algunas entradas de texto dentro de un componente de desplazamiento, en React Native que es principalmente ScrollView para poder manejar una larga lista de contenido y evitar el teclado. Si TextInput está dentro de ScrollView, la forma en que se descarta el teclado se comporta de manera un poco diferente y depende de keyboardShouldPersistTaps

Determina cuándo el teclado debe permanecer visible después de tocarlo.

  • 'nunca' (el valor predeterminado), al tocar fuera del ingreso de texto enfocado cuando el teclado está arriba, se cierra el teclado. Cuando esto sucede, los niños no recibirán el toque.
  • 'siempre', el teclado no se descartará automáticamente y la vista de desplazamiento no capturará toques, pero los elementos secundarios de la vista de desplazamiento pueden capturar toques.
  • 'manejado', el teclado no se descartará automáticamente cuando el toque fue manejado por un niño (o capturado por un antepasado).

El modo nunca debe ser el comportamiento deseado en la mayoría de los casos, al hacer clic en cualquier lugar fuera de la entrada de texto enfocada se debe cerrar el teclado.

En mi aplicación, hay algunas entradas de texto y un botón de acción. El escenario es que los usuarios ingresen algunas informaciones y luego presionen ese botón para registrar datos. Con el modo nunca, debemos presionar el botón dos veces, uno para cerrar el teclado y dos para presionar el botón. Entonces la solución es usar siempre el modo. De esta manera, el botón siempre obtiene primero el evento de prensa.

ScrollView se preocupa por el teclado

La clase nativa RCTScrollView que reacciona de manera nativa ScrollView tiene código para manejar el modo de descarte

RCT_SET_AND_PRESERVE_OFFSET (setKeyboardDismissMode, keyboardDismissMode, UIScrollViewKeyboardDismissMode)

La opción que elige es UIScrollViewKeyboardDismissMode para la propiedad keyboardDismissMode

La forma en que se descarta el teclado cuando comienza un arrastre en la vista de desplazamiento.

Como puede ver, los modos posibles son onDrag e interactivos. Y reaccionar nativo expone punto de personalización para esto a través de keyboardShouldPersistTaps

caso ninguno El teclado no se descarta con un arrastre.

case onDrag El teclado se descarta cuando comienza un arrastre.

caso interactivo El teclado sigue la pantalla táctil de arrastre y se puede tirar hacia arriba nuevamente para cancelar el descarte.

ScrollView dentro de un modal

Pero eso no funciona cuando ScrollView está dentro de Modal. Por modal me refería al componente modal en React Native. La única biblioteca que uso es react-navigation, y también admite la apertura de un modal de pantalla completa, pero de la misma manera que declaramos modal en react-navigation parece una pila y es confuso, por lo que preferiría no usarlo. Uso Modal en react-native y eso funciona bastante bien.

Entonces, si tenemos TextInput dentro de ScrollView dentro de Modal, keyboardShouldPersistTaps no funciona. Modal parece estar al tanto de ScrollView padre, por lo que tenemos que declarar keyboardShouldPersistTaps = 'siempre' en cada ScrollView padre. En React Native FlatList y SectionList usa ScrollView debajo del capó, por lo que debemos estar al tanto de todos esos componentes ScrollView.

Spelunking react-navigation

Dado que mi aplicación depende en gran medida de la navegación de reacción, es bueno tener una comprensión profunda de sus componentes para asegurarnos de dónde radica el problema. He escrito un poco sobre la estructura de navegación de reacción a continuación.

Al igual que todas las aplicaciones móviles tradicionales, mi aplicación consta de muchos navegadores de pila dentro del navegador de pestañas. En iOS, eso significa muchos UINavigationViewController dentro de UITabbarController. En react-navigation utilizo createMaterialTopTabNavigator dentro de createBottomTabNavigator

importar {createMaterialTopTabNavigator} desde 'react-navigation'
importar {createBottomTabNavigator, BottomTabBar} desde 'react-navigation-tabs'

La pantalla que tengo problemas con el teclado es un Modal presentado desde la segunda pantalla en uno de los navegadores de la pila, así que examinemos cada posible ScrollView en la jerarquía. Este proceso implica mucha lectura de código y así es como me encanta el código abierto.

Primero comencemos con createBottomTabNavigator que usa createTabNavigator junto con su propia TabNavigationView

La clase TabNavigationView extiende React.PureComponent 
exportar default createTabNavigator (TabNavigationView);

El navegador de pestañas tiene una vista de barra de pestañas debajo de ScreenContainer, que se usa para contener la vista. ScreenContainer es de react-native-Screens "Este proyecto tiene como objetivo exponer los componentes del contenedor de navegación nativo a React Native". A continuación se muestra cómo funciona el navegador de pestañas.

render () {
  const {navigation, renderScene, lazy} = this.props;
  const {rutas} = navigation.state;
  const {loaded} = this.state
  regreso (
    
      
        {routes.map ((ruta, índice) => {
          if (perezoso &&! loaded.includes (index)) {
            // No renderices una pantalla si nunca hemos navegado a ella
            volver nulo;
          
          const isFocused = navigation.state.index === index
          regreso (
            
              {renderScene ({ruta})}
            
          );
        })}
      
      {this._renderTabBar ()}
    
  );
}

La barra de pestañas se representa usando BottomTabBar en la función _renderTabBar. Mirando el código, todo el navegador de pestañas no tiene nada que ver con ScrollView.

Por lo tanto, solo queda createMaterialTopTabNavigator en la lista de sospechosos. Lo uso en la aplicación con swipeEnabled: true. Y al mirar las importaciones, el navegador de pestañas superior tiene

importar MaterialTopTabBar, {type TabBarOptions,} desde '../views/MaterialTopTabBar';

MaterialTopTabBar tiene importación desde react-native-tab-view

importar {TabBar} desde 'react-native-tab-view';

que tiene ScrollView


  

La propiedad keyboardShouldPersistTaps se configuró inicialmente para siempre, luego se volvió a manejar para evitar el error de que no podemos presionar ningún botón en la barra de pestañas mientras el teclado está abierto https://github.com/react-native-community/react-native -tab-view / issues / 375

Pero esta TabBar no tiene nada con nuestro problema, porque es solo para contener botones de barra de pestañas.

Deslizar en createMaterialTopTabNavigator

Echando otro vistazo a createMaterialTopTabNavigator, vemos más importaciones desde react-native-tab-view

importar {TabView, PagerPan} desde 'react-native-tab-view';

TabView ha deslizado Habilitado pasado

regreso (
  
);

y presenta PagerDefault, que a su vez usa PagerScroll para iOS

importar {Plataforma} desde 'react-native';
dejar a Pager;
switch (Platform.OS) {
  caso 'android':
    Pager = require ('./ PagerAndroid'). Default;
    descanso;
  caso 'ios':
    Pager = require ('./ PagerScroll'). Default;
    descanso;
  defecto:
    Pager = require ('./ PagerPan'). Default;
    descanso;
}
exportar buscapersonas predeterminado;

Por lo tanto, PagerScroll usa ScrollView para manejar el desplazamiento para que coincida con el estilo del material que el usuario puede desplazarse entre las páginas, y tiene keyboardShouldPersistTaps = "siempre", que debería ser correcto.

regreso (
  

Entonces, nada parece sospechoso en react-navigation, lo que me insta a mirar el código de mi proyecto.

Depuración de FlatList, SectionList y ScrollView

Como dije al comienzo de este artículo, el problema raíz es que necesitamos declarar keyboardShouldPersistTaps para todos los ScrollView primarios en la jerarquía. Eso significa buscar cualquier FlatList, SectionList y ScrollView

Afortunadamente, hay react-devtools que muestra el árbol de todos los componentes renderizados en la aplicación react, y eso también se guía en la sección de Depuración de react native.

Puede usar la versión independiente de React Developer Tools para depurar la jerarquía del componente React. Para usarlo, instale el paquete react-devtools a nivel mundial:

npm install -g react-devtools

Entonces, después de buscar, descubrí que hay una SectionList en la jerarquía que debería tener keyboardShouldPersistTaps = 'always' mientras no lo tenía.

Al analizar detenidamente el código, descubrí que el modal se activa desde un elemento de la lista de secciones. Ya sabemos que activar Modal en reaccionar nativo significa que incrustar ese Modal dentro de la jerarquía de vistas y controlar su visibilidad a través de un estado. Entonces, en términos de vista y componente, ese Modal está dentro de una SectionList. Y para su interés, si profundiza en el código nativo de reacción, SectionList en mi caso es solo VirtualizedSectionList, que es VirtualizedList, que usa ScrollView

Entonces, después de declarar keyboardShouldPersistTaps = 'always' en esa SectionList, el problema está resuelto. El usuario ahora puede ingresar algunos valores en las entradas de texto, luego presionar una vez en el botón enviar para enviar datos. El botón ahora captura los eventos táctiles primero en lugar de la vista de desplazamiento.

A dónde ir desde aquí

Afortunadamente, la solución para esto es simple, ya que implica arreglar nuestro código sin tener que alterar el código de reacción de navegación. Pero es bueno mirar el código de la biblioteca para saber qué hace y rastrear dónde se origina el problema. Gracias por seguir explorando tanto y espero que aprendas algo.

Si te gusta esta publicación, considera visitar mis otros artículos y aplicaciones