Pavel Lazarev Asked:2024-09-02 19:40:45 +0800 CST2024-09-02 19:40:45 +0800 CST 2024-09-02 19:40:45 +0800 CST 如何获取 UILabel 中最后一个字符的位置? 772 我需要做这样的事情: 其中“左长文本”和“右文本”是 2 个独立的 UILabels,并且它们之间有一条虚线 为此,我需要知道如何获取最后一个字符的位置,但这是我的猜测) 请告诉我如何获取 UILabel 中最后一个字符的位置,或者也许还有另一种方法来实现这个? ios 1 个回答 Voted Best Answer schmidt9 2024-09-03T03:28:47+08:002024-09-03T03:28:47+08:00 下面是获取所有标签行的边框并使用左右标签最后一行的边框的方法。行边界是通过迭代行中所有字符的坐标来确定的 - 如果两个字符在 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 } }
下面是获取所有标签行的边框并使用左右标签最后一行的边框的方法。行边界是通过迭代行中所有字符的坐标来确定的 - 如果两个字符在 Y 轴上的坐标不同,则它们位于不同的行上。然后我们将这些边界转换为屏幕坐标并绘制虚线
限制:标签必须具有相同的字体大小和文本格式,在测试项目中它们彼此底部对齐
线条边界是可见的,黑线是线条的完整高度,红线是沿着线条基线的边界(这正是绘制虚线所需的)
最终结果
测试项目:https://github.com/schmidt9/TwoLabelsWithLineBetween