如何确保CAShapeLayer调整大小以适应UIView?

6

我目前正在将MKPolyline转换为BezierPath,然后再转换为CAShapeLayer,并将该图层添加为UIView的子图层。目前难以确保路径不会绘制在UIView范围之外。我不想进行遮罩处理并使部分路径消失,而是要确保每个点都被重新调整大小并位于UIView的中心位置。

func addPathToView() {
    guard let path = createPath(onView: polylineView) else { return }
    path.fit(into: polylineView.bounds).moveCenter(to: polylineView.center).fill()
    path.lineWidth     = 3.0
    path.lineJoinStyle = .round

    guard let layer  = createCAShapeLayer(fromBezierPath: path) else { return }
    layer.path       = getScaledPath(fromPath: path, layer: layer)
    layer.frame      = polylineView.bounds
    layer.position.x = polylineView.bounds.minX
    layer.position.y = polylineView.bounds.minY

    polylineView.layer.addSublayer(layer)
}

func createCAShapeLayer( fromBezierPath path: UIBezierPath? ) -> CAShapeLayer? {
    guard let path = path else { print("No Path"); return nil }
    let pathLayer = CAShapeLayer(path: path, lineColor: UIColor.red, fillColor: UIColor.clear)
    return pathLayer
}

func createPath( onView view: UIView? ) -> UIBezierPath? {
    guard let polyline = Polyline().createPolyline(forLocations: locations) else { print("No Polyline"); return nil }
    guard let points   = convertMapPointsToCGPoints(fromPolyline: polyline) else { print("No CGPoints"); return nil }

    let path = UIBezierPath(points: points)

    return path
}

func convertMapPointsToCGPoints( fromPolyline polyline: MKPolyline? ) -> [CGPoint]? {
    guard let polyline = polyline else { print( "No Polyline"); return nil }

    let mapPoints = polyline.points()

    var points = [CGPoint]()

    for point in 0..<polyline.pointCount {
        let coordinate = MKCoordinateForMapPoint(mapPoints[point])
        points.append(mapView.convert(coordinate, toPointTo: view))
    }

    return points
}

func getScaledPath( fromPath path: UIBezierPath, layer: CAShapeLayer ) -> CGPath? {
    let boundingBox = path.cgPath.boundingBoxOfPath

    let boundingBoxAspectRatio = boundingBox.width / boundingBox.height
    let viewAspectRatio = polylineView.bounds.size.width / polylineView.bounds.size.height

    let scaleFactor: CGFloat
    if (boundingBoxAspectRatio > viewAspectRatio) {
        // Width is limiting factor
        scaleFactor = polylineView.bounds.size.width / boundingBox.width
    } else {
        // Height is limiting factor
        scaleFactor = polylineView.bounds.size.height/boundingBox.height
    }

    var affineTransorm = CGAffineTransform(scaleX: scaleFactor, y: scaleFactor)
    let transformedPath = path.cgPath.copy(using: &affineTransorm)

    guard let tPath = transformedPath else { print ("nope"); return nil }

    return tPath
}

extension UIBezierPath
{
    func moveCenter(to:CGPoint) -> Self{
        let bound  = self.cgPath.boundingBox
        let center = bounds.center

        let zeroedTo = CGPoint(x: to.x-bound.origin.x, y: to.y-bound.origin.y)
        let vector = center.vector(to: zeroedTo)

        offset(to: CGSize(width: vector.dx, height: vector.dy))
        return self
    }

    func offset(to offset:CGSize) -> Self{
        let t = CGAffineTransform(translationX: offset.width, y: offset.height)
        applyCentered(transform: t)
        return self
    }

    func fit(into:CGRect) -> Self{
        let bounds = self.cgPath.boundingBox

        let sw     = into.size.width/bounds.width
        let sh     = into.size.height/bounds.height
        let factor = min(sw, max(sh, 0.0))

        return scale(x: factor, y: factor)
    }

    func scale(x:CGFloat, y:CGFloat) -> Self{
        let scale = CGAffineTransform(scaleX: x, y: y)
        applyCentered(transform: scale)
        return self
    }

    func applyCentered(transform: @autoclosure () -> CGAffineTransform ) -> Self{
        let bound  = self.cgPath.boundingBox
        let center = CGPoint(x: bound.midX, y: bound.midY)
        var xform  = CGAffineTransform.identity

        xform = xform.concatenating(CGAffineTransform(translationX: -center.x, y: -center.y))
        xform = xform.concatenating(transform())
        xform = xform.concatenating( CGAffineTransform(translationX: center.x, y: center.y))
        apply(xform)

        return self
    }
}

