如何确保 CAShapeLayer 调整大小以适应 UIView
How to ensure CAShapeLayer resizes to fit in UIView
我目前正在将 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
}
}
这是我用来缩放 UIBezierPath 的方法:
我将使用 original(你的 MKPolyline 大小,我的原始数据)和 final(接收视图大小,它将如何显示)。
1.Calculate原始振幅(对我来说只是高度,对你来说也是宽度)
2.Write 将原始数据缩放到新的 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.Start绘图
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 绘制的逻辑仍有待完成。重要的是,当您 "copy" 折线时,您 move(to: )
正确的点就可以做到。这就是为什么我认为你没有正确的偏移量
UIBezierPath
可以像 CGRect
、CGPoint
或 'CGSize' 一样使用 CGAffineTransform
进行缩放。
// 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))
我目前正在将 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
}
}
这是我用来缩放 UIBezierPath 的方法: 我将使用 original(你的 MKPolyline 大小,我的原始数据)和 final(接收视图大小,它将如何显示)。
1.Calculate原始振幅(对我来说只是高度,对你来说也是宽度)
2.Write 将原始数据缩放到新的 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.Start绘图
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 绘制的逻辑仍有待完成。重要的是,当您 "copy" 折线时,您 move(to: )
正确的点就可以做到。这就是为什么我认为你没有正确的偏移量
UIBezierPath
可以像 CGRect
、CGPoint
或 'CGSize' 一样使用 CGAffineTransform
进行缩放。
// 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))