Primary/Compose
TextField에 default string을 줘도 커서가 맨 앞으로 가는 이유
해스끼
2024. 2. 28. 21:03
문제
초기 문자열이 있는 ``TextField``를 다음과 같이 선언하였다.
@LightPreview
@Composable
private fun TextFieldPreview() {
var textFieldValue by remember { mutableStateOf("검색하세요") }
KuringTheme {
TextField(
value = textFieldValue,
onValueChange = {
textFieldValue = it
}
)
}
}
프리뷰를 실행해 보면, 처음부터 문자열이 있음에도 불구하고(``검색하세요``) 커서가 맨 앞에 있는 모습을 확인할 수 있다. 실제로는 문자열의 맨 끝에 와야 자연스럽다.
원인
원인을 파악하기 위해 ``TextField``의 내부 구현을 뜯어보자.
우선, ``TextField``는 ``String``을 매개변수로 받는 오버로딩(이하 S)과, ``TextFieldValue``를 매개변수로 받는 오버로딩(이하 T)이 있다. S를 호출하면 S 내부에서 ``TextFieldValue``를 만들어서 T에 넘겨주는 방식이다.
그런데 ``TextFieldValue``의 생성자를 보면, 커서의 위치를 나타내는 ``selection``의 기본값이 ``TextRange.Zero``이다. 즉 명시적으로 값을 넘겨주지 않는 이상, 커서는 언제나 맨 앞에 위치한다는 것.
해결법?
따라서 커서의 위치를 문자열 맨 뒤로 옮기려면, ``TextFieldValue``의 selection 매개변수를 문자열 맨 뒤로 지정하여 ``TextField``에 넘겨줘야 한다.
@LightPreview
@Composable
private fun TextFieldPreview() {
val initialString = "검색하세요"
var textFieldValue by remember {
mutableStateOf(
TextFieldValue(
text = initialString,
selection = TextRange(initialString.length),
)
)
}
KuringTheme {
TextField(
value = textFieldValue,
onValueChange = {
textFieldValue = it
},
)
}
}
이론적으로는 위 코드를 실행하면 커서의 초기 위치가 맨 뒤여야 하지만, ``TextField``를 클릭하는 순간 selection의 값이 0으로 할당된다.
진짜 해결법
따라서 다음과 같이 맨 처음에만 새 ``TextFieldValue``의 업데이트를 막으면 된다.
@LightPreview
@Composable
private fun TextFieldPreview() {
val initialString = "검색하세요"
var textFieldValue by remember {
mutableStateOf(
TextFieldValue(
text = initialString,
selection = TextRange(initialString.length),
)
)
}
var isInitial by rememberSaveable { mutableStateOf(true) }
KuringTheme {
TextField(
value = textFieldValue,
onValueChange = {
if (!isInitial) {
textFieldValue = it
}
isInitial = false
},
)
Text(
text = textFieldValue.selection.toString(),
)
}
}