Flutter로 멋진 앱을 개발하고 드디어 사용자들에게 선보였을 때, 가장 당혹스러운 피드백 중 하나는 "특정 글자가 입력되지 않아요"일 것입니다. 특히 한국 사용자들을 대상으로 하는 앱이라면, 이 문제는 더욱 치명적일 수 있습니다. 분명히 한글 입력을 허용하도록 코드를 작성했는데, 왜 '어', '여', '쓰'와 같은 특정 단어들이 입력되지 않는다는 불만이 접수될까요? 이 문제의 주범은 바로 우리가 흔히 사용하는 정규식과 한국의 독특한 키보드 입력 방식, 특히 '천지인(天地人)' 키보드와의 충돌에 있습니다.
이 글에서는 Flutter의 TextField
위젯에서 FilteringTextInputFormatter
를 사용하여 한글 입력을 제한할 때 왜 특정 글자들이 누락되는지, 그 근본적인 원인을 한국어의 문자 조합 원리와 천지인 키보드의 동작 방식을 통해 심층적으로 파헤쳐 봅니다. 그리고 단순히 해결 코드 한 줄을 제시하는 것을 넘어, 왜 그 코드가 정답인지, 더 나아가 어떤 상황에 어떤 정규식을 사용해야 하는지에 대한 완벽한 가이드를 제공하고자 합니다. 이 글을 끝까지 읽으신다면, 다시는 텍스트 입력 문제로 사용자의 불만을 사는 일은 없을 것입니다.

