다국어 입력 환경에서의 사용자 경험 향상 방법
Finn • Bonwook Koo, iOS Engineer
안녕하세요, 채널톡 모바일팀 iOS 엔지니어 핀입니다.
채널톡 채팅 메시지에는 다양한 콘텐츠와 함께 텍스트를 입력할 수 있으며, 단순히 텍스트를 넘어 다양한 서식을 지원합니다. 기존 모바일 앱에서는 이러한 풍부한 콘텐츠 입력을 지원하지 않았지만, 최근 대대적인 업데이트를 통해 다양한 콘텐츠 삽입과 텍스트 서식 지원을 시작했습니다. 아직 모든 기능을 완벽하게 지원하는 건 아니지만, 꾸준히 개선해 나갈 예정입니다. 이러한 변화의 과정에서 채팅 메시지 입력 시 다양한 서식을 적용하기 위해 고민했던 부분들이 있습니다. 이번 포스팅에서는 그 중에서 iOS 앱에서 구현중에 사용자 텍스트 입력 처리 관련하여 겪었던 문제와 이를 해결하기 위해 했던 고민을 공유하고자 합니다.
채널톡 iOS 채팅 입력기의 서식 입력 기능
채널톡 iOS 앱에서는 사용자가 입력한 채팅 메시지를 처리하기 위해 NSTextStorage
를 사용합니다. 이는 NSMutableAttributedString
과 유사하지만, 내용 편집에 있어 더 강력한 기능을 제공합니다. 특히 NSTextStorageDelegate
를 구현하면 NSTextStorage
의 내용이 변화하는 시점을 정확하게 인지할 수 있습니다. UITextView
와 같은 뷰를 통해 사용자 텍스트 입력을 처리하는 경우, NSTextStorageDelegate
를 활용하는 것이 텍스트 변화를 감지하는 가장 효율적인 방법입니다.
NSTextStorageDelegate
는 NSTextStorage
객체의 문자나 속성(Attribute)에 변화가 발생하기 직전과 처리가 완료된 후에 각각 한 번씩 메서드를 호출합니다. 이 흐름은 사용자가 UITextView
에 포커스를 두고 타이핑 할 때도 동일하게 적용됩니다. UITextView
내부에 있는 NSTextStorage
의 값이 변경되면, 등록된 NSTextStorageDelegate
는 값 변경 과정에 따라 메서드를 호출하게 됩니다. 아래 코드를 통해 그 구조를 확인할 수 있습니다.
만약 이 NSTextStorageDelegate
구현 메서드에서 인자로 전달받은 textStorage.string
을 출력한다면 어떻게 될까요? 영어 입력의 경우, 만약 사용자가 "abc"를 입력하면 "a", "ab", "abc"가 순서대로 출력될 것입니다. 그렇다면 한글 입력의 경우는 어떨까요? "안"이라는 문자를 입력한다고 가정하면, 사용자는 "ㅇ", "ㅏ", "ㄴ"을 차례로 입력했을 겁니다.
한국어 원어민의 입장에서는 "ㅇ" -> "아" -> "안"이 자연스럽게 출력될 것이라고 예상하기 쉽지만, 실제로는 그렇지 않습니다. "ㅇ" -> "아" -> "안"의 과정 사이사이에 문자가 지워지고 다시 채워지는 복잡한 과정이 발생합니다. 다음 시퀀스 다이어그램을 통해 이 과정을 이해할 수 있습니다.
“안”을 입력하는 상황에서의 시퀀스 다이어그램
일반적인 텍스트 입력 상황에서는 이러한 과정이 문제가 되지 않습니다. 하지만 텍스트 입력기에서 서식 적용을 지원한다면 어떨까요? 예를 들어 사용자가 "ㅇ" -> "ㅏ" -> "ㄴ" 순으로 문자를 입력하던 중, "ㅏ" 입력 직후에 볼드 스타일을 적용하는 버튼을 눌렀다고 가정해 봅시다. 이 경우 사용자의 의도는 무엇일까요?
최종적으로 입력할 "안"에 볼드를 적용하고 싶었다.
이미 입력한 "아"에 대해서만 볼드를 적용하고 싶었다 (최종 "안"에는 적용하지 않음).
첫 번째 경우, 우리는 사용자가 "안"에 볼드를 적용하고 싶었는지 알 수 있는 방법이 없습니다. 사용자는 아직 "아"까지만 입력했기 때문입니다. 코드상으로 사용자가 입력할 수 있는 모든 경우를 대비하면 어떻게든 구현은 되겠지만, 현실적으로 빠르게 입력되는 사용자 입력을 처리하기 위해서는 좋은 방법은 아닐 것입니다. 만약 사용자 입력의 미래를 100% 정확하게 예측할 수 있다면 좋겠지만, 아쉽게도 그러기는 어렵습니다.
두 번째 경우, "아" 자체에 볼드를 적용하는 것은 가능합니다. 하지만 사용자가 "ㄴ"을 입력하면 이미 볼드 스타일이 적용된 "아"는 삭제되고 새로운 "안" 문자가 추가됩니다. 그렇다면 우리는 새로 추가된 문자인 "안"에 볼드를 다시 적용해야 할까요? 만약 그렇다고 한다면, 다소 복잡하긴 하지만 구현은 가능할 것입니다. 하지만 "안"을 입력한 사용자가 바로 뒤에 "ㅣ"를 입력하여 최종 결과가 "아니"가 된다면 어떨까요? 이때도 "안" -> "" -> "아니"의 과정을 거치게 됩니다. 이때도 사용자는 "아"에 볼드를 적용하고 싶었을까요? 어쩌면 "니"에만 적용하고 싶었던 것은 아닐까요?
결국, 미래를 100% 정확하게 예측할 수 없다면 사용자의 의도를 완전히 이해하기는 어렵습니다. 설령 미래를 예측했다고 해도, 사용자의 의도를 최대한 이해하려고 노력할 수 있을 뿐 실제 의도가 무엇이었는지는 알 수 없습니다. 사용자 역시 실수를 할 수 있으니까요. 결국 사용자의 의도를 예측할 수 없다면 스타일 버튼을 클릭한 시점 이전의 텍스트 입력은 이미 완결된 텍스트 입력이라고 인지하기로 했습니다.
예를 들어 "아"를 입력한 상황에서 사용자가 볼드 버튼을 눌렀다면, "아"는 이미 입력이 완료된 문자로 간주하는 것입니다. 만약 그 뒤에 "ㄴ"을 입력하면 "아ㄴ"이라는 결과가 나타나며, 물론 "ㄴ"에는 볼드 스타일이 적용됩니다.
가나(일본 문자) 입력 경우 한글과는 또 다른 형태로 변화합니다. "おはよう"라는 문자열을 입력하는 상황이라고 생각해볼까요? 실제 입력 과정에 대해 이야기하기 전에, "おはよう" 대신 동일한 발음을 영어로 입력하는 "ohayo"라는 문자열을 입력하는 상황을 가정해보겠습니다.
앞에서도 얘기했듯이 사용자 입력에 의한 텍스트 변경을 감지하기 위해 NSTextStorageDelegate
를 사용하고 있습니다. 이 때 NSTextStorageDelegate
는 textStorage
에 변경이 일어날 때마다 변경된 영역만큼의 범위를 NSRange
형태로 알려줍니다 (위에서 보여드린 Delegate 메서드의 editedRange
인자를 참고). 예를 들어 "oh"까지 입력된 상태에서 "a"를 입력하면 editedRange
인자를 통해 location: 2
, length: 1
의 변경이 일어났음을 알려주는 식입니다.
하지만 "おはよう"를 입력하는 경우는 약간 다릅니다. iOS에서 기본적으로 제공하는 입본어 입력 방식에는 “Romaji”와 “Kana”가 있어 조금 형태가 다르지만 일단 여기에서는 최대한 단순화해볼게요.
"おは"까지 입력된 상황에서 "よ"를 추가로 입력한 상황을 가정해 봅시다. 이때는 위에서 언급한 영문자 입력 케이스와 달리 editedRange
가 location: 0
, length: 3
으로 전달됩니다. 즉, "おは" 뒤에 하나의 문자가 추가된 것이 아니라 전체가 덮어씌워지는 방식으로 동작하는 것입니다. 한글을 입력할 때는 이전 문자를 삭제하고 새로 삽입했던 것과는 달리, 새로 덮어씌워지는 형식이라는 차이가 있습니다.
사실 이는 iOS의 UITextInput
에서 설명하는 Marked Text라는 개념 때문에 발생하는 현상입니다. Marked Text는 UIKit이나 AppKit 등에서 통용되는 개념으로, 이에 대해 상세히 설명된 문서는 별도로 제공되지 않습니다. 다만 UITextInput
에 대한 Apple 개발자 문서의 Overview 항목에서 이에 대한 간략한 설명을 볼 수 있습니다. 일부 문구를 발췌하자면 아래와 같습니다.
Marked text, a part of multistage text input, represents provisionally inserted text that the user has yet to confirm.
결론적으로 Marked Text는 다단계 텍스트 입력 상황에서 사용자가 입력했지만 아직 확정하지 않은 텍스트를 의미합니다. 위에서 "おは"가 "おはよ"로 변경될 때 editedRange
가 전체 영역을 아우르고 있었던 이유는 확정되지 않은 상태에서 텍스트가 입력되었기 때문에 전체 텍스트를 대체하는 식으로 동작한 것입니다. 가나 입력 환경에 익숙한 분이라면 아시겠지만, 이러한 이유 때문에 가나 입력 키보드를 사용해 보면 문자를 입력하는 중에 엔터키가 위치해야 할 버튼에 "確定", 즉 "확정"이라고 쓰여 있습니다.
확정 전과 후의 Enter 버튼 텍스트 차이
한국어 입력의 경우와 비슷하게, 입력이 확정되지 않은 상태에서 텍스트 스타일을 적용하는 상황이라면 어떨까요? 여전히 사용자가 최종적으로 입력하고 싶어 하는 텍스트가 무엇인지는 알 수 없기 때문에, 이전까지 입력한 텍스트를 강제로 확정(unmark)시키고 그 이후에 입력되는 텍스트에 스타일을 입히는 방식으로 접근하고 있습니다.
가나 문자를 예시로 들었지만, 이 외에도 다단계 텍스트 입력이 발생하는 문자는 모두 Marked Text 개념이 적용됩니다. 따라서 다국어 입력 환경을 지원하는 상황이라면 이러한 부분을 반드시 고려해야 합니다. 다만 한글 입력의 경우에는 다단계 텍스트 입력으로 판정되지 않고 텍스트가 두 번 변경되는 (삭제 → 삽입) 식으로 동작하게 된 이유는 아직 명확히 파악하지 못했습니다.
이번 포스팅에서는 채널톡 iOS 앱에 텍스트 서식 기능을 추가하면서 겪었던 기술적인 도전과 그 해결 과정을 공유해 드렸습니다. 특히 한글과 일본어 같은 다국어 입력 환경에서 NSTextStorage
와 Marked Text 개념이 어떻게 복잡성을 더하는지, 그리고 이를 어떻게 해결했는지에 대한 저희의 고민을 엿볼 수 있으셨기를 바랍니다.
사용자의 의도를 완벽하게 예측하는 것은 어려운 일이지만, 저희는 현재 상황에서 최선의 사용자 경험을 제공하기 위해 끊임없이 노력하고 있습니다. 아직 모든 기능이 완벽하게 지원되는 것은 아니지만, 앞으로도 꾸준한 업데이트를 통해 더 풍부하고 편리한 채팅 경험을 제공할 수 있도록 최선을 다하겠습니다.
We Make a Future Classic Product