Comment faire glisser automatiquement la fenêtre derrière le clavier lorsque TextInput a le focus?

90

J'ai vu ce hack pour les applications natives pour faire défiler automatiquement la fenêtre, mais je me demande la meilleure façon de le faire dans React Native ... Lorsqu'un <TextInput>champ obtient le focus et est positionné bas dans la vue, le clavier couvrira le champ de texte.

Vous pouvez voir ce problème dans la TextInputExample.jsvue de l' exemple UIExplorer .

Quelqu'un at-il une bonne solution?

McG
la source
3
Je suggérerais d'ajouter cela comme un problème sur le tracker Github et de voir si quelque chose en résulte, car cela va être une plainte très courante.
Colin Ramsay

Réponses:

83

Réponse 2017

C'est KeyboardAvoidingViewprobablement la meilleure façon de procéder maintenant. Consultez les documents ici . C'est vraiment simple par rapport au Keyboardmodule qui donne au développeur plus de contrôle pour effectuer des animations. Spencer Carli a démontré toutes les manières possibles sur son blog médium .

Réponse de 2015

La bonne façon de faire cela react-nativene nécessite pas de bibliothèques externes, tire parti du code natif et inclut des animations.

Définissez d'abord une fonction qui gérera l' onFocusévénement pour chacun TextInput(ou pour tout autre composant vers lequel vous souhaitez faire défiler):

// 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);
}

Ensuite, dans votre fonction de rendu:

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

Cela utilise RCTDeviceEventEmitterpour les événements de clavier et le dimensionnement, mesure la position du composant à l'aide RCTUIManager.measureLayoutet calcule le mouvement de défilement exact requis dans scrollResponderInputMeasureAndScrollToKeyboard.

Vous voudrez peut-être jouer avec le additionalOffsetparamètre, pour répondre aux besoins de votre conception d'interface utilisateur spécifique.

Sherlock
la source
5
C'est une belle trouvaille, mais pour moi, ce n'était pas suffisant, car si ScrollView s'assurera que TextInput est à l'écran, ScrollView affichait toujours du contenu sous le clavier sur lequel l'utilisateur ne pouvait pas faire défiler. La définition de la propriété ScrollView "keyboardDismissMode = on-drag" permet à l'utilisateur de supprimer le clavier, mais s'il n'y a pas assez de contenu de défilement sous le clavier, l'expérience est un peu discordante. Si le ScrollView n'a qu'à faire défiler à cause du clavier en premier lieu, et que vous désactivez le rebond, il semble qu'il n'y ait aucun moyen de fermer le clavier et d'afficher le contenu ci
miracle2k
2
@ miracle2k - J'ai une fonction qui réinitialise la position de la vue de défilement lorsqu'une entrée est floue, c'est-à-dire lorsque le clavier se ferme. Peut-être que cela pourrait aider dans votre cas?
Sherlock
2
@Sherlock À quoi ressemble cette fonction de réinitialisation de la vue de défilement flou? Solution géniale au fait :)
Ryan McDermott
8
Dans les nouvelles versions de React Native, vous devrez appeler: * import ReactNative de 'react-native'; * avant d'appeler * ReactNative.findNodeHandle () * Sinon, l'application plantera
amirfl
6
Maintenant import {findNodeHandle} from 'react-native' stackoverflow.com/questions/37626851/…
antoine129
26

Facebook open source KeyboardAvoidingView en react native 0.29 pour résoudre ce problème. La documentation et l'exemple d'utilisation peuvent être trouvés ici .

farwayer
la source
32
Méfiez-vous de KeyboardAvoidingView, ce n'est tout simplement pas facile à utiliser. Il ne se comporte pas toujours comme prévu. La documentation est pratiquement inexistante.
Renato Back
doc et comportement s'améliorent maintenant
antoine129
Le problème que j'ai, c'est que le KeyboardAvoidingView mesure la hauteur du clavier à 65 sur mon simulateur iPhone 6 et donc ma vue est toujours cachée derrière le clavier.
Marc
La seule façon dont je pouvais gérer c'était par une approche de bottompadding déclenchée parDeviceEventEmitter.addListener('keyboardDidShow', this.keyboardDidShow.bind(this));
Marc
12

