I want to curve polyline between two points.
Android do that with the help of SphericalUtil Class .
But in iOS i don't know how to do that.
For Android , Here is a reference link: Draw ARC Polyline in Google Map
I want same for iOS.
I want to curve polyline between two points.
Android do that with the help of SphericalUtil Class .
But in iOS i don't know how to do that.
For Android , Here is a reference link: Draw ARC Polyline in Google Map
I want same for iOS.
Late to the party, but adding a solution for anyone else coming across this in the future:
UIBezierPath
to draw your curvesFocus on the extension to UIBezierPath
. Each pair of points is drawn as a quad curve using a perpendicular control point calculated in CGPoint.controlpoint(_, _)
.
Play around with the tension
value to alter the sharpness of the curve. A tension
of 2
generates something close to a quarter-circle.
extension UIBezierPath {
static func from(_ points: [CGPoint], tension: CGFloat) -> UIBezierPath {
let path = UIBezierPath()
guard let first = points.first, points.count > 1 else {
return path
}
path.move(to: first)
points
.pairwise()
.dropFirst()
.forEach { a, b in
path.addQuadCurve(
to: b,
controlPoint: .controlpoint(a!, b, tension: tension)
)
}
return path
}
}
extension CGPoint {
static func controlpoint(_ l: CGPoint, _ r: CGPoint, tension: CGFloat = 2) -> CGPoint {
midpoint(l, r) + (perpendicular(l, r) / tension)
}
static func midpoint(_ l: CGPoint, _ r: CGPoint) -> CGPoint {
(l + r) / 2
}
static func perpendicular(_ l: CGPoint, _ r: CGPoint) -> CGPoint {
let d = l - r
return CGPoint(x: -d.y, y: d.x)
}
static func + (l: CGPoint, r: CGPoint) -> CGPoint {
CGPoint(
x: l.x + r.x,
y: l.y + r.y
)
}
static func - (l: CGPoint, r: CGPoint) -> CGPoint {
CGPoint(
x: l.x - r.x,
y: l.y - r.y
)
}
static func / (point: CGPoint, divisor: CGFloat) -> CGPoint {
CGPoint(
x: point.x / divisor,
y: point.y / divisor
)
}
}
extension Sequence {
func pairwise() -> Zip2Sequence<[Element?], Self> {
zip([nil] + map(Optional.some), self)
}
}
Obviously this only deals in CGPoints
, not CLLocationCoordinate2D
or any other map-related data. Fine for drawing in views, though:
MKOverlayPathRenderer
final class BezierPolylineRenderer: MKOverlayPathRenderer {
var tension: CGFloat = 2.5
override func createPath() {
guard let multiPoint = overlay as? MKMultiPoint else {
assertionFailure("Expected MKMultiPoint")
return
}
let points = (0 ..< multiPoint.pointCount)
.map { multiPoint.points()[$0] }
.map { point(for: $0) }
path = UIBezierPath.from(points, tension: tension).cgPath
}
override func draw(
_: MKMapRect,
zoomScale: MKZoomScale,
in context: CGContext
) {
context.addPath(path)
applyStrokeProperties(to: context, atZoomScale: zoomScale)
context.setStrokeColor((strokeColor ?? .gray).cgColor)
context.setLineWidth(lineWidth / zoomScale)
context.strokePath()
}
}
final class Delegate: NSObject, MKMapViewDelegate {
func mapView(_: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if let polyline = overlay as? MKPolyline {
let renderer = BezierPolylineRenderer(overlay: polyline)
renderer.strokeColor = .blue
renderer.lineWidth = 10
return renderer
} else {
fatalError("Unexpected overlay type: \(overlay)")
}
}
}
let delegate = Delegate()
let map = MKMapView()
map.delegate = delegate
map.addOverlay(MKPolyline(coordinates: coordinates, count: coordinates.count))
Putting it all together, it looks like this:
... there are alternative methods to calculate control points for your bezier curves:
extension UIBezierPath {
static func from(_ points: [CGPoint], tension: CGFloat) -> UIBezierPath {
let path = UIBezierPath()
guard let first = points.first, points.count > 1 else {
return UIBezierPath()
}
var derivatives: [CGPoint] = []
for j in 0 ..< points.count {
let prev = points[max(j - 1, 0)]
let next = points[min(j + 1, points.count - 1)]
derivatives.append((next - prev) / tension)
}
path.move(to: first)
for i in 1 ..< points.count {
let cp1 = points[i - 1] + (derivatives[i - 1] / tension)
let cp2 = points[i] - (derivatives[i] / tension)
path.addCurve(to: points[i], controlPoint1: cp1, controlPoint2: cp2)
}
return path
}
}
The method above scans ahead and behind the current point to derive two control points, resulting in a smoother path: