Sure, Why not?

UITextView에서 한글 스타일 적용 본문

💻

UITextView에서 한글 스타일 적용

joho2022 2025. 10. 13. 06:00

 

 

UITextView에서 볼드, 이탤릭과 같은 스타일을 적용하다가

영어는 바로바로 스타일적용이 되고, 이탤릭도 바로 적용이 되는 반면,

한글만 유독 매끄럽지 않다는 걸 경험했다.

 

흥미롭게도 기본 애플 메모 앱에서도 스타일 전환 시 영어나 일본어와 다르게

내가 생각하는 것과 달리

한글 역시 동일하게 매끄럽게 동작하지 않았다 

 

왜 한글은 스타일 적용 후 스페이스 한번을 해야 제대로 적용이 되는걸까...

 

그 이유를 짚고, 내가 선택한 기준과 구현방법을 정리하고자 한다.

 


 

텍스트 스타일 적용

첫 번째로 텍스트에 다양한 글꼴, 색상 밑줄 등등 여러 스타일을 적용하기 위해 대표적인 방법들로

NSAttributedString, NSMutableAttributedString 그리고 최근에 나온 AttributedString 중심으로

비교하고자 한다.

 

NSAttributedString

텍스트에 스타일을 적용할 수 있는 객체이다.

특징은 객체가 생성된 후에 스타일을 추가하거나 변경할 수 없다.

 

변경할려면 복사해서, NSMutableAttributedString 로 스타일 변경 가능함

 

Key로 속성과 그에 맞는 값을 설정하며, 설정할 수 있는 종류들은 아래에 나와있다.

 

NSAttributedString.Key | Apple Developer Documentation

The attributes you apply to ranges of characters in an attributed string.

developer.apple.com

 

예시)

let attributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 24, weight: .bold),
            .foregroundColor: UIColor.blue
]

let attributedString = NSAttributedString(string: "NSAttributedString!", attributes: attributes)

 

NSAttributedString

 

 


 

NSMutableAttributedString

NSAttributedString을 상속받은 클래스로,

생성 후에도 속성을 자유롭게 추가와 변경할 수 있고, 특정 범위(NSRange)에만 폰트,색상,밑줄 등의 스타일을 입힐 수 있다.

 

예시)

let mutableString = NSMutableAttributedString(string: "NSMutableAttributedString!")

 mutableString.beginEditing()

 mutableString.addAttributes([
     .font: UIFont.systemFont(ofSize: 24, weight: .bold)
 ], range: NSRange(location: 0, length: mutableString.length))

 mutableString.addAttributes([
     .foregroundColor: UIColor.systemRed,
     .underlineStyle: NSUnderlineStyle.single.rawValue
 ], range: NSRange(location: 0, length: 9))

 mutableString.endEditing()

 

 


 

AttributedString

 

iOS 15 이상부터 사용이 가능하고,

타입의 프로퍼티에 스타일을 설정가능해서 안전하고 현대적인 방법

SwiftUI 환경에서 스타일 다룰 때 권장한다. 물론 UIKit도 가능

마크다운을 지원한다.

 

예시)

var attributedString = AttributedString("AttributedString!")
attributedString.font = UIFont.systemFont(ofSize: 20, weight: .bold)
attributedString.foregroundColor = UIColor.purple

let markdownString = try? AttributedString(
    markdown: "This is a **bold** text and _italic_ text."
)

 

 


 

Glyph ?

먼저 글리프 이해가 필요하다.

폰트 파일은 단순히 글자 하나하나 모양 정보만 담고 있는 것이 아니라,

각 문자에 해당하는 글리프 라는 시각적인 표현을 담고 있다.

 

 

Typographical Concepts

Typographical Concepts This chapter defines some important typographical concepts relevant to the text system. Many of the terms representing these concepts are reflected in text system APIs. If you’re familiar with typography, you can skip this chapter.

developer.apple.com

 

 

 

A라는 문자에 여러 모양이 존재하는 것처럼 말이다.

 

대부분 한글 폰트는 한글 글자에 대한 기울어진 글리프를 가지고 있지 않다.

그래서 traitItalic 를 적용하더라도, 해당 글리프가 존재하지 않아서 한글은 기울기가 적용되지 않는다. 

 

 

CGAffineTransform를 이용한 한글 기울기 표현

그래서 CGAffineTransform 를 이용하는 방법이 있다.

 

2D 그래픽을 그리는데 위치, 크기, 회전 등등 모양을 변형시키는 행렬(matrix) 기반의 구조체이다.

 

