NSAttributedString 다국어설정 시 주의해야할점

Zion·2024년 6월 12일
0

소중한 야근의 결실 ☻

다국어 설정시 특정 언어(영어, 스페인어)...

를 제외한 다른 언어들의 화면이 뜨지 않는다!!!

Terminating app due to uncaught exception 'NSRangeException', reason: 'NSMutableRLEArray objectAtIndex:effectiveRange:: Out of bounds

오류는 Out of range라고 뜨는데 이게 어디서 뜨는건지... 당최 알 수가 없었다.
무엇의 범위가 바깥으로 나갔는지,
베트남어나 다른언어일때 화면이 그려지지 않는다!!

Before

        let text = CustomLocalizeHelper.localizedString(forKey: "key")
        let attributedString = NSMutableAttributedString.init(string: text)
        attributedString.addAttribute(
            NSAttributedString.Key.underlineStyle,
            value: 1,
            range: NSRange.init(
                location: text.startIndex.encodedOffset,
                length: text.endIndex.encodedOffset
            )
        )
        attributedString.addAttribute(
            NSAttributedString.Key.foregroundColor,
            value: UIColor.white,
            range: NSRange.init(
                location: text.startIndex.encodedOffset,
                length: text.endIndex.encodedOffset
            )
        )
        attributedString.addAttribute(
            NSAttributedString.Key.font,
            value: UIFont.systemFont(ofSize: 12, weight: .regular),
            range: NSRange.init(
                location: text.startIndex.encodedOffset,
                length: text.endIndex.encodedOffset
            )
        )

After

		let range = NSRange(location: 0, length: (text as NSString).length)
        attributedString.addAttribute(
            NSAttributedString.Key.underlineStyle,
            value: NSUnderlineStyle.single.rawValue,
            range: range
        )
        attributedString.addAttribute(
            NSAttributedString.Key.foregroundColor,
            value: UIColor.white,
            range: range
        )
        attributedString.addAttribute(
            NSAttributedString.Key.font,
            value: UIFont.systemFont(
                ofSize: 12,
                weight: .regular),
            range: range
        )

여기서 중요한 부분은 문자열의 길이를 계산하는 데 text.startIndex.encodedOffsettext.endIndex.encodedOffset 대신 text as NSString을 사용하여 문자열의 길이를 얻는 것입니다. NSRange(location:length:) 초기화에 이 값을 사용하면 범위가 정확히 지정됩니다.

text.startIndex.encodedOffsettext.endIndex.encodedOffset을 사용하는 것 자체는 문법적으로 문제가 없지만, 다국어 문자열에서 발생하는 문제는 인덱싱 방식과 관련이 있습니다.
특히, 다국어 문자열의 경우 단순히 startIndex와 endIndex를 사용하는 것이 올바르지 않을 수 있습니다. 이유는 다음과 같습니다:

문자열의 인덱싱 방식 차이

Swift의 String은 유니코드 스칼라 값을 사용하여 인덱싱되기 때문에, 단순히 startIndexendIndex를 사용하면 예상치 못한 결과를 초래할 수 있습니다.

특히 다국어 문자열에서는 유니코드 문자들이 여러 개의 코드 포인트로 구성될 수 있기 때문입니다.

NSString의 길이 사용: NSStringlength 속성은 NSString의 바이트 길이를 반환하며,
이는 다국어 문자열에서도 올바르게 작동합니다.
이는 특히 NSRange와 같은 NS 기반 API를 사용할 때 중요합니다.

인덱싱 방식 차이

유니코드 스칼라 값을 사용하여 인덱싱되기 때문에, 단순히 startIndexendIndex를 사용하면 예상치 못한 결과를 초래할 수 있습니다?

예를 들어, 특정 유니코드 문자는 여러 유니코드 스칼라 값으로 구성될 수 있으며, 이 경우 문자열의 실제 길이와 문자 수가 일치하지 않을 수 있습니다.

let text = "こんにちは"
let startIndex = text.startIndex
let endIndex = text.endIndex

print(text[startIndex]) // こ
print(text[endIndex])   // Error

여기서 text.endIndex는 문자열의 끝 이후를 가리키기 때문에, 직접 접근하려고 하면 오류가 발생합니다. 정확히 말하면 text.endIndex는 문자열의 마지막 유효 인덱스보다 하나 더 큰 인덱스를 가리킵니다. 따라서 이 인덱스로 문자열에 접근할 수 없습니다.

let endIndex = text.index(before: text.endIndex) 이렇게 사용해야 마지막 글자 가지고 올 수 있음.

또한, Swift 문자열에서 encodedOffset은 문자열의 UTF-8 인코딩에서의 바이트 오프셋을 의미하며, 이는 문자열의 길이와 항상 일치하지 않습니다. 특히 다국어 문자열에서는 더욱 그렇습니다.

UTF-8 바이트 오프셋

encodedOffset은 문자열의 UTF-8 인코딩에서의 바이트 오프셋을 의미하며, 이는 문자열의 길이와 항상 일치하지 않습니다.

예시

예를 들어, 영어 문자열과 다국어 문자열의 encodedOffset과 길이 차이를 살펴보겠습니다.

예시 1: 영어 문자열

let englishText = "hello"
let startIndex = englishText.startIndex
let endIndex = englishText.endIndex

print(englishText[startIndex]) // h
print(englishText[englishText.index(before: endIndex)]) // o

print(startIndex.encodedOffset) // 0
print(endIndex.encodedOffset)   // 5

예시 2: 다국어 문자열 (일본어)

let japaneseText = "こんにちは"
let startIndex = japaneseText.startIndex
let endIndex = japaneseText.endIndex

print(japaneseText[startIndex]) // こ
print(japaneseText[japaneseText.index(before: endIndex)]) // は

print(startIndex.encodedOffset) // 0
print(endIndex.encodedOffset)   // 15

설명

영어 문자열 (hello):
문자열의 길이는 5입니다.
각 문자는 1 바이트로 인코딩됩니다.
startIndex.encodedOffset은 0이고, endIndex.encodedOffset은 5입니다.

다국어 문자열 (こんにちは):
문자열의 길이는 5입니다.
각 일본어 문자는 3 바이트로 인코딩됩니다.
startIndex.encodedOffset은 0이고, endIndex.encodedOffset은 15입니다.

중요 차이점

영어 문자열의 경우, 문자열의 길이와 encodedOffset이 일치합니다. 이는 각 문자가 1 바이트로 인코딩되기 때문입니다.

다국어 문자열의 경우, 문자열의 길이와 encodedOffset이 일치하지 않습니다. 이는 각 문자가 여러 바이트(여기서는 3 바이트)로 인코딩되기 때문입니다.

이 차이는 특히 NSRange와 같은 범위를 설정할 때 중요합니다. encodedOffset을 잘못 사용하면 범위 오류가 발생할 수 있습니다. 따라서 문자열의 길이를 계산할 때는 UTF-8 바이트 오프셋 대신 NSString의 length 속성을 사용하는 것이 더 안전합니다.

다시 원래 코드로 돌아가면, 문자열의 범위를 설정할 때 Swift 문자열의 인덱스를 직접 사용하지 않고 NSString을 사용하여 범위를 계산하는 이유를 이해할 수 있습니다.
이는 특히 다국어 문자열에서 인덱스가 정확히 맞지 않아 발생하는 오류를 방지하기 위함입니다.

결론

다국어를 사용할땐 length를 이용해 text 범위로 사용하자!

profile
어제보다만 나아지는

0개의 댓글