Textinput formatting problem in React Native

Asked

Viewed 374 times

-1

I have the following React Native component which is a screen to fill in data from an activity:

import React, { useState } from 'react';
import { View, Text, TextInput, TouchableOpacity, Switch, ScrollView } from "react-native";
import BarraSuperior from "../../Recursos/BarraSuperior/Index";
import Estilos from './Styles';
import Atividades from '../../Services/sqlite/Atividades';
import DateTimePicker from '@react-native-community/datetimepicker';
import { Feather } from '@expo/vector-icons';
import normalizador from '../../Recursos/normalizador';
import { useNavigation, DrawerActions } from '@react-navigation/native';
    
export default function Cadastro() {
    //Define os dados do CRUD
    const [titulo, setTitulo] = useState('')
    const [categoria, setCategoria] = useState('')
    const [descricao, setDescricao] = useState('')
    const [btn, setBtn] = useState(false)
    const [date, setDate] = useState(new Date());

    //Métodos do DateTimePicker
    const [mode, setMode] = useState('date');
    const [show, setShow] = useState(false);

    const Navigation = useNavigation()

    // Navigation.dispatch(DrawerActions.openDrawer())

    function NavigateToAtividades() {
        Navigation.navigate('Atividades')
    }

    const onChange = (event, selectedDate) => {
        const currentDate = selectedDate || date;
        setShow(Platform.OS === 'ios');
        setDate(currentDate);
    };
    // Muda setShow para true o que faz com que DateTimePicker apareça na tela.
    const showMode = (currentMode) => {
        setShow(true);
        setMode(currentMode);
    };
    //Altera o modo de exibição para DatePicker
    const showDatepicker = () => {
        showMode('date');
    };
    //Altera o modo de exibição para TimePicker
    const showTimepicker = () => {
        showMode('time');
    };
    //Formata a data que vai ser mostrada no campo de seleção da data
    const formatData = () => {
        let dia = date.getDate();
        let mes = date.getMonth();
        let ano = date.getFullYear();

        if (dia.toString().length === 1) {
            dia = '0' + dia
        }
        if (mes.toString().length === 1) {
            mes = '0' + (mes + 1)
        }
        return dia + '/' + mes + '/' + ano
    }
    //Formata as horas e minutos que vão ser mostrados no campo de seleção de tempo
    const formatTime = () => {
        let hora = date.getHours()
        let minutos = date.getMinutes()

        if (hora.toString().length === 1) {
            hora = '0' + hora
        }
        if (minutos.toString().length === 1) {
            minutos = '0' + minutos
        }
        return hora + ':' + minutos
    }
    //Setter para os campos de texto e botões
    const reset = () => {
        setBtn(false);
        setCategoria(' ');
        setTitulo(' ');
        setDescricao(' ');
        setDate(new Date());
    }
    //Passa os dados para o CRUD
    const save = () => {
        //Verifica se nenhum dos campos obrigatórios estão vazios, se não, é passado os dados para o banco de dados e o usuário é retornardo para a tela de Atividades. Se algum campo estiver vazio, será retornado um Alert
        if (titulo === '' || titulo === ' ') {
            return alert('Digite o Titulo')
        } else if (categoria === '' || categoria === ' ') {
            return alert('Digite a categoria')
        } else {
            Atividades.create({ titulo: titulo, categoria: categoria, descricao: descricao, data: date.toString(), notificar: btn, atrasado: false, concluida: false, dataConcluida: '' })
                .then(alert('Adicionado com sucesso!'))
                .catch(err => console.log(err))
            reset()
            NavigateToAtividades()
        }
    }


    return (
        <View style={Estilos.mainView}>
            <BarraSuperior
                conteudo='Nova tarefa'
                onPress={() => Navigation.dispatch(DrawerActions.openDrawer())}
                valor={false} 
                />
            <View style={{flex: 1,justifyContent: 'center', padding: '1%'}}>
            <View style={Estilos.secondaryView}>
                <Text style={Estilos.titulos}>Título da tarefa</Text>
                <TextInput 
                    style={Estilos.campos} 
                    value={titulo} 
                    onChangeText={text => setTitulo(text)}
                    />
                <Text style={Estilos.titulos}>Categoria</Text>
                <TextInput 
                    style={Estilos.campos} 
                    value={categoria} 
                    onChangeText={categoria => setCategoria(categoria)} 
                    />
                <Text style={Estilos.titulos}>Descrição<Text style={Estilos.descricao}> (Opcional)</Text></Text>
                <TextInput 
                    style={Estilos.textoInput} 
                    value={descricao} 
                    multiline
                    scrollEnabled={true}
                    onChangeText={text => setDescricao(text)} 
                    />
                <Text style={Estilos.titulos}>Data</Text>
                <View style={Estilos.data}>
                    <Feather 
                        name='calendar' 
                        color='gold' 
                        size={normalizador.widthPercentageToDP('4%')} 
                        />
                    <TouchableOpacity onPress={showDatepicker}>
                        <Text style={Estilos.textoData}>{formatData()}</Text>
                    </TouchableOpacity>
                    <Feather 
                        name='clock' 
                        color='gold' 
                        size={normalizador.widthPercentageToDP('4%')} 
                        />
                    <TouchableOpacity onPress={showTimepicker}>
                        <Text style={Estilos.textoData}>{formatTime()}</Text>
                    </TouchableOpacity>
                </View>
                <View style={Estilos.viewSwitch}>
                    <Text style={Estilos.notificar}>Noticar</Text>
                    <Switch 
                        trackColor={{ false: '#dedede', true: '#3e7fff' }} 
                        thumbColor={btn ? '#7eaaff' : '#dedede'} 
                        value={btn} 
                        onValueChange={() => { setBtn(!btn) }} 
                        />
                </View>
            </View>
            
            </View>
            <TouchableOpacity style={Estilos.Btn} onPress={save}>
                <Text style={Estilos.textBtn}>Adicionar</Text>
            </TouchableOpacity>
            
            {show && (
                <DateTimePicker
                    minimumDate={new Date}
                    testID="dateTimePicker"
                    value={date}
                    mode={mode}
                    is24Hour={true}
                    display="default"
                    onChange={onChange}
                />
            )}
        </View>
    )
}

