首页 > 解决方案 > 如何找到在 UIBezierPath 表示的不规则形状内绘制文本的位置?

问题描述

在我的应用程序中,我使用这个东西UIBezierPath从 SVG 文件中导入了一堆s ,它们都代表不规则的形状。我想在形状内绘制文本以便“标记”这些形状。这是一个例子:

在此处输入图像描述

我想在形状内绘制文本“A”。基本上,这将在draw(_:)custom 的方法中完成UIView,我希望调用带有签名的方法,例如:

func drawText(_ text: String, in path: UIBezierPath, fontSize: CGFloat) {
    // draws text, or do nothing if there is not enough space 
}

在网上看了一些,我发现了这个帖子,它似乎总是在图像的中心绘制字符串。这不是我想要的。如果我将 放在(我知道我可以使用)A的边界框的中心,它将在形状之外。这可能是因为形状是凹的。UIBezierPathUIBezierPath.bounds

请注意,文本很短,所以我不需要担心换行和其他东西。

当然,形状内部有很多地方可以放入“A”,我只想要一个解决方案,选择可以以合理大小显示文本的任何地方。我想到的一种方法是找到可以内接在形状中的最大矩形,然后在该矩形的中心绘制文本。我发现这篇文章是在 Matlab 中执行此操作的,而且这似乎是一个非常计算密集型的操作……我不确定这是一个好的解决方案。

我应该如何实施drawText(_:in:fontSize:)

为避免成为 XY 问题,这里有一些背景:

我正在处理的形状实际上是行政区域的边界。换句话说,我正在绘制地图。我不想使用现有的地图 API,因为我希望我的地图看起来很粗糙。它只会显示行政区域的边界及其上的标签。所以我当然可以使用地图 API 使用的任何算法在他们的地图上绘制标签,对吧?

这个问题不是这个的重复因为我知道我可以做到这一点UIBezierPath.contains。我试图找到一个点。它也不是这个的副本,因为我的问题是关于在路径绘制文本,而不是.

标签: iosswiftcore-graphicsuibezierpath

解决方案


TextKit 是为这样的任务而构建的。您可以在贝塞尔形状路径之外创建一个路径数组,然后将其设置为 textView 的排除路径:

textView.textContainer.exclusionPaths = [pathsAroundYourBezier];

请记住,排除路径是不会显示容器中文本的路径。苹果文档在这里:https ://developer.apple.com/documentation/uikit/nstextcontainer/1444569-exclusionpaths

由于 TEXTVIEW 开始时存在排除路径的错误而进行的更新:

我想出了一种方法来找到路径文本可以适合的位置。

用法:

    let pathVisible = rectPathSharpU(CGSize(width: 100, height: 125), origin: CGPoint(x: 0, y: 0))
    let shapeLayer = CAShapeLayer()
    shapeLayer.path = pathVisible.cgPath
    shapeLayer.strokeColor = UIColor.green.cgColor
    shapeLayer.backgroundColor = UIColor.clear.cgColor
    shapeLayer.lineWidth = 3
    self.view.layer.addSublayer(shapeLayer)

    let path = rectPathSharpU(CGSize(width: 100, height: 125), origin: CGPoint(x: 0, y: 0))
    let fittingRect = findFirstRect(path: path, thatFits: "A".size())!
    print("fittingRect: \(fittingRect)")
    let label = UILabel.init(frame: fittingRect)
    label.text = "A"
    self.view.addSubview(label)

输出:

可能存在需要考虑弯曲路径的情况,可能通过迭代路径边界中的每个 y 点,直到找到相当大的空间。

查找第一个拟合矩形的函数:

func findFirstRect(path: UIBezierPath, thatFits: CGSize) -> CGRect? {
    let points = path.cgPath.points
    allPoints: for point in points {
        var checkpoint = point
        var size = CGSize(width: 0, height: 0)
        thisPoint: while size.width <= path.bounds.width {
            if path.contains(checkpoint) && path.contains(CGPoint.init(x: checkpoint.x + thatFits.width, y: checkpoint.y + thatFits.height)) {
                return CGRect(x: checkpoint.x, y: checkpoint.y, width: thatFits.width, height: thatFits.height)
            } else {
                checkpoint.x += 1
                size.width += 1
                continue thisPoint
            }
        }
    }
    return nil
}

查找字符串大小的扩展:

extension String {
    func size(width:CGFloat = 220.0, font: UIFont = UIFont.systemFont(ofSize: 17.0, weight: .regular)) -> CGSize {
        let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
        label.numberOfLines = 0
        label.lineBreakMode = NSLineBreakMode.byWordWrapping
        label.font = font
        label.text = self

        label.sizeToFit()

        return CGSize(width: label.frame.width, height: label.frame.height)
    }
}

创建测试路径:

func rectPathSharpU(_ size: CGSize, origin: CGPoint) -> UIBezierPath {

    // Initialize the path.
    let path = UIBezierPath()

    // Specify the point that the path should start get drawn.
    path.move(to: CGPoint(x: origin.x, y: origin.y))
    // add lines to path
    path.addLine(to: CGPoint(x: (size.width / 3) + origin.x, y: (size.height / 3 * 2) + origin.y))
    path.addLine(to: CGPoint(x: (size.width / 3 * 2) + origin.x, y: (size.height / 3 * 2) + origin.y))
    path.addLine(to: CGPoint(x: size.width + origin.x, y: origin.y))
    path.addLine(to: CGPoint(x: (size.width) + origin.x, y: size.height + origin.y))
    path.addLine(to: CGPoint(x: origin.x, y: size.height + origin.y))

    // Close the path. This will create the last line automatically.
    path.close()

    return path
}

如果这不适用于像您的图片示例那样具有很多弧线的路径,请发布实际的路径数据,以便我可以进行测试。

奖励:我还创建了一个函数来查找对称路径的最宽部分,尽管没有考虑高度。虽然它可能有用:

func findWidestY(path: UIBezierPath) -> CGRect {
    var widestSection = CGRect(x: 0, y: 0, width: 0, height: 0)
    let points = path.cgPath.points
    allPoints: for point in points {
        var checkpoint = point
        var size = CGSize(width: 0, height: 0)
        thisPoint: while size.width <= path.bounds.width {
            if path.contains(checkpoint) {
                checkpoint.x += 1
                size.width += 1
                continue thisPoint
            } else {
                if size.width > widestSection.width {
                    widestSection = CGRect(x: point.x, y: point.y, width: size.width, height: 1)
                }
                break thisPoint
            }
        }
    }
    return widestSection
}

推荐阅读