extension UIBezierPath
{
    convenience init(points:[CGPoint])
    {
        self.init()

        //connect every points by line.
        //the first point is start point
        for (index,aPoint) in points.enumerated()
        {
            if index == 0 {
                self.move(to: aPoint)
            }
            else {
                self.addLine(to: aPoint)
            }
        }
    }
}

//2. To create layer use this extension

extension CAShapeLayer
{
    convenience init(path:UIBezierPath, lineColor:UIColor, fillColor:UIColor)
    {
        self.init()
        self.path = path.cgPath
        self.strokeColor = lineColor.cgColor
        self.fillColor = fillColor.cgColor
        self.lineWidth = path.lineWidth

        self.opacity = 1
        self.frame = path.bounds
    }
}

enter image description here


你的moveCenter函数为什么要进行偏移? - Ocunidee
@Ocunidee,UIBezierPathExtension是我在网上找到的一种尝试解决问题的方法链接。目前,我感觉自己只是把不同拼图的部分组合在一起,勉强让它们工作,这就是为什么我来到Stack Overflow寻求帮助的原因!我无法确定为什么我不能简单地调整层的大小,将其框架设置为uiview的边界并完成。 - lifewithelliott
你尝试过注释掉关于偏移量的部分吗? - Ocunidee
@Ocunidee 是的,我感觉可以用完全不同的方式来处理bezierpath的定位。只是不知道最好的方法是什么。 - lifewithelliott
如果您想的话,我可以发布一个示例,展示如何使用BezierPath和CAShapeLayer绘制给定比例的图形,而这个比例事先是不知道的。但是这个示例不会包含任何关于MKPolyline的内容。 - Ocunidee
@Ocunidee 那太棒了!任何帮助我理解如何处理这个问题的东西都可以。谢谢 - lifewithelliott
2个回答

4
一个 UIBezierPath 可以使用 CGAffineTransform 进行缩放,就像 CGRectCGPoint 或 'CGSize' 一样。
// calculate the scale
//
let scaleWidth  = toSize.width / fromSize.width
let scaleHeight = toSize.height / fromSize.height

// re-scale the path
//
path.apply(CGAffineTransform(scaleX: scaleWidth, y: scaleHeight))

1
这是我用来缩放UIBezierPath的方法: 我将使用原始数据(您的MKPolyline大小,我的原始数据)和最终数据(接收视图大小,如何显示它)。
1.计算原始振幅(对我来说,它只是高度,但对您来说也将包括宽度)。
2.编写一个函数,将原始数据缩放到新的X和Y轴比例尺上(对于点位置,它会像这样)。
func scaleValueToYAxis(_ value: Double) -> CGFloat {
    return finalHeight - CGFloat(value) / originalYAmplitude) * finalHeight
}

func scaleValueToXAxis(_ value: Double) -> CGFloat {
     return finalWidth - CGFloat(value) / originalXAmplitude) * finalWidth

}

3.开始绘制。
let path = UIBezierPath()
let path.move(to: CGPoint(x: yourOriginForDrawing, y: yourOriginForDrawing)) // final scale position

path.addLine(to: CGPoint(x: nextXPoint, y: nextYPoint)) // this is not relevant for you as you don't draw point by point
// what is important here is the fact that you take your original
//data X and Y and make them go though your scale functions 

let layer = CAShapeLayer()
let layer.path = path.cgPath
let layer.lineWidth = 1.0
let layer.strokeColor = UIColor.black

yourView.layer.addSublayer(layer)

正如您所看到的,关于从MKPolyline绘制的逻辑仍需完成。重要的是,在“复制”折线时,您需要move(to: )正确的点来执行它。这就是为什么我认为您没有正确的偏移量的原因。

仍在努力获得可工作的输出。对“原始数据X和Y并使它们通过您的比例函数”有些困惑。我在代码底部添加了两个扩展来显示我如何创建BezierPath和CAShapeLayer。感谢迄今为止的帮助,希望我的补充细节能够聚焦于我的问题@Ocunidee。 - lifewithelliott
你的第一个方法看起来不错(你有一个点数组吗?) 你不应该给CAShapeLayer设置框架。将其添加到特定视图中将已经为其提供了视图的边界。 你还缺少最后一件事,那就是缩放。你是否有MKPolyline的CGPoint数组? - Ocunidee
我有一个 CGPoint 数组,里面装满了 MapPoints。convertMapPointsToCGPoints(_:) 我认为一切都是正确的。也许只是缩放的问题? - lifewithelliott
我非常确定。您应该获取包含地图点的视图的宽度和高度,然后通过比例来查看在该空间中1pt等于您新空间中的1pt。 - Ocunidee
是的!花了我一些时间,不确定为什么,但我使用的是polylineView.center而不是polylineView.bounds.center,这也是对齐的一个因素。还不确定完全为什么,但现在已经按预期工作了! - lifewithelliott

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接