1. 모든 문제의 시작: TextField와 InputFormatters
사용자로부터 텍스트 입력을 받는 것은 모든 애플리케이션의 가장 기본적인 기능입니다. Flutter에서는 TextField
위젯을 통해 이 기능을 손쉽게 구현할 수 있습니다. 이름, 아이디, 비밀번호, 검색어 등 다양한 정보를 입력받기 위해 우리는 TextField
를 사용합니다.
import 'package:flutter/material.dart';
class BasicTextField extends StatelessWidget {
const BasicTextField({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Padding(
padding: EdgeInsets.all(20.0),
child: TextField(
decoration: InputDecoration(
labelText: '자유롭게 입력하세요',
border: OutlineInputBorder(),
),
),
),
),
);
}
}
위 코드는 가장 기본적인 형태의 TextField
입니다. 이 상태에서는 사용자가 키보드로 입력할 수 있는 모든 문자(한글, 영어, 숫자, 특수문자 등)가 아무런 제약 없이 입력됩니다. 하지만 대부분의 경우, 우리는 특정 형식의 입력만을 허용해야 합니다. 예를 들어, '나이'를 입력하는 필드에는 숫자만, '이름'을 입력하는 필드에는 한글이나 영어만 허용하고 싶을 수 있습니다.
이때 사용하는 것이 바로 TextField
의 inputFormatters
속성입니다. 이 속성은 List<TextInputFormatter>
타입을 받으며, 사용자의 입력을 실시간으로 가공하거나 필터링하는 역할을 합니다. Flutter는 몇 가지 유용한 기본 TextInputFormatter
를 제공합니다.
LengthLimitingTextInputFormatter
: 입력 가능한 최대 글자 수를 제한합니다.FilteringTextInputFormatter
: 정규식(Regular Expression)을 기반으로 허용하거나 거부할 문자를 지정합니다.
우리가 겪는 한글 입력 문제의 핵심은 바로 이 FilteringTextInputFormatter
의 사용법에 있습니다.
2. 흔히 사용하는 '잘못된' 한글 필터링 코드
인터넷이나 여러 튜토리얼에서 한글 입력만 허용하기 위한 코드를 찾아보면, 다음과 같은 형태의 코드를 쉽게 발견할 수 있습니다.
// 흔히 사용되지만, 문제가 있는 코드
TextField(
decoration: const InputDecoration(
labelText: '이름 (한글만)',
border: OutlineInputBorder(),
),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[ㄱ-ㅎ|가-힣]')),
],
)
이 코드를 분석해 보겠습니다. FilteringTextInputFormatter.allow()
는 정규식(RegExp)에 매칭되는 문자만 입력을 '허용'하는 포매터입니다. 위 코드에서 사용된 정규식 [ㄱ-ㅎ|가-힣]
의 의미는 다음과 같습니다.
[ㄱ-ㅎ]
: 한글 자음 'ㄱ'부터 'ㅎ'까지의 모든 문자를 의미합니다. (예: ㄱ, ㄴ, ㄷ, ...)|
: '또는(OR)'을 의미하는 논리 연산자입니다.[가-힣]
: 완성형 한글 '가'부터 '힣'까지의 모든 문자를 의미합니다. 이는 유니코드상에 존재하는 모든 현대 한글 음절을 포함합니다. (예: 가, 나, 다, ..., 강, 밥, 힣)
얼핏 보기에는 완벽해 보입니다. 한글 자음과 완성형 한글을 모두 허용했으니, 한글 입력에는 아무런 문제가 없을 것이라고 생각하기 쉽습니다. 실제로 대부분의 쿼티(QWERTY) 기반 한글 키보드(두벌식)에서는 이 코드가 큰 문제 없이 동작하는 것처럼 보입니다. 그러나 사용자가 '천지인' 키보드를 사용하는 순간, 문제는 수면 위로 드러납니다.
3. 문제의 근원: 한글 조합 원리와 천지인 키보드
왜 [ㄱ-ㅎ|가-힣]
정규식이 천지인 키보드에서 문제를 일으키는지 이해하려면, 먼저 한글이라는 문자가 어떻게 구성되는지, 그리고 천지인 키보드가 어떤 방식으로 글자를 만들어내는지를 알아야 합니다.
한글의 제자(製字) 원리: 초성, 중성, 종성
한글은 자음과 모음을 결합하여 하나의 음절(글자)을 만드는 '모아쓰기' 방식을 사용합니다. 하나의 음절은 초성(첫 자음), 중성(가운데 모음), 그리고 선택적으로 종성(끝 자음)으로 이루어집니다.
예를 들어, '한'이라는 글자는 다음과 같이 조합됩니다.
- 초성: ㅎ (자음)
- 중성: ㅏ (모음)
- 종성: ㄴ (자음)
우리가 키보드로 '한'을 입력할 때, 운영체제의 입력기(IME, Input Method Editor)는 'ㅎ', 'ㅏ', 'ㄴ' 키 입력을 순차적으로 받아 이를 조합하여 '한'이라는 완성된 글자로 화면에 보여줍니다. 이 과정에서 우리는 중간 단계를 거의 인지하지 못합니다.
천지인 키보드의 동작 방식
천지인 키보드는 피처폰 시절부터 널리 사용되어 온 모바일용 한글 입력 방식입니다. 적은 수의 버튼으로 모든 한글을 입력할 수 있다는 장점이 있어 스마트폰 시대에도 여전히 많은 사용자들이 선호하는 방식입니다.
천지인의 핵심 원리는 '천(ㆍ)', '지(ㅡ)', '인(ㅣ)' 세 가지 기본 모음을 조합하여 모든 모음을 만들어내는 것입니다.
- ㅏ = ㅣ + ㆍ
- ㅓ = ㆍ + ㅣ
- ㅗ = ㆍ + ㅡ
- ㅜ = ㅡ + ㆍ
- ㅑ = ㅣ + ㆍ + ㆍ
- ㅕ = ㆍ + ㆍ + ㅣ
- ... 등등
이제 결정적인 단서가 나왔습니다. 천지인 키보드로 '어'라는 글자를 입력하는 과정을 생각해 봅시다.
- 사용자는 'ㅇ'을 누릅니다. (현재 입력창: 'ㅇ')
- 사용자는 모음 'ㅓ'를 만들기 위해 먼저 'ㆍ' 키를 누릅니다. 이때 입력기는 'ㅇ'과 조합되기 전의 중간 글자인 'ㆍ'를 잠시 내부적으로 처리하거나 표시합니다.
- 사용자는 이어서 'ㅣ' 키를 누릅니다. 입력기는 'ㆍ'와 'ㅣ'가 조합되어 'ㅓ'가 되는 것을 인지하고, 앞서 입력된 'ㅇ'과 합쳐 최종적으로 '어'를 만듭니다.
문제의 재구성: 필터가 중간 과정을 막아버린다!
다시 우리의 '잘못된' 필터링 코드로 돌아가 봅시다.
FilteringTextInputFormatter.allow(RegExp(r'[ㄱ-ㅎ|가-힣]'))
이 필터는 [ㄱ-ㅎ]
범위의 자음과 [가-힣]
범위의 완성형 한글만 허용합니다. 그런데 천지인 키보드로 '어'를 입력하는 2단계에서 어떤 일이 벌어질까요? 사용자가 'ㆍ' 키를 누르는 순간, FilteringTextInputFormatter
는 이 'ㆍ'라는 문자를 검사합니다. 하지만 'ㆍ'는 [ㄱ-ㅎ]
에도 속하지 않고, [가-힣]
에도 속하지 않는 별개의 문자입니다. 따라서 필터는 이 문자의 입력을 그 즉시 '거부'해 버립니다.
결과적으로 입력기(IME)는 'ㅓ'를 조합하기 위한 핵심 재료인 'ㆍ'를 전달받지 못하므로, '어'라는 글자를 절대로 만들어낼 수 없게 됩니다. 이는 'ㆍ'가 들어가는 모든 모음, 즉 'ㅓ, ㅕ, ㅐ, ㅔ, ㅚ, ...' 등의 입력이 모두 막히는 심각한 문제로 이어집니다.
4. 완벽한 해결책: '중간 문자'를 허용하라
문제의 원인을 명확히 알았으니, 해결책은 간단합니다. 한글 조합에 사용되는 중간 문자들을 필터의 허용 목록에 추가해주면 됩니다. 천지인 키보드에서 사용하는 핵심 중간 문자는 다음과 같습니다.
ㆍ
(아래아): 'ㅓ', 'ㅗ' 등을 만드는 데 사용됩니다.ᆢ
(쌍아래아): 'ㅕ', 'ㅛ' 등을 만드는 데 사용됩니다.
이 두 문자를 기존의 정규식에 추가하면 됩니다.
// 완벽하게 동작하는 해결 코드
TextField(
decoration: const InputDecoration(
labelText: '이름 (천지인 완벽 지원)',
border: OutlineInputBorder(),
),
inputFormatters: [
// 'ㆍ'와 'ᆢ'를 정규식에 추가
FilteringTextInputFormatter.allow(RegExp(r'[ㄱ-ㅎ|가-힣|ㆍ|ᆢ]')),
],
)
새로운 정규식 [ㄱ-ㅎ|가-힣|ㆍ|ᆢ]
는 이제 자음, 완성형 한글뿐만 아니라 천지인 키보드의 조합용 문자인 'ㆍ'와 'ᆢ'까지 허용합니다. 이 코드를 적용하면, 천지인 키보드 사용자가 '어'를 입력할 때 'ㆍ' 문자가 필터에 의해 차단되지 않고 정상적으로 입력기에 전달되어 '어'라는 글자가 성공적으로 조합될 수 있습니다.
전체 예제 코드: 문제점과 해결책 비교
직접 문제를 확인하고 해결책을 적용해볼 수 있도록 전체 비교 코드를 작성했습니다. 아래 코드를 직접 실행하여 두 개의 텍스트 필드에서 천지인 키보드로 '여름' 또는 '어머니'를 입력해 보세요. 첫 번째 필드에서는 입력이 불가능하고, 두 번째 필드에서는 원활하게 입력되는 것을 확인할 수 있습니다.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Korean Input Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
home: const KoreanInputTestScreen(),
);
}
}
class KoreanInputTestScreen extends StatelessWidget {
const KoreanInputTestScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('한글 입력 포매터 테스트'),
),
body: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'아래 두 입력 필드에서 천지인(또는 유사한 조합형) 키보드로 \'여름\', \'어머니\', \'했다\' 등을 입력하여 차이를 확인해보세요.',
style: TextStyle(fontSize: 16),
),
const SizedBox(height: 30),
// 1. 문제가 발생하는 TextField
const Text(
'1. 잘못된 포매터 (천지인 입력 불가)',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.red),
),
const SizedBox(height: 8),
TextField(
decoration: const InputDecoration(
labelText: '이름 입력',
hintText: '\'어\', \'여\' 등 입력 시도',
border: OutlineInputBorder(),
),
inputFormatters: [
// 문제의 정규식: 'ㆍ', 'ᆢ'가 없음
FilteringTextInputFormatter.allow(RegExp(r'[ㄱ-ㅎ|가-힣]')),
],
),
const SizedBox(height: 40),
// 2. 문제가 해결된 TextField
const Text(
'2. 올바른 포매터 (천지인 입력 가능)',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.green),
),
const SizedBox(height: 8),
TextField(
decoration: const InputDecoration(
labelText: '이름 입력',
hintText: '정상적으로 입력됩니다.',
border: OutlineInputBorder(),
),
inputFormatters: [
// 해결된 정규식: 'ㆍ', 'ᆢ' 추가
FilteringTextInputFormatter.allow(RegExp(r'[ㄱ-ㅎ|가-힣|ㆍ|ᆢ]')),
],
),
],
),
),
),
),
);
}
}
5. 활용도를 높이는 추가 팁과 고려사항
단순히 한글만 허용하는 것을 넘어, 실제 앱 개발에서는 더 복잡한 요구사항을 마주하게 됩니다. 예를 들어, 닉네임에는 한글, 영어, 숫자를 모두 허용하지만 특수문자는 막고 싶을 수 있습니다. 또는, 이름 입력란에는 띄어쓰기를 허용해야 할 수도 있습니다.
상황별 추천 정규식 패턴
다양한 시나리오에 맞춰 사용할 수 있는 정규식 패턴들을 소개합니다. 자신의 요구사항에 맞게 조합하여 사용해 보세요.
-
한글만 허용 (천지인 완벽 지원)
RegExp(r'[ㄱ-ㅎ|가-힣|ㆍ|ᆢ]')
-
한글 + 띄어쓰기(공백) 허용
공백 문자를 의미하는
\s
를 추가합니다.RegExp(r'[ㄱ-ㅎ|가-힣|ㆍ|ᆢ|\s]')
-
한글 + 영어 + 숫자 허용
영문(
a-z
,A-Z
)과 숫자(0-9
) 범위를 추가합니다.RegExp(r'[a-zA-Z0-9ㄱ-ㅎ|가-힣|ㆍ|ᆢ]')
-
한글 + 영어 + 숫자 + 띄어쓰기 허용 (가장 일반적인 닉네임 형태)
위 패턴에 공백(
\s
)을 추가합니다.RegExp(r'[a-zA-Z0-9ㄱ-ㅎ|가-힣|ㆍ|ᆢ|\s]')
-
특정 특수문자만 추가로 허용 (예: 밑줄 `_`, 하이픈 `-`)
허용할 특수문자를 직접 나열합니다.
RegExp(r'[a-zA-Z0-9ㄱ-ㅎ|가-힣|ㆍ|ᆢ|\s|_|-]')
고급 기법: 나만의 커스텀 TextInputFormatter 만들기
정규식만으로 해결하기 어려운 매우 복잡한 입력 규칙이 필요할 때가 있습니다. 예를 들어, '첫 글자는 반드시 한글이어야 하고, 중간에는 숫자 입력이 가능하지만 연속된 두 개의 특수문자는 허용하지 않는다'와 같은 규칙은 정규식 하나로 표현하기 매우 까다롭습니다. 이런 경우에는 TextInputFormatter
클래스를 직접 상속받아 우리만의 포매터를 만들 수 있습니다.
TextInputFormatter
를 상속받는 클래스는 formatEditUpdate
라는 메서드를 오버라이드해야 합니다. 이 메서드는 사용자가 키를 누를 때마다 호출되며, 이전 텍스트 값(oldValue
)과 새로운 텍스트 값(newValue
)을 인자로 받습니다. 우리는 이 메서드 안에서 원하는 로직을 구현하여 최종적으로 반환할 TextEditingValue
를 결정할 수 있습니다.
// 복잡한 규칙을 위한 커스텀 포매터 예시 (개념)
class MyComplexFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
// 여기에 복잡한 로직을 구현합니다.
// 예: newValue.text가 특정 규칙을 만족하는지 확인
// 규칙을 만족하면 newValue를 그대로 반환
if (/* 규칙 만족 조건 */) {
return newValue;
}
// 규칙을 만족하지 않으면 이전 값(oldValue)을 반환하여 입력을 무시
return oldValue;
}
}
이 방법은 유연성이 매우 높지만, 코드가 복잡해지고 모든 예외 케이스를 직접 처리해야 하는 부담이 있습니다. 따라서 대부분의 경우에는 FilteringTextInputFormatter
와 적절한 정규식을 조합하는 것만으로도 충분하며, 이 방법은 최후의 수단으로 고려하는 것이 좋습니다.
결론: 사용자를 배려하는 디테일의 중요성
Flutter에서 TextField
의 한글 입력 문제는 단순한 코드 한 줄의 실수가 아니라, 다양한 사용자 환경과 문자 인코딩의 복잡성을 간과했을 때 발생하는 대표적인 사례입니다. 특히 한국 시장을 목표로 하는 앱이라면, 두벌식 키보드뿐만 아니라 천지인, 나랏글, 베가 등 다양한 한글 입력 방식을 사용하는 사용자가 존재한다는 사실을 항상 인지해야 합니다.
이번에 살펴본 RegExp(r'[ㄱ-ㅎ|가-힣|ㆍ|ᆢ]')
패턴은 이러한 다양성을 고려한, 작지만 매우 중요한 디테일입니다. 이 해결책을 통해 우리는 기술적으로 올바른 코드를 작성하는 것을 넘어, 모든 사용자가 불편함 없이 우리 앱을 사용할 수 있도록 배려하는 개발자로서 한 걸음 더 나아갈 수 있습니다.
앞으로 텍스트 입력을 처리할 때는 다음의 체크리스트를 꼭 기억하세요.
- 요구사항 명확화: 정확히 어떤 문자(한글, 영어, 숫자, 공백, 특수문자)를 허용하고, 어떤 문자를 막아야 하는가?
- 입력 방식 고려: 타겟 사용자들이 사용할 가능성이 있는 모든 키보드 입력 방식(특히 조합형 문자)을 고려했는가?
- 정규식 검증: 작성한 정규식이 모든 엣지 케이스를 포함하는가? (예: 천지인의 'ㆍ', 'ᆢ')
- 실기기 테스트: 에뮬레이터뿐만 아니라 실제 Android 및 iOS 기기에서 다양한 키보드 설정으로 충분히 테스트했는가?
이러한 세심한 접근 방식은 앱의 완성도를 높이고 사용자의 만족도를 극대화하는 가장 확실한 방법이 될 것입니다.
0 개의 댓글:
Post a Comment