EEYatHo 앱 깎는 이야기

Swift ) UILabel 크기 미리 알기, truncated 파악 - EEYatHo iOS 본문

iOS, Swift

Swift ) UILabel 크기 미리 알기, truncated 파악 - EEYatHo iOS

EEYatHo 2022. 4. 20. 18:53
반응형

 

 

boundingRect


Swift에는 NSString, NSAttributedString를 감싸는 CGRect를 계산해주는 메소드가 있다.

extension NSString {
    // ...
    @available(iOS 7.0, *)
    open func boundingRect(with size: CGSize, options: NSStringDrawingOptions = [], attributes: [NSAttributedString.Key : Any]? = nil, context: NSStringDrawingContext?) -> CGRect
}

extension NSAttributedString {
    // ...
    @available(iOS 6.0, *)
    open func boundingRect(with size: CGSize, options: NSStringDrawingOptions = [], context: NSStringDrawingContext?) -> CGRect
}
  • Size는 max값을 넣어주면 된다. (보통은 높이를 구하는 용도라, 넓이에 제한을 둔다.)
  • options는 .usesLineFragmentOrigin 사용 (이유는 정확히 모름 문서 링크)
  • attributes을 넣어도 되고, NSAttributedString,boundingRect를 사용해도 된다.
  • context는 nil (이유 정확히 모름 문서 링크)

 

여기서 반환되는 CGRect가

UILabel에서 사용하는 inrinsicContentSize와 같기 때문에,

UILabel의 크기를 미리 계산할 수 있다.

 

 

 

사용예) UILabel 높이 구하기


func getLabelHeight(att: NSAttributedString, labelWidth: CGFloat) -> CGFloat {
    let rect = CGSize(width: labelWidth, height: CGFloat.greatestFiniteMagnitude)
    let labelSize = att.boundingRect(with: rect, options: .usesLineFragmentOrigin, context: nil)
    return ceil(labelSize.height * 2) / 2
}

 

마지막 ceil 처리는, 실제로 Layout 될는 height값이 0.5 단위로 올림되기 때문에 넣어주었다.

(ex. 70.2 -> 70.5 , 65.8 -> 66.0)

 

주의할 점으로,

  • 당연히 font를 적용한 값을 넣어야 정확한 높이가 나온다.

  • attribute 자체적으로 NSLineBreakMode를 포함할 수 있는데,
    생략을 포함하는 Mode(2,3,4,5)인 경우, 무조건 1줄짜리 높이가 리턴된다.
public enum NSLineBreakMode : Int {
    case byWordWrapping = 0 // Wrap at word boundaries, default 단어 단위로 줄바꿈

    case byCharWrapping = 1 // Wrap at character boundaries 1글자 단위로 줄바꿈

    case byClipping = 2 // Simply clip 짜르고 그냥 안보여줌

    case byTruncatingHead = 3 // Truncate at head of line: "...wxyz" 앞부분 점처리

    case byTruncatingTail = 4 // Truncate at tail of line: "abcd..." 뒷부분 점처리

    case byTruncatingMiddle = 5 // Truncate middle of line:  "ab...yz" 중간부분 점처리
}

 

 

 

응용) lineBreak, truncate 되었는지 파악하기 ( isTruncated )


func isTruncated(targetAtt: NSAttributedString,
                        maxAtt: NSAttributedString,
                        labelWidth: CGFloat) -> Bool {
    let targetHeight = getLabelHeight(att: targetAtt, labelWidth: labelWidth)
    let maxHeight = getLabelHeight(att: maxAtt, labelWidth: labelWidth)
    return targetHeight > maxHeight
}
let labelNumberOfLine = 4
if labelNumberOfLine < 1 {
    print("Not Truncated")
}

let labelWidth = UIScreen.main.bounds.width - 64

let targetString = "실제로 넣어야 하는 String값"
let targetAtt = NSAttributedString(string: targetString)
...
// targetAtt addAttributes 작업.
// font, linespacing, lineHeight 등.
// lineBreakMode는 넣지않도록 주의.


var maxLineString = "0"
(1...labelNumberOfLine).forEach { _ in maxLineString.append("\n0")}
let maxLineAtt = NSAttributedString(string: maxLineString)
...
// maxLineAtt addAttributes 작업.
// targetAtt와 동일하게 해야함.

if isTruncated(targetAtt: targetAtt, maxLineAtt: maxLineAtt, labelWidth: labelWidth) {
    print("Truncated")
} else {
    print("Not Truncated")
}

아이디어는 간단하다.

numberOfLine으로 최대 높이를 구하고, 넘으면 Truncated 되었다고 판단.

 

작동은 잘 된다..

하지만 호출 부분이 너무 지저분하다 ㅠ

 

명료한 하나의 메소드로 모든게 해결되는 멋진 상황을 만들고 싶은데,

 

attribute를 특정 범위에만 지정해줄 수 있다는 점 때문에,

font, lineSpacing, lineHeight 등이 일정하지 않은 NSAttributedString인 케이스가 존재하고,

이 때의 maxHeight 찾는 것을 일반화하는게 아직 내 머리론 안되서.. 🤔

 

 

Comments