【问题标题】:How to ensure CAShapeLayer resizes to fit in UIView如何确保 CAShapeLayer 调整大小以适应 UIView
【发布时间】:2017-06-24 10:59:22
【问题描述】:

我目前正在将 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
    }
}

【问题讨论】:

  • 为什么要在 moveCenter 函数中偏移?
  • @Ocunidee UIBezierPathExtension 是在网上找到的解决我的问题的尝试link 目前我觉得我只是将不同谜题的部分放在一起,几乎没有做任何事情,这就是我来堆栈溢出的原因求助!无法确定为什么我不能简单地调整图层大小,将其框架设置为 uiview 的边界并完成。
  • 你试过注释掉关于偏移量的部分吗?
  • @Ocunidee 是的,我觉得我可以以完全不同的方式接近 bezierpath 定位。只是不知道最好的方法
  • 如果您愿意,我可以发布一个示例,说明如何使用 BezierPath 和 CAShapeLayer 绘制到我事先不知道的给定比例。但是该示例不包含任何有关 MKPolyline 的内容

标签: swift scale uibezierpath polyline cashapelayer


【解决方案1】:

UIBezierPath 可以像 CGRectCGPoint 或使用 CGAffineTransform 的“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))

【讨论】:

    【解决方案2】:

    这是我用来缩放 UIBezierPath 的一种方法: 我将使用 original(您的 MKPolyline 大小,我的原始数据)和 final(接收视图大小,它将如何显示)。

    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
    • 您的第一种方法看起来不错(那么您确实有一个点数组吗?)您不应该为 CAShapeLayer 设置框架。将其添加到特定视图的事实将赋予它视图的边界。你错过了最后一件事,那就是缩放。你的 MKPolyline 的 CGPoint 是否有一个数组?
    • 我确实有一个用 MapPoints 填充的 CGPoint 数组。 convertMapPointsToCGPoints(_:) 我认为那里的一切都是正确的。所以它可能只是缩放?
    • 我很确定它是。您应该获得地图点所在视图的宽度和高度,然后按比例查看该空间中的 1pt 如何等于新空间中的 1pt
    • 是的!花了我一段时间,不知道为什么,但我使用的是 polylineView.center 而不是 polylineView.bounds.center,这也是对齐的一个因素。尚不完全清楚为什么,但现在可以正常工作了!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-05-21
    • 2012-07-22
    • 1970-01-01
    • 2015-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多