My Boundary As Much As I Experienced

RN) 모바일 키보드 조작에 대한 모든 것 (TextInput, AvoidingKeyboardView) 본문

FrontEnd/React Native

RN) 모바일 키보드 조작에 대한 모든 것 (TextInput, AvoidingKeyboardView)

Bumang 2024. 8. 21. 00:11

이번에 회사에서 RN으로 초기앱을 세팅부터 다시 해볼 수 있는 기회가 생겼다.

돈 되보이는 것들은 모두 한 번 만들어보자는 싸장님의 방향성 덕분에 앞으로도 초기세팅을 해볼 일이 많아질거 같다..

 

RN은 React와 거의 비슷한 구조로 사용할 수 있지만 가장 차이나는 부분은 input의 활용 부분인 거 같다.

이번에 초기세팅하면서 배운 부분들을 정리해보려 한다.

 

1. TextInput

웹에서 쓰는 input은 RN에서 TextInput 컴포넌트로 사용할 수 있다.

 

웹과 구별되는 속성:

 

editable:

  - 사용자 입력을 받을 수 있는지 없는지 여부를 설정한다. (boolean)

type:

  - 텍스트에 입력되는 정보들의 타입을 결정한다. 기본은 "text" ("text" | "number" | "password")

keyboardType:

  - 텍스트인풋을 선택했을 때 올라오는 키보드를 ("default" | "numeric" | "email-address")

 

ReturnKeyType:

  - 노출된 키보드의 '다음' 버튼의 텍스트를 결정한다. ("next" | "done" | "go" | "send" | "search")

 

onChangeText:

  - 웹에서는 onChange속성을 사용하겠지만, 여기선 onChangeText라는 속성밖에 사용하지 못한다. 또한 event를 받지 않고 실제 텍스트string를 패러미터로 받는다. ( (arg: string) => void; )

 

onSubmitEditing?: 

  -  수정을 완료하고 제출할 때 어떤 액션을 취해줄지 결정할 수 있다. api호출을 발생시킬수도 있고 다음 인풋으로 보낼수도 있다. 다음 인풋으로 보내려면 ref들을 적절히 이용해줘야 한다. 다음 input 선택하는 방법은 추후 포스팅으로 올리겠다. (cb)

 

blurOnSubmit:

- 제출 후 키보드가 내려가는지 안 내려가는지 여부를 결정한다.

 

 

 

나는 사용하기 편하게 customTextInput을 만들어 스타일과여러 속성들을 적용할 수 있게 만들었다.

(에러 시 인풋 아래 에러 문구 넣는 것도 추가할 예정이다.)

import React, { forwardRef, useState } from "react";
import { TextInput, StyleSheet, StyleProp } from "react-native";
import { theme } from "theme";

interface TextInputProps {
  type?: "text" | "number" | "password";
  keyboardType?: "default" | "numeric" | "email-address";
  editable?: boolean;
  placeholder?: string;
  secureTextEntry?: boolean;
  value?: string;
  onChangeText: (...arg: any[]) => void;
  style?: StyleProp<any>;
  borderColor?: string;
  returnKeyType?: "next" | "done" | "go" | "send" | "search";
  onSubmitEditing?: () => void;
  blurOnSubmit?: boolean;
}

const CustomTextInput = forwardRef<TextInput, TextInputProps>(
  (
    {
      type = "text", // 'text', 'number', 'password'
      keyboardType = "default", // 'default', 'numeric', 'email-address', etc.
      returnKeyType = "done",
      editable = true,
      placeholder = "",
      value,
      onChangeText,
      secureTextEntry = false,
      style,
      borderColor = theme.colors.gray.DEFAULT,
      onSubmitEditing,
      blurOnSubmit = true,
    }: TextInputProps,
    ref
  ) => {
    const [isFocused, setIsFocus] = useState(false);

    const styles = StyleSheet.create({
      input: {
        height: 48,
        borderColor: isFocused ? theme.colors.orange.DEFAULT : borderColor,
        borderBottomWidth: isFocused ? 2 : 1,
        padding: 10,
        marginVertical: 10,
        width: "100%",
        // backgroundColor: "white",
        // borderRadius: 8,
        fontSize: 16,
      },
    });

    return (
      <TextInput
        onFocus={() => setIsFocus(true)}
        onBlur={() => setIsFocus(false)}
        style={[styles.input, style]}
        keyboardType={keyboardType}
        placeholder={placeholder}
        value={value}
        onChangeText={onChangeText}
        secureTextEntry={type === "password" ? true : secureTextEntry}
        editable={editable}
        returnKeyType={returnKeyType}
        onSubmitEditing={onSubmitEditing}
        blurOnSubmit={blurOnSubmit}
        ref={ref}
      />
    );
  }
);