With the following formatting:

import {StyleSheet} from "react-native";
import normalizador from "../../Recursos/normalizador";
    
    
export default StyleSheet.create({
    textoData: {
        fontWeight: 'bold',
        fontSize: normalizador.widthPercentageToDP('3%'),
        color: 'white'
    },
    descricao : {
        fontFamily: 'Poppins_400Regular',
        fontSize : normalizador.widthPercentageToDP('2%')
    },
    data: {
        alignItems: 'center',
        justifyContent: 'space-evenly',
        padding: '3%',
        flexDirection: 'row',
        position: 'relative',
        backgroundColor: 'blue',
        width: '60%',
        height: '8%',
        borderRadius: 20
    },
    textBtn: {
        color: '#FFF',
        fontFamily: 'Poppins_600SemiBold',
        fontSize: normalizador.widthPercentageToDP('4%')
    },
    Btn: {
        minHeight: '7%',
        minWidth: '35%',
        backgroundColor: 'blue',
        borderRadius: 20,
        alignItems: 'center',
        justifyContent: 'center'
    },
    textoInput : {
        padding: '3%',
        width: '95%',
        height: '30%',
        borderWidth: 1,
        borderColor: '#d9d9d9',
        borderRadius: 20
    },
    secondaryView: {
        justifyContent: 'space-between',
        flex: 1,
        backgroundColor: '#FFF',
        elevation: 20,
        borderRadius: 20,
        padding: '4%'
    },
    mainView : {
        flex: 1,
        alignItems: 'stretch'
    },
    titulos: {
        fontFamily: 'Poppins_700Bold',
        fontSize: normalizador.widthPercentageToDP('4%'),
        color: 'black',
    },
    campos: {
        width: '95%',
        height: '8%',
        borderWidth: 1,
        borderColor: '#d9d9d9',
        borderRadius: 20
    },
    viewSwitch: {
        justifyContent: 'center',
        alignItems: 'center'
    },
    notificar: {
        fontSize: normalizador.widthPercentageToDP('3%')
    }
})

