Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
Tags
- appstore
- iOS16
- Archive
- geofencing
- Realm
- view
- 개발자
- Firebase
- 한글
- 웹뷰
- JPA
- Swift
- mac
- window
- rxswift
- FLUTTER
- Git
- MacOS
- Session
- Code
- SwiftUI
- github
- UIButton
- IOS
- darkmode
- stack
- Xcode
- Notification
- error
- Apple
Archives
- Today
- Total
EEYatHo 앱 깎는 이야기
Swift ) UILabel 크기 미리 알기, truncated 파악 - EEYatHo iOS 본문
반응형
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 찾는 것을 일반화하는게 아직 내 머리론 안되서.. 🤔
흠
'iOS, Swift' 카테고리의 다른 글
iOS ) iOS 16 개발자 모드 - EEYatHo iOS (0) | 2022.11.01 |
---|---|
Swift ) 웹뷰 Status Code 테스트, 에러처리 stub - EEYatHo iOS (0) | 2022.06.17 |
Swift ) 여러 기본 앱 URL schemes - EEYatHo iOS (0) | 2022.04.11 |
Swift ) 런치스크린, 스플래시 이미지 안보일 때 - EEYatHo iOS (0) | 2022.01.18 |
iOS ) 애플 스토어 커넥트 주소 오류 - EEYatHo iOS (0) | 2022.01.17 |
Comments