HTML String을 AttributedString으로 변환 시 Font 적용하기

김동현·2022년 9월 12일
0

trouble_shooting

목록 보기
1/1
post-thumbnail


여러분의 앱에서 노출되는 모든 텍스트는
하드코딩으로 작업되어 있나요?
필요에 따라 부분적으로 서버에서 텍스트를 받아와
노출시키는 영역도 있을 것 입니다.

만약 서버에서 텍스트를 받아온다면,
해당 텍스트는 한글로 바로 받아와 사용할 수 있는 상태인가요?

그럴수도 있겠지만
html의 형태로 받는 케이스도 있을텐데요.
(제가 그랬어요.)
그와중에 마주한 문제도 있었구요.

오늘은 그 문제를 해결했던 방법에 대해서 글을 써볼까 합니다.

일단 이슈는 이러했습니다.
위에서 설명을 드렸다시피 노출할 text 값은
서버에서 받아와 설정 해준다고 가정을 하겠습니다.
(html 형태의 String 타입 값)

"<b>12</b> 34 56 <b>78</b>"


html을 NSMutableAttributedString 포멧으로 변환해서
노출시키는 기본적인 방법을 사용하여 진행해보겠습니다.

let resultAtt = NSMutableAttributedString()
let data = "<b>12</b> 34 56 <b>78</b>".data(using: .utf8)
let att = try! NSAttributedString(data: data!, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding:String.Encoding.utf8.rawValue], documentAttributes: nil)
resultAtt.append(att) 
label.attributedText = resultAtt


노출되는 기대값은 12, 78이 bold 처리가 되어있어야 하겠죠.
잘 나오네요.
그런데 attribute의 로그를 찍어서 확인 해보면
font-family: \"TimesNewRomanPS-BoldMT\"
로 확인이 되네요.

html을 한글로 인코딩하는 작업중
TimesNewRomanPS-BoldMT 폰트로 변경이 되버린 것 같아요.

맞아요. 문제가 발생했습니다!
SFUI-Bold폰트가 아닌 다른 폰트로 적용이 되버린 것이죠.

폰트를 SFUI-Bold로 명시해서 내려주라고 부탁하면 되는거 아니야?
라고 당연히 생각 해봤습니다.

동시에 혹시나 앱에서 이 상황을 해결 할만한 솔루션이 있지 않을까?
라고도 생각해봤고요.

검색을 해봤는데 역시나 있더라구요.😀

 private func getAttribute(htmlText: String, withRegularFont regularFont: UIFont, andBoldFont boldFont: UIFont) -> NSMutableAttributedString {
    var att = NSMutableAttributedString()
    guard let data = htmlText.data(using: .utf8) else { return NSMutableAttributedString() }
    do {
        att = try NSMutableAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil)
        let range = NSRange(location: 0, length: att.length)
        att.enumerateAttribute(.font, in: range, options: .longestEffectiveRangeNotRequired)
        { value, range, _ in
            let currentFont: UIFont = (value as? UIFont) ?? .init()
            var replacementFont: UIFont?
            if currentFont.fontName.contains("bold") || currentFont.fontName.contains("Bold") {
                replacementFont = boldFont
            } else {
                replacementFont = regularFont
            }
            let replacementAttributeFont = [NSAttributedString.Key.font: replacementFont ?? .init()]
            att.addAttributes(replacementAttributeFont, range: range)
        }
    } catch let error {
        print(error.localizedDescription)
    }
    return att
}

label.attributedText = getAttribute(
	htmlText: "<b>12</b> 34 56 <b>78</b>",
	withRegularFont: UIFont.systemFont(ofSize: 14, weight: .regular),
	andBoldFont: UIFont.systemFont(ofSize: 14, weight: .bold))


해결 방법은 enumerateAttribute라는 메서드를 사용하는 것이였습니다.
htmlText를 인코딩하고
추출된 NSMutableAttributedString를 기반으로
enumerateAttribute 메서드를 호출시키는 방법이지요.

여기서 중요한건 호출 시 첫번째 인자로 .font를 넣어줬다는 것입니다.
.font를 사용해서 NSMutableAttributedString 메서드를 호출하면
loop를 진행하면서 AttributedString의 모든 케릭터를 순회하게 되는데,
이때 하나의 캐릭터마다의 font를 얻을 수가 있어요.

그 폰트가 bold라면 위에서 확인했다시피 TimesNewRomanPS-BoldMT 이겠죠?
저희는 SFUI-Bold 가 필요한데 말이죠.
그럼 이제 TimesNewRomanPS-BoldMT인 캐릭터를 찾았으니
SFUI-Bold로 변경해주기만 하면 되겠네요.
위 로직처럼 UIFont.systemFont로 설정해주겠습니다 .
이제 의도한대로 만족스럽게 잘 나오네요🙌


+ 위에서 사용한 enumerateAttribute 메소드에서
폰트뿐만 아니라 다른 attribute 속성들을 기준으로 enumerate를 돌릴 수도 있습니다!

profile
iOS 개발자 김동현입니다 :)

0개의 댓글