如何找到在 UIBezierPath 表示的不规则形状内绘制文本的位置?

How to find where to draw text inside an irregular shape represented by a UIBezierPath?

在我的应用程序中,我有一堆 UIBezierPath 是我使用 this thing 从 SVG 文件导入的,它们都代表不规则形状。我想在形状内绘制文本,以便 "label" 这些形状。这是一个例子:

我想在形状内绘制文字 "A"。基本上,这将在自定义 UIViewdraw(_:) 方法中完成,我希望调用带有签名的方法,例如:

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

上网查了一下,发现this post, which seems to always draw the string in the centre of the image. This is not what I want. If I put the A in the centre of the bounding box of the UIBezierPath (which I know I can get with UIBezierPath.bounds),应该是在形状。这可能是因为形状是凹的。

请注意,文本很短,因此我无需担心换行等问题。

当然,形状里面有很多地方我可以把"A"放进去,我只想要一个解决方案,选择any一个可以显示的地方文本大小合适。我想到的一种方法是找到可以刻在形状中的最大矩形,然后在该矩形的中心绘制文本。我发现 可以在 Matlab 中执行此操作,而且这似乎是一项计算密集型工作...我不确定这是一个好的解决方案。

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

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

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

这个问题也不是 this, as I know I can do that with UIBezierPath.contains. I'm trying to find a point. It's not a duplicate of this 的重复问题,因为我的问题是关于在 内部 路径绘制文本,而不是 在 [=48] 上=].

TextKit 就是为这样的任务而构建的。您可以在贝塞尔曲线形状路径 外部 创建一个路径数组,然后将其设置为文本视图的 exclusionPaths:

textView.textContainer.exclusionPaths = [pathsAroundYourBezier];

Keep in mind that the exclusion paths are paths where text in the container will not be displayed. Apple documentation here: 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)

输出:

There may be cases with curved paths that will need to be taken into account, perhaps by iterating through every y point in a path bounds until a sizable space is found.

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

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
}

If this doesn't work for paths with a lot of arcs like your picture example, please post the actual path data so I can test with that.

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

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
}