Nous avons combiné une partie de la forme de code react-native-keyboard-spacer et le code de @Sherlock pour créer un composant KeyboardHandler qui peut être enveloppé autour de n'importe quelle vue avec des éléments TextInput. Fonctionne comme un charme! :-)

/**
 * 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
la source
Quelque chose de simple / évident qui empêcherait le clavier de s'afficher dans un simulateur iOS?
seigel
1
Avez-vous essayé Command + K (Hardware-> Keyboard-> Toggle Software Keboard)?
John kendall
Essayez la version modifiée de ceci ici: gist.github.com/dbasedow/f5713763802e27fbde3fc57a600adcd3 Je pense que c'est mieux car il ne repose sur aucun délai d'attente qui, à mon avis, est fragile.
CoderDave
10

Vous devez d'abord installer react-native-keyboardevents .

  1. Dans XCode, dans le navigateur de projet, cliquez avec le bouton droit de la souris sur Bibliothèques ➜ Ajouter des fichiers à [nom de votre projet] Allez dans node_modules ➜ react-native-keyboardevents et ajoutez le fichier .xcodeproj
  2. Dans XCode, dans le navigateur de projet, sélectionnez votre projet. Ajoutez la lib * .a du projet keyboardevents aux phases de construction de votre projet ➜ Lier le binaire aux bibliothèques Cliquez sur le fichier .xcodeproj que vous avez ajouté auparavant dans le navigateur de projet et accédez à l'onglet Paramètres de construction. Assurez-vous que «All» est activé (au lieu de «Basic»). Recherchez les chemins de recherche d'en-tête et assurez-vous qu'il contient à la fois $ (SRCROOT) /../ react-native / React et $ (SRCROOT) /../../ React - marquez les deux comme récursifs.
  3. Exécutez votre projet (Cmd + R)

Puis de retour au pays javascript:

Vous devez importer les événements de réaction-native-keyboard.

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

Ensuite, dans votre vue, ajoutez un état pour l'espace clavier et mettez à jour en écoutant les événements clavier.

  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,
    };
  },

Enfin, ajoutez un espaceur à votre fonction de rendu sous tout, donc quand il augmente la taille, cela augmente votre contenu.

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

Il est également possible d'utiliser l'API d'animation, mais par souci de simplicité, nous nous contentons d'ajuster après l'animation.

Brysgo
la source
1
Ce serait génial de voir un exemple de code / quelques informations supplémentaires sur la façon de faire l'animation. Le saut est assez capricieux, et en travaillant uniquement avec les méthodes "will show" et "did show", je ne peux pas vraiment comprendre comment deviner la durée de l'animation du clavier ou la hauteur de "will show".
Stephen
2
[email protected] envoie maintenant des événements de clavier (par exemple, "keyboardWillShow") via DeviceEventEmitter, afin que vous puissiez enregistrer des écouteurs pour ces événements. En traitant avec un ListView, cependant, j'ai trouvé que l'appel de scrollTo () sur le scrollview de ListView fonctionnait mieux: this.listView.getScrollResponder().scrollTo(rowID * rowHeight); cela est appelé sur TextInput d'une ligne lorsqu'il reçoit un événement onFocus.
Jed Lau
4
Cette réponse n'est plus valide, car le RCTDeviceEventEmitter fait le travail.
Sherlock
6

Essaye ça:

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>);
}

...

Cela a fonctionné pour moi. La vue se rétrécit fondamentalement lorsque le clavier est affiché et repousse lorsqu'il est masqué.

pomo
la source
En outre, cette solution fonctionne bien (RN 0.21.0) stackoverflow.com/a/35874233/3346628
pomo
utilisez this.keyboardWillHide.bind (this) au lieu de self
animekun
Utilisez plutôt le clavier DeviceEventEmitter
Madura Pradeep
4

C'est peut-être trop tard, mais la meilleure solution est d'utiliser une bibliothèque native, IQKeyboardManager

Faites simplement glisser et déposez le répertoire IQKeyboardManager du projet de démonstration vers votre projet iOS. C'est tout. Vous pouvez également configurer certains valus, comme isToolbar activé, ou l'espace entre la saisie de texte et le clavier dans le fichier AppDelegate.m. Plus de détails sur la personnalisation se trouvent dans le lien de page GitHub que j'ai ajouté.

Adrian Zghibarta
la source
1
C'est une excellente option. Voir également github.com/douglasjunior/react-native-keyboard-manager pour une version encapsulée pour ReactNative - c'est facile à installer.
loevborg
3

J'ai utilisé TextInput.onFocus et ScrollView.scrollTo.

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

@Stephen

Si cela ne vous dérange pas de ne pas animer la hauteur exactement à la même vitesse que celle du clavier, vous pouvez simplement utiliser LayoutAnimation, de sorte qu'au moins la hauteur ne se mette pas en place. par exemple

importez LayoutAnimation de react-native et ajoutez les méthodes suivantes à votre composant.

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);
  },

Quelques exemples d'animations sont (j'utilise le ressort ci-dessus):

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,
      },
    },
  },
};

METTRE À JOUR:

Voir la réponse de @ sherlock ci-dessous, à partir de react-native 0.11, le redimensionnement du clavier peut être résolu à l'aide de la fonctionnalité intégrée.

deanmcpherson
la source
2

Vous pouvez combiner quelques-unes des méthodes en quelque chose d'un peu plus simple.

Attachez un auditeur onFocus à vos entrées

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

Notre méthode de défilement vers le bas ressemble à quelque chose comme:

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

Cela indique à notre vue de défilement (n'oubliez pas d'ajouter une référence) de faire défiler jusqu'à la position de notre entrée focalisée - 200 (c'est à peu près la taille du clavier)

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

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

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

Ici, nous réinitialisons notre vue de défilement vers le haut,

entrez la description de l'image ici

Nath
la source
2
@pouvez-vous fournir votre méthode render ()?
valerybodak
0

J'utilise une méthode plus simple, mais elle n'est pas encore animée. J'ai un état de composant appelé "bumpedUp" que je par défaut à 0, mais mis à 1 lorsque textInput obtient le focus, comme ceci:

Sur mon textInput:

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

J'ai aussi un style qui donne au conteneur d'emballage de tout sur cet écran une marge inférieure et une marge supérieure négative, comme ceci:

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

Et puis sur le conteneur d'emballage, j'ai défini les styles comme ceci:

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

Ainsi, lorsque l'état "bumpedUp" est mis à 1, le style bumpedcontainer entre en action et déplace le contenu vers le haut.

Un peu hacky et les marges sont codées en dur, mais ça marche :)

Stirman
la source
0

J'utilise brysgo answer pour élever le bas de mon scrollview. Ensuite, j'utilise le onScroll pour mettre à jour la position actuelle du scrollview. J'ai ensuite trouvé ce React Native: Obtenir la position d'un élément pour obtenir la position de l'entrée de texte. Je fais ensuite quelques calculs simples pour déterminer si l'entrée est dans la vue actuelle. Ensuite, j'utilise scrollTo pour déplacer le montant minimum plus une marge. C'est assez fluide. Voici le code pour la partie défilante:

            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 );
                        }
                     );
                };
            },
aintnorest
la source
0

Je rencontre également cette question. Enfin, je le résolve en définissant la hauteur de chaque scène, telle que:

<Navigator
    ...
    sceneStyle={{height: **}}
/>

Et, j'utilise également un module tiers https://github.com/jaysoo/react-native-extra-dimensions-android pour obtenir la hauteur réelle.

Renguang Dong
la source