UILabel에서 텍스트의 하위 문자열에 대한 CGRect를 어떻게 찾습니까?
주어진에 대해 NSRange
, 그 글리프에 해당하는 CGRect
a 를 찾고 싶습니다 . 예를 들어, "The quick brown fox jumps over the lazy dog"라는 문장에서 "dog"이라는 단어가 포함 된 을 찾고 싶습니다 .UILabel
NSRange
CGRect
트릭은에 UILabel
여러 줄이 있고 텍스트가 실제로 attributedText
이므로 문자열의 정확한 위치를 찾는 것이 약간 어렵습니다.
내 UILabel
하위 클래스 에 작성하려는 메서드는 다음과 같습니다.
- (CGRect)rectForSubstringWithRange:(NSRange)range;
관심있는 분들을위한 세부 정보 :
이것에 대한 나의 목표는 UILabel의 정확한 모양과 위치를 가진 새로운 UILabel 을 만들어 애니메이션화 할 수있는 것입니다. 나머지는 알아 냈지만,이 단계가 특히 저를 방해하고 있습니다.
지금까지 문제를 해결하기 위해 내가 한 일 :
- 나는 아이폰 OS 7이 문제를 해결할 텍스트 키트의 비트가있을 거라고 기대했던,하지만 대부분은 내가 텍스트 키트 본 적이 모든 예제에 초점을 맞추고
UITextView
및UITextField
보다는UILabel
. - 여기서 문제를 해결하겠다고 약속하는 Stack Overflow에 대한 또 다른 질문을 보았습니다. 그러나 허용 된 답변은 2 년이 넘었으며 코드가 속성 텍스트로 제대로 작동하지 않습니다.
이에 대한 정답에는 다음 중 하나가 포함됩니다.
- 표준 텍스트 키트 방법을 사용하여 한 줄의 코드로이 문제를 해결합니다. 나는 그것이 관련
NSLayoutManager
되고textContainerForGlyphAtIndex:effectiveRange
- UILabel을 여러 줄로 나누는 복잡한 메서드를 작성하고 Core Text 메서드를 사용하여 한 줄 내에서 글리프의 사각형을 찾습니다. 내 현재 최선의 방법은 @mattt의 우수한 TTTAttributedLabel 을 분해 하는 것입니다.이 방법은 한 지점에서 글리프를 찾는 방법을 가지고 있습니다. 만약 내가 그것을 반전하고 글리프의 포인트를 찾으면 작동 할 것입니다.
업데이트 : 다음은이 문제를 해결하기 위해 지금까지 시도한 세 가지 사항에 대한 github 요점입니다 : https://gist.github.com/bryanjclark/7036101
다음 여호수아의 대답은 코드에서, 나는 직장에 잘 보인다 다음과 함께했다 :
- (CGRect)boundingRectForCharacterRange:(NSRange)range
{
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:[self attributedText]];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
[textStorage addLayoutManager:layoutManager];
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:[self bounds].size];
textContainer.lineFragmentPadding = 0;
[layoutManager addTextContainer:textContainer];
NSRange glyphRange;
// Convert the range for glyphs.
[layoutManager characterRangeForGlyphRange:range actualGlyphRange:&glyphRange];
return [layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer];
}
Luke Rogers의 답변을 바탕으로 작성되었지만 신속하게 작성되었습니다.
스위프트 2
extension UILabel {
func boundingRectForCharacterRange(_ range: NSRange) -> CGRect? {
guard let attributedText = attributedText else { return nil }
let textStorage = NSTextStorage(attributedString: attributedText)
let layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
let textContainer = NSTextContainer(size: bounds.size)
textContainer.lineFragmentPadding = 0.0
layoutManager.addTextContainer(textContainer)
var glyphRange = NSRange()
// Convert the range for glyphs.
layoutManager.characterRangeForGlyphRange(range, actualGlyphRange: &glyphRange)
return layoutManager.boundingRectForGlyphRange(glyphRange, inTextContainer: textContainer)
}
}
사용 예 (Swift 2)
let label = UILabel()
let text = "aa bb cc"
label.attributedText = NSAttributedString(string: text)
let sublayer = CALayer()
sublayer.borderWidth = 1
sublayer.frame = label.boundingRectForCharacterRange(NSRange(text.range(of: "bb")!, in: text))
label.layer.addSublayer(sublayer)
스위프트 3/4
extension UILabel {
func boundingRect(forCharacterRange range: NSRange) -> CGRect? {
guard let attributedText = attributedText else { return nil }
let textStorage = NSTextStorage(attributedString: attributedText)
let layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
let textContainer = NSTextContainer(size: bounds.size)
textContainer.lineFragmentPadding = 0.0
layoutManager.addTextContainer(textContainer)
var glyphRange = NSRange()
// Convert the range for glyphs.
layoutManager.characterRange(forGlyphRange: range, actualGlyphRange: &glyphRange)
return layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
}
}
사용 예 (Swift 3/4)
let label = UILabel()
let text = "aa bb cc"
label.attributedText = NSAttributedString(string: text)
let sublayer = CALayer()
sublayer.borderWidth = 1
sublayer.frame = label.boundingRect(forCharacterRange: NSRange(text.range(of: "bb")!, in: text))
label.layer.addSublayer(sublayer)
내 제안은 Text Kit를 사용하는 것입니다. 불행히도 우리는 a가 UILabel
사용 하는 레이아웃 관리자에 액세스 할 수 없지만 복제본을 만들어 범위에 대한 rect를 가져 오는 데 사용할 수 있습니다.
내 제안은 NSTextStorage
라벨에있는 것과 똑같은 속성 텍스트를 포함 하는 개체 를 만드는 것입니다. 다음으로 생성 NSLayoutManager
하여 텍스트 스토리지 개체에 추가합니다. 마지막으로 NSTextContainer
레이블과 동일한 크기로를 만들고 레이아웃 관리자에 추가합니다.
이제 텍스트 저장소는 레이블과 동일한 텍스트를 가지며 텍스트 컨테이너는 레이블과 동일한 크기이므로 .NET을 사용하여 범위에 대한 사각형을 생성 한 레이아웃 관리자에게 요청할 수 있습니다 boundingRectForGlyphRange:inTextContainer:
. 먼저 glyphRangeForCharacterRange:actualCharacterRange:
레이아웃 관리자 개체를 사용하여 문자 범위를 글리프 범위로 변환해야 합니다.
레이블 내에서 지정한 범위 의 경계 CGRect
를 제공해야하는 모든 것이 잘 진행 됩니다.
나는 이것을 테스트하지 않았지만 이것이 나의 접근 방식이며 UILabel
그 자체가 어떻게 작동 하는지 모방함으로써 성공할 가능성이 높습니다.
신속한 4 솔루션, 여러 줄 문자열에서도 작동하며 경계가 intrinsicContentSize로 대체되었습니다.
extension UILabel {
func boundingRectForCharacterRange(range: NSRange) -> CGRect? {
guard let attributedText = attributedText else { return nil }
let textStorage = NSTextStorage(attributedString: attributedText)
let layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
let textContainer = NSTextContainer(size: intrinsicContentSize)
textContainer.lineFragmentPadding = 0.0
layoutManager.addTextContainer(textContainer)
var glyphRange = NSRange()
layoutManager.characterRange(forGlyphRange: range, actualGlyphRange: &glyphRange)
return layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
}
}
Swift 3 용으로 번역되었습니다.
func boundingRectForCharacterRange(_ range: NSRange) -> CGRect {
let textStorage = NSTextStorage(attributedString: self.attributedText!)
let layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
let textContainer = NSTextContainer(size: self.bounds.size)
textContainer.lineFragmentPadding = 0
layoutManager.addTextContainer(textContainer)
var glyphRange = NSRange()
layoutManager.characterRange(forGlyphRange: range, actualGlyphRange: &glyphRange)
return layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
}
대신 UITextView를 기반으로 클래스를 만들 수 있습니까? 그렇다면 UiTextInput 프로토콜 메서드를 확인하십시오. 특히 지오메트리 및 히트 휴지 방법을 참조하십시오.
정답은 실제로 할 수 없다는 것입니다. 여기에있는 대부분의 솔루션 UILabel
은 다양한 정밀도로 내부 동작 을 재현하려고합니다 . 최근 iOS 버전에서는UILabel
TextKit / CoreText 프레임 워크를 사용하여 텍스트를 렌더링합니다. 이전 버전에서는 실제로 WebKit을 내부적으로 사용했습니다. 미래에 무엇을 사용할지 누가 알겠습니까? TextKit으로 재생산하는 것은 지금도 분명한 문제가 있습니다 (미래 보장 솔루션에 대해 이야기하지 않음). 우리는 텍스트 레이아웃과 렌더링이 실제로 어떻게 수행되는지, 즉 어떤 매개 변수가 사용되는지 잘 알지 못합니다 (또는 보장 할 수 없습니다). 언급 된 모든 경우를 살펴보십시오. 일부 솔루션은 테스트 한 간단한 경우에 대해 '우연히'작동 할 수 있습니다. 이는 현재 iOS 버전 내에서도 아무것도 보장하지 않습니다. 텍스트 렌더링이 어렵습니다. RTL 지원, 동적 유형, 이모티콘 등을 시작하지 마십시오.
적절한 솔루션이 필요한 경우 UILabel
. 이는 단순한 구성 요소이며 TextKit 내부가 API에 노출되지 않는다는 사실은 Apple이 개발자가이 구현 세부 사항에 의존하는 솔루션을 빌드하는 것을 원하지 않는다는 분명한 표시입니다.
또는 UITextView를 사용할 수 있습니다. 그것은 편리하게 TextKit 내부를 노출하고 매우 구성 가능하므로 거의 모든 것을 얻을 수 있습니다 UILabel
.
더 낮은 수준으로 이동하여 TextKit을 직접 사용할 수도 있습니다 (또는 CoreText의 경우 더 낮음). 실제로 텍스트 위치를 찾아야하는 경우 이러한 프레임 워크에서 사용할 수있는 다른 강력한 도구가 곧 필요할 수 있습니다. 처음에는 사용하기가 다소 어렵지만 대부분의 복잡성은 실제로 텍스트 레이아웃과 렌더링이 작동하는 방식을 배우는 데서 오는 것 같습니다. 이는 매우 관련성이 높고 유용한 기술입니다. Apple의 우수한 텍스트 프레임 워크에 익숙해지면 텍스트로 훨씬 더 많은 일을 할 수 있다는 느낌이들 것입니다.
plain text
확장 프로그램을 찾는 분들에게 !
extension UILabel {
func boundingRectForCharacterRange(range: NSRange) -> CGRect? {
guard let text = text else { return nil }
let textStorage = NSTextStorage.init(string: text)
let layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
let textContainer = NSTextContainer(size: bounds.size)
textContainer.lineFragmentPadding = 0.0
layoutManager.addTextContainer(textContainer)
var glyphRange = NSRange()
// Convert the range for glyphs.
layoutManager.characterRange(forGlyphRange: range, actualGlyphRange: &glyphRange)
return layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
}
}
PS 업데이트 된 Noodle of Death`s 답변 .
자동 글꼴 크기 조정을 활성화 한 경우이를 수행하는 또 다른 방법은 다음과 같습니다.
let stringLength: Int = countElements(self.attributedText!.string)
let substring = (self.attributedText!.string as NSString).substringWithRange(substringRange)
//First, confirm that the range is within the size of the attributed label
if (substringRange.location + substringRange.length > stringLength)
{
return CGRectZero
}
//Second, get the rect of the label as a whole.
let textRect: CGRect = self.textRectForBounds(self.bounds, limitedToNumberOfLines: self.numberOfLines)
let path: CGMutablePathRef = CGPathCreateMutable()
CGPathAddRect(path, nil, textRect)
let framesetter = CTFramesetterCreateWithAttributedString(self.attributedText)
let tempFrame: CTFrameRef = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, stringLength), path, nil)
if (CFArrayGetCount(CTFrameGetLines(tempFrame)) == 0)
{
return CGRectZero
}
let lines: CFArrayRef = CTFrameGetLines(tempFrame)
let numberOfLines: Int = self.numberOfLines > 0 ? min(self.numberOfLines, CFArrayGetCount(lines)) : CFArrayGetCount(lines)
if (numberOfLines == 0)
{
return CGRectZero
}
var returnRect: CGRect = CGRectZero
let nsLinesArray: NSArray = CTFrameGetLines(tempFrame) // Use NSArray to bridge to Array
let ctLinesArray = nsLinesArray as Array
var lineOriginsArray = [CGPoint](count:ctLinesArray.count, repeatedValue: CGPointZero)
CTFrameGetLineOrigins(tempFrame, CFRangeMake(0, numberOfLines), &lineOriginsArray)
for (var lineIndex: CFIndex = 0; lineIndex < numberOfLines; lineIndex++)
{
let lineOrigin: CGPoint = lineOriginsArray[lineIndex]
let line: CTLineRef = unsafeBitCast(CFArrayGetValueAtIndex(lines, lineIndex), CTLineRef.self) //CFArrayGetValueAtIndex(lines, lineIndex)
let lineRange: CFRange = CTLineGetStringRange(line)
if ((lineRange.location <= substringRange.location) && (lineRange.location + lineRange.length >= substringRange.location + substringRange.length))
{
var charIndex: CFIndex = substringRange.location - lineRange.location; // That's the relative location of the line
var secondary: CGFloat = 0.0
let xOffset: CGFloat = CTLineGetOffsetForStringIndex(line, charIndex, &secondary);
// Get bounding information of line
var ascent: CGFloat = 0.0
var descent: CGFloat = 0.0
var leading: CGFloat = 0.0
let width: Double = CTLineGetTypographicBounds(line, &ascent, &descent, &leading)
let yMin: CGFloat = floor(lineOrigin.y - descent);
let yMax: CGFloat = ceil(lineOrigin.y + ascent);
let yOffset: CGFloat = ((yMax - yMin) * CGFloat(lineIndex))
returnRect = (substring as NSString).boundingRect(with: CGSize(width: Double.greatestFiniteMagnitude, height: Double.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: [.font: self.font ?? UIFont.systemFont(ofSize: 1)], context: nil)
returnRect.origin.x = xOffset + self.frame.origin.x
returnRect.origin.y = yOffset + self.frame.origin.y + ((self.frame.size.height - textRect.size.height) / 2)
break
}
}
return returnRect
'IT TIP' 카테고리의 다른 글
엔터티에서 Enum을 사용할 때 쿼리 작성 문제 (0) | 2020.11.04 |
---|---|
앵커 태그 대상 속성에서 _self, _top 및 _parent의 차이점 (0) | 2020.11.04 |
splatting이 rhs에 튜플을 생성하지만 lhs에 목록을 생성하는 이유는 무엇입니까? (0) | 2020.11.04 |
가변 높이 UITableView 내에서 콘텐츠를 기반으로 UIWebView 높이를 결정하는 방법은 무엇입니까? (0) | 2020.11.04 |
데코레이터보다 책임 체인을 사용하는 이유는 무엇입니까? (0) | 2020.11.04 |