I am currently converting an MKPolyline to a BezierPath then a CAShapeLayer then adding the layer as a sublayer to a UIView. Currently struggling to ensure the path is not drawn outside the bounds of the UIView. I do not want to mask and have part of the path dissapear, but rather ensure that every point is resized and positioned in the center of the 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
}
}