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