My problem is that every time I open the keyboard to type the text the screen components are resized instead of them simply moving up, example:

Screen without opening Textinput:

inserir a descrição da imagem aqui

Screen after opening Textinput:

inserir a descrição da imagem aqui

What can I do to fix this formatting error?

1 answer

2


What I think is causing this distortion of the components by the appearance of the keyboard, is the use of percentage values as a measure. Similar things happen in web:

.window {
  width: 200px;
  height: 20px;
  resize: both;
  overflow: auto;
  background: #eee;
}

.distorced-container {
  width: 50%;
  height: 10%;
  background: #aaa;
}
<div class="window">
  <div class="distorced-container">
    Hello, World!
  </div>
</div>

When resizing the container external (the rectangle of lighter gray color), you will see that, by the container internal (the rectangle of darker gray color) have their measurements based on percentage, changing the dimensions of the container external causes it to get distorted.

That’s because the container internal is dimensioned this way:

  • The width should be 50% of the total width of the parent element;
  • The height should be 10% of the total height of the parent element;

If the overall dimensions of the parent element are 500px x 100px, the total dimensions of this item shall be 250px x 10px. In case, as it is 200px x 20px, then it will be 100px x 2px. That is, the dimensions of this internal element depend on the dimensions of its parent element.

Similarly it is in React Native. This container external (<div class="window">) is similar to the visible application window. And this container intern (<div class="distorced-container">) is similar to the text field.

When the visible window is modified by the keyboard, the dimensions of the text field are updated based on the percentage you have set relative to the width of the window and its parent component.


So a suggestion is that you also use the function widthPercentageToDP, together with the heightPercentageToDP, to define the measurements of the components - as I personally use - because thus, although you pass a percentage to it, a "fixed value will be returned".

export default StyleSheet.create({
    ...
    textoInput : {
        padding: '3%',
        width: normalizador.widthPercentageToDP('95%'),
        height: normalizador.heightPercentageToDP('30%'),
        borderWidth: 1,
        borderColor: '#d9d9d9',
        borderRadius: 20
    },
    ...
    campos: {
        width: normalizador.widthPercentageToDP('95%'),
        height: normalizador.heightPercentageToDP('8%'),
        borderWidth: 1,
        borderColor: '#d9d9d9',
        borderRadius: 20
    },
    ...
})

This is because the function will be executed only in the creation of the object and will therefore return the value only once. For example:

function computeWidth(value) {
  const properValue = typeof value === 'number' ? value : parseFloat(value);
  
  return window.innerWidth * properValue / 100;
}

console.log({
  valorConstanteAdaptativo: '50%',
  valorConstanteComputado: computeWidth('50%'),
});

While the value '50%' will cause the element to change its dimensions (adapt) according to the dimensions of its parent element, the value returned by computeWidth('50%') will calculate the size of 50% screen once and there will remain until the function is called again, which is when the object may have been created again.

I created that snippet at the Snack to demonstrate how this actually works in React Native. In iOS, it seems that there is no change in the dimensions by the keyboard, as occurs in Android and web.

See that, with the activation of the keyboard, the <TextInput /> based on percentage (the top) was flattened, while the <TextInput /> based on the value computed by the function (the bottom one) was "intact".

  • Thank you very much! Actually, after several attempts I realized that the components were being resized based on the screen size available after the keyboard opening. Just didn’t know how to fix the size of the components using percentage (your dirt). So, I used your idea to solve the problem and the answer dirt below to improve the experience of using the app.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.