UIFontDescriptor는 폰트의 속성을 담고 있는 구조체인데,

UIFontDescriptor를 사용해서 폰트의 정보를 가져온다.

 

CGAffineTransform를 생성하고, 기울기를 담당하는 슬랜트값을 설정하여 행렬을 만들어준다.

양수일수록 오른쪽, 음수일수록 왼쪽으로 기울어진다.

 

그리고 기존 Descriptor에 매트릭스를 적용하고 UIFont를 생성하면 기울기적용된 폰트를 적용할 수 있다.

 

 

예시)

extension UIFont {
    func withSlant(degrees: CGFloat = 15) -> UIFont {
        let radians = degrees * .pi / 180
        let shear = tan(radians)                     
        let matrix = CGAffineTransform(a: 1, b: 0, c: shear, d: 1, tx: 0, ty: 0)
        let desc = fontDescriptor.withMatrix(matrix)
        return UIFont(descriptor: desc, size: pointSize)
    }
}

 

 

 

 


텍스트 입력기 관찰을 위해 NSTextStorage

언어별 텍스트 입력기가 실제로 어떻게 동작되는지 비교하기 위해,

NSTextStorage로 입력을 관찰할 수 있는 NSTextStorageDelegate 를 활용했다.

 

먼저, NSTextStorage 는 텍스트 뷰의 내용을 저장하고 관리하는 클래스이다.

단순히 문자만이 아닌, 스타일와 같은 속성도 관리한다.

NSMutableAttributedString 의 상속받기 때문에 스타일을 자유롭게 변경이 가능하다.

 

입력 중 발생하는 조합상태나 입력 확정 시점을 파악이 가능하다.

 

사실 iOS 15 이상에서는 NSTextStorageObserving 도 고려할 수도 있지만,

편집 전과 후 시점을 명확히 구분할 때 NSTextStorageDelegate 사용하는 것이 직관적으로 느껴져 선택했다.

 

extension VC: NSTextStorageDelegate {
    
    //    before
    func textStorage(_ textStorage: NSTextStorage,
                     willProcessEditing editedMask: NSTextStorage.EditActions,
                     range editedRange: NSRange,
                     changeInLength delta: Int) {
        
    }
    
    //    after
    func textStorage(_ textStorage: NSTextStorage,
                     didProcessEditing editedMask: NSTextStorage.EditActions,
                     range editedRange: NSRange,
                     changeInLength delta: Int) {
        
    }
    
}

 

언어별 텍스트 입력 동작 비교

 

abc 입력했을 때

영어 입력의 경우,

새로운 문자를 입력할 때마다 location 값이 0 → 1 → 2로 증가하는 것을 확인할 수 있었다.

location은 편집이 시작된 위치를, length는 편집된 문자열의 길이를 의미한다.

 

즉, 각 입력이 한 글자 단위로 독립적인 편집 사이클을 형성? 되는 모습을 볼 수 있었다.

 

 

 

 

 

ともだち 입력하고 확정 후 아무 글자 입력 했을 때

 

일본어 경우에는,

 

사진과 같이 Marked Text라는 개념을 바탕으로 아직 확정되지 않은 글자 초안 같은 것을 다룬다.

키보드에 확정 버튼을 누르면 location이 4로 바뀌는 것을 확인할 수 있다.

 

 

'안' 입력했을 때

 

한글은 예상과 다르게 동작한다.

조합 과정이 있으니 length가 점점 늘어날 줄 알았지만,

실제로는 이전 글자를 삭제하고 새로운 글자를 삽입하는 방식으로 작동한다.

 

그래서 그런지 애플 기본 메모 앱에서도 폰트를 변경한 직후 한글을 입력하면

새로 입력된 글자에 변경된 폰트가 아닌 이전 폰트가 적용되는 현상이 발생한다.

 

나는 폰트적용시 바로 스페이스바 없이 적용된 한글입력을 원했기 때문에

 

편집이 완료된 후 textStorage에 현재 폰트 속성을 다시 보정하는 방식으로

한글 입력 시에도 스타일이 매끄럽게 유지되도록 구현하였다.

 


결과

사용자가 폰트 스타일 버튼 토글한 그 시점에 텍스트가 바로 적용되도록 기준을 잡았다.

( 기본 메모앱 )한글에 스타일 바로 적용 안됨 / (test 앱) 스타일 바로 적용되도록

 

 

Reference

 

채널톡 iOS 채팅 입력기의 작은 비밀

다국어 입력 환경에서의 사용자 경험 향상 방법

channel.io