export default CustomTextInput;

 

 

2. KeyboardAvoidingView

키보드가 올라왔을 때 컨텐츠를 가리는 것을 방지하는 View이다.

아래 튜토리얼을 참고하는게 직관적으로 이해하기 좋을 것 같다.

 

https://www.youtube.com/watch?v=UcJyQ5MOoJo

 

난 처음에 KeyboardAvoidingView로 전체 페이지를 감쌌었는데 좋은 방법은 아니었다.

키보드가 올라갔을 때 전체페이지가 그대로 키보드 영역을 의식하에 Y축으로 조금씩 이동해야되는데

키보드가 나타날 때 페이지 전체가 움직일 수 있으며, 이로 인해 불필요한 영역까지 이동하게 될 수 있다.

특히, 비입력 요소가 많은 페이지에서는 사용자가 입력 필드가 아닌 다른 곳을 보고 있을 때

UI가 의도치 않게 이동할 수 있기 때문에 조심해야된다.

 

정말 키보드에 가리지 말아야할 일부 컴포넌트에만 써줘야 할 것 같다.

 

하여튼 나는 아래처럼 KeyboardAwareView라는 컴포넌트를 만들었다.

TouchableWithoutFeedback, SafeAreaView, KeyboardAvoidingView를 조합하여

키보드 영역 외를 누르면 키보드가 내려가는 컴포넌트를 만들어서 재사용하였다.

import { SafeAreaView } from "react-native-safe-area-context";
import {
  Keyboard,
  KeyboardAvoidingView,
  Platform,
  StyleProp,
  TouchableWithoutFeedback,
} from "react-native";

interface KeyboardAwareProps {
  style?: StyleProp<any>;
  children: React.ReactNode;
}

const KeyboardAwareView = ({ children, style }: KeyboardAwareProps) => {
  return (
    <TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
      <SafeAreaView style={style}>
        <KeyboardAvoidingView //
          behavior={Platform.OS === "ios" ? "padding" : "height"}
          // keyboardVerticalOffset={300}
        >
          {children}
        </KeyboardAvoidingView>
      </SafeAreaView>
    </TouchableWithoutFeedback>
  );
};

export default KeyboardAwareView;

 

keyboardVerticalOffset:

키보드가 올라와서 어떤 요소를 가리는 것을 방지한다.

이걸 사용하면 아래처럼 키보드가 올라올 때 인풋창이 가리지 않고

키보드 위에 달려서(?) 잘 올라오도록 해줄 수 있다.

 

 

 

보통 안드로이드인 경우 굳이 해줄 필요 없고 iOS인 경우 100px 정도를 추가적으로 들어올려주면 된다고 한다.

iOS에선 왜 100px을 더 들어올려줘야 제대로 올라가냐고? 나도 몰라 얘네가 이렇게 구현해놨음..

 

경험적으로 keyboardVerticalOffset을 0으로 하면

iOS에선 해당 컴포넌트의 top부분이 키보드의 top부분과 맞닿아 가려지게 된다.

즉, 안드에서는 키보드 높이만큼 깔끔히 타겟 컴포넌트가 올라가는데, iOS는 좀 더 들어올려줘야 자연스러운 편.

알아서 offset값을 조절해서 원하는 View를 만들어라.

const TypingComponent = () => {
  return (
    <KeyboardAvoidingView
      style={styles.container}
      behavior={Platform.OS === "ios" ? "padding" : "height"}
      keyboardVerticalOffset={Platform.OS === "ios" ? 80 : 0}
      <View style={styles.inputContainer}>
        <TextInput
          placeholder="메시지를 입력하세요..."
          style={styles.textInput}
        />
        <Button
          title="전송"
          onPress={() => {
            /* 메시지 전송 로직 */
          }}
        />
      </View>
    </KeyboardAvoidingView>
  );
};