如何找到在 UIBezierPath 表示的不规则形状内绘制文本的位置?
How to find where to draw text inside an irregular shape represented by a UIBezierPath?
在我的应用程序中,我有一堆 UIBezierPath
是我使用 this thing 从 SVG 文件导入的,它们都代表不规则形状。我想在形状内绘制文本,以便 "label" 这些形状。这是一个例子:
我想在形状内绘制文字 "A"。基本上,这将在自定义 UIView
的 draw(_:)
方法中完成,我希望调用带有签名的方法,例如:
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
}
在我的应用程序中,我有一堆 UIBezierPath
是我使用 this thing 从 SVG 文件导入的,它们都代表不规则形状。我想在形状内绘制文本,以便 "label" 这些形状。这是一个例子:
我想在形状内绘制文字 "A"。基本上,这将在自定义 UIView
的 draw(_:)
方法中完成,我希望调用带有签名的方法,例如:
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一个可以显示的地方文本大小合适。我想到的一种方法是找到可以刻在形状中的最大矩形,然后在该矩形的中心绘制文本。我发现
我应该如何实施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
}