이 포스트는 UILabel에서 text의 길이가 numberOfLines 값을 넘었을때 나오는 생략부호(... 또는 Ellipsis 또는 three dot)을 바꾸기 위한 포스팅입니다 :)
실제로 바꾸는 것은 아니고 그렇게 보이도록 하는 방법입니다.
최종 수정일 - 22. 09. 1 PM 3:20
방법
1. replaceEllipsis(with string: String) 함수를 생성
extention UILabel {
func replaceEllipsis(with string: String) {
guard let text = self.text else { return }
lineBreakMode = .byClipping
// STEP 0: Ellipsis가 필요 없는 경우 return
if numberOfLine(for: text) <= self.numberOfLines {
return
}
}
}
2. UILabel의 text를 분리하기
let stringArray = text.components(separatedBy: "\n")
3. 분리한 String 마다 UILabel에 대입하여 numberOfLine 값 구하기
var numberOfLines: Int = 0
var index: Int = 0
while !(numberOfLines >= self.numberOfLines) {
guard let string = stringArray[safe: index] else { break }
let numberOfLine = numberOfLine(for: string)
numberOfLines += numberOfLine
if !(numberOfLines >= self.numberOfLines) { index += 1 }
}
3-1 numberOfLine을 구하는 함수
fileprivate func numberOfLine(for text: String) -> Int {
guard let font = self.font, text.count != 0 else { return 0 }
let rect = CGSize(width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude)
let labelSize = text.boundingRect(with: rect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
let numberOfLine = Int(ceil(CGFloat(labelSize.height) / font.lineHeight))
return numberOfLine
}
4. 구한 numberOfLine을 더하면서 UILabel의 numberOfLines 값과 같거나 커지는 index 값으로 String 구하기
guard let last = stringArray[safe: index] else { return }
5. 원하는 Ellipsis를 마지막에 붙였을때 UILabel의 numberOfLines 값을 넘지 않도록 마지막을 제거
var result = stringArray[0..<index].joined(separator: "\n") + "\n" + last
while !(numberOfLine(for: result + string) == self.numberOfLines) {
result.removeLast()
}
6. 완성된 String 값 적용
result += string
self.text = result
self.sizeToFit()
7. 사용 방법
@IBOutlet weak var label: UILabel!
label.numberOfLines = 3
label.text = /* TEXT */
label.replaceEllipsis(with: "... 더보기")
7. 결과
응용 01 - 강조 표시
1. AttributedString 을 이용한 강조
func replaceEllipsis(with string: String, highlight: String? = nil) {
// ...코드 생략...
guard let highlight = highlight else { return }
let attributedString = NSMutableAttributedString(string: result)
let length = result.count
var range = NSRange(location: 0, length: length)
var rangeArray = [NSRange]()
// STEP 1: 전체 텍스트에서 해당 단어가 포함된 모든 범위 구하기
while range.location != NSNotFound {
range = (attributedString.string as NSString).range(of: highlight, options: .caseInsensitive, range: range)
rangeArray.append(range)
if range.location != NSNotFound {
range = NSRange(location: range.location + range.length, length: result.count - (range.location + range.length))
}
}
// STEP 2: Ellipsis 특성상 텍스트 가장 마지막에 위치하므로 구한 범위 중 가장 마지막 범위를 사용
guard let range = rangeArray.filter({ $0.location != NSNotFound }).last else { return }
attributedString.addAttribute(.foregroundColor, value: UIColor.systemBlue, range: range)
self.attributedText = attributedString
}
2. 결과
응용 02 - 터치 이벤트
1. 클래스 생성(참고 자료 - Bruno Chen Chih Ying)
class RangeGestureRecognizer: UITapGestureRecognizer {
// Stored variables
var range: NSRange?
var function: (()->())?
func didTapAttributedTextInLabel(_ label: UILabel, in targetRange: NSRange) -> Bool {
// Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: CGSize.zero)
let textStorage = NSTextStorage(attributedString: label.attributedText!)
// Configure layoutManager and textStorage
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
// Configure textContainer
textContainer.lineFragmentPadding = 0.0
textContainer.lineBreakMode = label.lineBreakMode
textContainer.maximumNumberOfLines = label.numberOfLines
let labelSize = label.bounds.size
textContainer.size = labelSize
// Find the tapped character location and compare it to the specified range
let locationOfTouchInLabel = self.location(in: label)
let textBoundingBox = layoutManager.usedRect(for: textContainer)
let textContainerOffset = CGPoint(x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
y: (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y);
let locationOfTouchInTextContainer = CGPoint(x: locationOfTouchInLabel.x - textContainerOffset.x,
y: locationOfTouchInLabel.y - textContainerOffset.y);
let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
return NSLocationInRange(indexOfCharacter, targetRange)
}
}
2. 터치 이벤트 추가
func replaceEllipsis(with string: String, highlight: String? = nil, handler: @escaping ()->()) {
// ...코드 생략...
guard let range = rangeArray.filter({ $0.location != NSNotFound }).last else { return }
// ...코드 생략...
self.isUserInteractionEnabled = true
let gestureRecognizer = RangeGestureRecognizer(target: self, action: #selector(didTapAttributedTextInLabel(_ :)))
gestureRecognizer.numberOfTapsRequired = 1
gestureRecognizer.range = range
gestureRecognizer.function = handler
self.addGestureRecognizer(gestureRecognizer)
}
@objc func didTapAttributedTextInLabel(_ sender: RangeGestureRecognizer) {
guard let range = sender.range,
let function = sender.function else { return }
if sender.didTapAttributedTextInLabel(self, in: range) {
function()
}
}
3. 적용 방법
label.replaceEllipsis(with: "... 더보기", highlight: "더보기") {
print("더보기")
}
'iOS > Swift' 카테고리의 다른 글
[Swift] 무한 배너 만들기(Make a Infinite slide banner with carousel) (0) | 2022.09.20 |
---|---|
[Swift] 배너 만들기(Make a Slide banner with Carousel) (1) | 2022.09.19 |
[Swift] textViewDidChange(_:) 위치값 초기화 현상 수정 (0) | 2022.08.22 |
[Swift] 화면 눌러서 키보드 내리기 (Tap to hide keyboard) (0) | 2022.08.12 |
[Swift] 속성 문자열 사용법 (NSAttributedString) (0) | 2022.08.12 |