Как получить позицию последнего символа в UILabel?
Мне нужно сделать что-то типа такого:
Где "Left long text" и "Right text" - это 2 отдельных UILabel и между ними прерывистая линия
Для этого мне нужно узнать как получить позицию последнего символа, но это моё предположение)
Пожалуйста, подскажите, как получить позицию последнего символа в UILabel или может быть есть другой вариант как это реализовать?
Ответы (1 шт):
Ниже представлен метод получения границ для всех строк лейблов и использование границ последней строки из левого и правого лейбла. Границы строк определяются путем перебора координат всех символов в строках - если координаты двух символов различаются по оси Y, значит они на разных строках. Затем мы концертируем эти границы в координаты экрана и рисуем пунктир
Ограничения: лейблы должны иметь одинаковый размер шрифта и одинаковое форматирование текста, в тестовом проекте они выровнены с друг другом по нижней границе
Границы строк визуально, черные линии это полная высота строк, красные линии это границы по базовым линиям строк (которые как раз нужны для рисования пунктира)
Конечный результат
Тестовый проект: https://github.com/schmidt9/TwoLabelsWithLineBetween
//
// GlyphPositionLabel.swift
// TwoLabelsWithLineBetween
//
import UIKit
struct TextLineGeometry {
var fullRect: CGRect // used for debug here
var baselineRect: CGRect
}
class GlyphPositionLabel: UILabel {
var debug = false
/// See https://stackoverflow.com/a/77427472/3004003
var linesGeometry: [TextLineGeometry] {
guard let text, let attributedText, let font, !text.isEmpty else {
return []
}
let textContainer = NSTextContainer(size: bounds.size)
textContainer.lineFragmentPadding = 0;
textContainer.maximumNumberOfLines = self.numberOfLines;
textContainer.lineBreakMode = self.lineBreakMode;
let textStorage = NSTextStorage(attributedString: attributedText)
let layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
layoutManager.addTextContainer(textContainer)
let baseline = font.ascender
var geometries = [TextLineGeometry]()
var index = text.startIndex
var currentRect: CGRect?
while index != text.endIndex {
let range = index..<text.index(after: index)
// ignore white spaces
if text[range].trimmingCharacters(in: .whitespaces).isEmpty {
index = text.index(after: index)
continue
}
let rect = layoutManager.boundingRect(forGlyphRange: NSRange(range, in: text), in: textContainer)
index = text.index(after: index)
if currentRect == nil {
currentRect = rect
} else if abs(currentRect!.minY - rect.minY) < 1 {
// glyph on the same line, resize line rect
currentRect!.size = CGSize(width: rect.maxX, height: currentRect!.height)
} else {
let geometry = TextLineGeometry(
fullRect: currentRect!,
baselineRect: CGRect(origin: currentRect!.origin, size: CGSize(width: currentRect!.width, height: baseline))
)
geometries.append(geometry)
currentRect = rect
}
}
let geometry = TextLineGeometry(
fullRect: currentRect!,
baselineRect: CGRect(origin: currentRect!.origin, size: CGSize(width: currentRect!.width, height: baseline))
)
geometries.append(geometry)
return geometries
}
override func draw(_ rect: CGRect) {
super.draw(rect)
if !debug {
return
}
linesGeometry.forEach {
UIColor.black.setStroke()
let fullPath = UIBezierPath(rect: $0.fullRect)
fullPath.stroke()
UIColor.red.setStroke()
let baseLinePath = UIBezierPath(rect: $0.baselineRect)
baseLinePath.stroke()
}
}
}
//
// ViewController.swift
// TwoLabelsWithLineBetween
//
import UIKit
class ViewController: UIViewController {
@IBOutlet var leftLabel: GlyphPositionLabel!
@IBOutlet var rightLabel: GlyphPositionLabel!
let lineLayerName = "lineLayerName"
let linePadding: CGFloat = 4
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
drawLine()
}
func drawLine() {
leftLabel.debug = false
rightLabel.debug = false
guard
let leftLastLineGeometry = leftLabel.linesGeometry.last,
let rightLastLineGeometry = rightLabel.linesGeometry.last else {
return
}
let leftBaselineRect = leftLabel.convert(leftLastLineGeometry.baselineRect, to: view)
let leftBaselineBottomRightPoint = CGPoint(x: leftBaselineRect.maxX + linePadding, y: leftBaselineRect.maxY)
let rightBaselineRect = rightLabel.convert(rightLastLineGeometry.baselineRect, to: view)
let rightBaselineBottomLeftPoint = CGPoint(x: rightBaselineRect.minX - linePadding, y: rightBaselineRect.maxY)
for layer in view.layer.sublayers ?? [] {
if layer.name == lineLayerName {
layer.removeFromSuperlayer()
break
}
}
let lineLayer = CAShapeLayer()
lineLayer.lineDashPattern = [4, 2]
lineLayer.strokeColor = UIColor.gray.cgColor
lineLayer.lineWidth = 1
lineLayer.name = lineLayerName
view.layer.addSublayer(lineLayer)
let linePath = UIBezierPath()
linePath.move(to: leftBaselineBottomRightPoint)
linePath.addLine(to: rightBaselineBottomLeftPoint)
lineLayer.path = linePath.cgPath
}
}