Dibujar curvas suaves: métodos necesarios

¿Cómo suavizar un conjunto de puntos en una aplicación de dibujo de iOS MIENTRAS SE MUEVE? He probado UIBezierpaths pero todo lo que obtengo son puntas irregulares donde se cruzan, cuando cambio los puntos 1,2,3,4 – 2,3,4,5. He oído hablar de curvas spline y todos los otros tipos. Soy bastante nuevo en la progtwigción de iPhone y no entiendo cómo progtwigrlo en mi aplicación de dibujo de cuarzo. Un ejemplo sólido sería muy apreciado, he pasado semanas corriendo en círculos y parece que nunca puedo encontrar ningún código iOS para esta tarea. La mayoría de las publicaciones solo enlazan con una simulación java o páginas en wikipedia sobre el ajuste de curvas que no hace nada por mí. Además, no quiero cambiar a OpenGL ES. Espero que alguien finalmente pueda proporcionar el código para responder a esta pregunta circulante.


Este fue mi código para UIBezierPath, que dejó los bordes en la intersección ///

ACTUALIZADO A UNA RESPUESTA A CONTINUACIÓN

#define VALUE(_INDEX_) [NSValue valueWithCGPoint:points[_INDEX_]] #define POINT(_INDEX_) [(NSValue *)[points objectAtIndex:_INDEX_] CGPointValue] - (UIBezierPath*)smoothedPathWithGranularity:(NSInteger)granularity { NSMutableArray *points = [(NSMutableArray*)[self pointsOrdered] mutableCopy]; if (points.count < 4) return [self bezierPath]; // Add control points to make the math make sense [points insertObject:[points objectAtIndex:0] atIndex:0]; [points addObject:[points lastObject]]; UIBezierPath *smoothedPath = [self bezierPath]; [smoothedPath removeAllPoints]; [smoothedPath moveToPoint:POINT(0)]; for (NSUInteger index = 1; index < points.count - 2; index++) { CGPoint p0 = POINT(index - 1); CGPoint p1 = POINT(index); CGPoint p2 = POINT(index + 1); CGPoint p3 = POINT(index + 2); // now add n points starting at p1 + dx/dy up until p2 using Catmull-Rom splines for (int i = 1; i  3) { PVPoint *control1 = [pointsOrdered objectAtIndex:[pointsOrdered count] - 2]; PVPoint *control2 = [pointsOrdered objectAtIndex:[pointsOrdered count] - 1]; [bezierPath moveToPoint:[[pointsOrdered objectAtIndex:[pointsOrdered count] - 3] CGPoint]]; [[self bezierPath] addCurveToPoint:CGPoint controlPoint1:[control1 CGPoint] controlPoint2:[control2 CGPoint]]; } } - (BOOL)isComplete { return [[self points] count] > 1; } - (UIBezierPath *)bezierPath { if (!bezierPath) { bezierPath = [UIBezierPath bezierPath]; for (NSUInteger p = 0; p < [[self points] count]; p++) { if (!p) [bezierPath moveToPoint:[(PVPoint *)[[self pointsOrdered] objectAtIndex:p] CGPoint]]; else [bezierPath addLineToPoint:[(PVPoint *)[[self pointsOrdered] objectAtIndex:p] CGPoint]]; } [bezierPath retain]; } return bezierPath; } - (CGPathRef)CGPath { return [[self bezierPath] CGPath]; } 

pantalla del teléfono

Acabo de implementar algo similar en un proyecto en el que estoy trabajando. Mi solución fue usar una spline Catmull-Rom en lugar de utilizar splines Bezier. Estos proporcionan una curva muy suave A TRAVÉS de un conjunto de puntos en lugar de un bemol puntos spline ‘alrededor’.

 // Based on code from Erica Sadun #import "UIBezierPath+Smoothing.h" void getPointsFromBezier(void *info, const CGPathElement *element); NSArray *pointsFromBezierPath(UIBezierPath *bpath); #define VALUE(_INDEX_) [NSValue valueWithCGPoint:points[_INDEX_]] #define POINT(_INDEX_) [(NSValue *)[points objectAtIndex:_INDEX_] CGPointValue] @implementation UIBezierPath (Smoothing) // Get points from Bezier Curve void getPointsFromBezier(void *info, const CGPathElement *element) { NSMutableArray *bezierPoints = (__bridge NSMutableArray *)info; // Retrieve the path element type and its points CGPathElementType type = element->type; CGPoint *points = element->points; // Add the points if they're available (per type) if (type != kCGPathElementCloseSubpath) { [bezierPoints addObject:VALUE(0)]; if ((type != kCGPathElementAddLineToPoint) && (type != kCGPathElementMoveToPoint)) [bezierPoints addObject:VALUE(1)]; } if (type == kCGPathElementAddCurveToPoint) [bezierPoints addObject:VALUE(2)]; } NSArray *pointsFromBezierPath(UIBezierPath *bpath) { NSMutableArray *points = [NSMutableArray array]; CGPathApply(bpath.CGPath, (__bridge void *)points, getPointsFromBezier); return points; } - (UIBezierPath*)smoothedPathWithGranularity:(NSInteger)granularity; { NSMutableArray *points = [pointsFromBezierPath(self) mutableCopy]; if (points.count < 4) return [self copy]; // Add control points to make the math make sense [points insertObject:[points objectAtIndex:0] atIndex:0]; [points addObject:[points lastObject]]; UIBezierPath *smoothedPath = [self copy]; [smoothedPath removeAllPoints]; [smoothedPath moveToPoint:POINT(0)]; for (NSUInteger index = 1; index < points.count - 2; index++) { CGPoint p0 = POINT(index - 1); CGPoint p1 = POINT(index); CGPoint p2 = POINT(index + 1); CGPoint p3 = POINT(index + 2); // now add n points starting at p1 + dx/dy up until p2 using Catmull-Rom splines for (int i = 1; i < granularity; i++) { float t = (float) i * (1.0f / (float) granularity); float tt = t * t; float ttt = tt * t; CGPoint pi; // intermediate point pi.x = 0.5 * (2*p1.x+(p2.x-p0.x)*t + (2*p0.x-5*p1.x+4*p2.x-p3.x)*tt + (3*p1.x-p0.x-3*p2.x+p3.x)*ttt); pi.y = 0.5 * (2*p1.y+(p2.y-p0.y)*t + (2*p0.y-5*p1.y+4*p2.y-p3.y)*tt + (3*p1.y-p0.y-3*p2.y+p3.y)*ttt); [smoothedPath addLineToPoint:pi]; } // Now add p2 [smoothedPath addLineToPoint:p2]; } // finish by adding the last point [smoothedPath addLineToPoint:POINT(points.count - 1)]; return smoothedPath; } @end 

La implementación original de Catmull-Rom se basa en un código de Erica Sadun en uno de sus libros, lo modifiqué ligeramente para permitir una curva suavizada completa. Esto se implementa como una categoría en UIBezierPath y funcionó muy bien para mí.

La ruta original está en rojo, la ruta suavizada está en verde.

@Rakesh tiene toda la razón: no necesitas usar el algoritmo Catmull-Rom si solo quieres una línea curva. Y el enlace que sugirió hace exactamente eso. Así que aquí hay una adición a su respuesta .

El siguiente código NO utiliza el algoritmo y la granularidad de Catmull-Rom, sino que dibuja una línea de cuatro curvas (los puntos de control se calculan para usted). Esto es esencialmente lo que se hace en el tutorial ios de dibujo a mano alzada sugerido por Rakesh, pero en un método independiente que puede colocar en cualquier lugar (o en una categoría UIBezierPath) y obtener una spline de curva cuádruple lista para usar.

Necesita tener una matriz de CGPoint envuelta en NSValue ‘s

 + (UIBezierPath *)quadCurvedPathWithPoints:(NSArray *)points { UIBezierPath *path = [UIBezierPath bezierPath]; NSValue *value = points[0]; CGPoint p1 = [value CGPointValue]; [path moveToPoint:p1]; if (points.count == 2) { value = points[1]; CGPoint p2 = [value CGPointValue]; [path addLineToPoint:p2]; return path; } for (NSUInteger i = 1; i < points.count; i++) { value = points[i]; CGPoint p2 = [value CGPointValue]; CGPoint midPoint = midPointForPoints(p1, p2); [path addQuadCurveToPoint:midPoint controlPoint:controlPointForPoints(midPoint, p1)]; [path addQuadCurveToPoint:p2 controlPoint:controlPointForPoints(midPoint, p2)]; p1 = p2; } return path; } static CGPoint midPointForPoints(CGPoint p1, CGPoint p2) { return CGPointMake((p1.x + p2.x) / 2, (p1.y + p2.y) / 2); } static CGPoint controlPointForPoints(CGPoint p1, CGPoint p2) { CGPoint controlPoint = midPointForPoints(p1, p2); CGFloat diffY = abs(p2.y - controlPoint.y); if (p1.y < p2.y) controlPoint.y += diffY; else if (p1.y > p2.y) controlPoint.y -= diffY; return controlPoint; } 

Este es el resultado: enter image description here

La clave para obtener dos curvas de bezier para unir sin problemas es que los puntos de control relevantes y los puntos de inicio / final en las curvas deben ser colineales. Piense en el punto de control y en el punto final como una línea que es tangente a la curva en el punto final. Si una curva comienza en el mismo punto donde termina otro, y si ambos tienen la misma línea tangente en ese punto, la curva será suave. Aquí hay un poco de código para ilustrar:

 - (void)drawRect:(CGRect)rect { #define commonY 117 CGPoint point1 = CGPointMake(20, 20); CGPoint point2 = CGPointMake(100, commonY); CGPoint point3 = CGPointMake(200, 50); CGPoint controlPoint1 = CGPointMake(50, 60); CGPoint controlPoint2 = CGPointMake(20, commonY); CGPoint controlPoint3 = CGPointMake(200, commonY); CGPoint controlPoint4 = CGPointMake(250, 75); UIBezierPath *path1 = [UIBezierPath bezierPath]; UIBezierPath *path2 = [UIBezierPath bezierPath]; [path1 setLineWidth:3.0]; [path1 moveToPoint:point1]; [path1 addCurveToPoint:point2 controlPoint1:controlPoint1 controlPoint2:controlPoint2]; [[UIColor blueColor] set]; [path1 stroke]; [path2 setLineWidth:3.0]; [path2 moveToPoint:point2]; [path2 addCurveToPoint:point3 controlPoint1:controlPoint3 controlPoint2:controlPoint4]; [[UIColor orangeColor] set]; [path2 stroke]; } 

Observe que path1 termina en path2 , path2 inicia en path2 , y los puntos de control 2 y 3 comparten el mismo valor Y, commonY , con commonY . Puede cambiar cualquiera de los valores en el código como desee; siempre que esos tres puntos caigan en la misma línea, los dos caminos se unirán sin problemas. (En el código anterior, la línea es y = commonY . La línea no tiene que ser paralela al eje X, es más fácil ver que los puntos son colineales de esa manera).

Aquí está la imagen que dibuja el código anterior:

dos caminos se unieron sin problemas

Después de mirar su código, la razón por la cual su curva es irregular es porque está pensando en puntos de control como puntos en la curva. En una curva bezier, los puntos de control generalmente no están en la curva. Como toma los puntos de control de la curva, los puntos de control y el punto de intersección no son colineales, por lo que las rutas no se unen suavemente.

Algunas buenas respuestas aquí, aunque creo que están lejos (la respuesta de user1244109 solo admite tangentes horizontales, no útiles para curvas genéricas), o demasiado complicadas (lo siento, fans de Catmull-Rom).

Implementé esto de una manera mucho más simple, usando curvas Quad Bezier. Estos necesitan un punto de inicio, un punto final y un punto de control. Lo más natural que se puede hacer es usar los puntos de contacto como puntos de inicio y finalización. No hagas esto! No hay puntos de control apropiados para usar. En cambio, pruebe esta idea: use los puntos táctiles como puntos de control y los puntos medios como puntos de inicio / finalización. Está garantizado que tendrá las tangentes adecuadas de esta manera, y el código es estúpido simple. Aquí está el algoritmo:

  1. El punto de “aterrizaje” es el inicio de la ruta y la location tienda en prevPoint .
  2. Para cada ubicación arrastrada, calcule midPoint , el punto entre currentPoint y prevPoint .
    1. Si esta es la primera ubicación arrastrada, agregue currentPoint como un segmento de línea.
    2. Para todos los puntos en el futuro, agregue una curva cuadrangular que termine en el midPoint y use el punto prevPoint como punto de control . Esto creará un segmento que curva suavemente desde el punto anterior al punto actual.
  3. Almacene currentPoint en prevPoint , y repita # 2 hasta que el arrastre termine.
  4. Agregue el punto final como otro segmento recto, para finalizar la ruta.

Esto da como resultado curvas de muy buen aspecto, ya que el uso de los midPoints garantiza que la curva es una tangente suave en los puntos finales (ver foto adjunta).

El código Swift se ve así:

 var bezierPath = UIBezierPath() var prevPoint: CGPoint? var isFirst = true override func touchesBegan(touchesSet: Set, withEvent event: UIEvent?) { let location = touchesSet.first!.locationInView(self) bezierPath.removeAllPoints() bezierPath.moveToPoint(location) prevPoint = location } override func touchesMoved(touchesSet: Set, withEvent event: UIEvent?) { let location = touchesSet.first!.locationInView(self) if let prevPoint = prevPoint { let midPoint = CGPoint( x: (location.x + prevPoint.x) / 2, y: (location.y + prevPoint.y) / 2, ) if isFirst { bezierPath.addLineToPoint(midPoint) else { bezierPath.addQuadCurveToPoint(midPoint, controlPoint: prevPoint) } isFirst = false } prevPoint = location } override func touchesEnded(touchesSet: Set, withEvent event: UIEvent?) { let location = touchesSet.first!.locationInView(self) bezierPath.addLineToPoint(location) } 

O bien, si tiene una matriz de puntos y desea construir UIBezierPath en una sola toma:

 var points: [CGPoint] = [...] var bezierPath = UIBezierPath() var prevPoint: CGPoint? var isFirst = true // obv, there are lots of ways of doing this. let's // please refrain from yak shaving in the comments for point in points { if let prevPoint = prevPoint { let midPoint = CGPoint( x: (point.x + prevPoint.x) / 2, y: (point.y + prevPoint.y) / 2, ) if isFirst { bezierPath.addLineToPoint(midPoint) } else { bezierPath.addQuadCurveToPoint(midPoint, controlPoint: prevPoint) } isFirst = false } else { bezierPath.moveToPoint(point) } prevPoint = point } if let prevPoint = prevPoint { bezierPath.addLineToPoint(prevPoint) } 

Aquí están mis notas:

ejemplo de algoritmo

Necesitamos observar algo antes de aplicar cualquier algoritmo en los puntos capturados.

  1. En general, UIKit no da los puntos a la misma distancia.
  2. Necesitamos calcular los puntos intermedios entre dos CGPoints [que ha capturado con el método Touch moved]

Ahora para obtener una línea suave, hay muchas maneras.

Algunas veces podemos lograrlo aplicando algoritmos polinomiales de segundo grado o polinomios de tercer grado o catmullRomSpline

 - (float)findDistance:(CGPoint)point lineA:(CGPoint)lineA lineB:(CGPoint)lineB { CGPoint v1 = CGPointMake(lineB.x - lineA.x, lineB.y - lineA.y); CGPoint v2 = CGPointMake(point.x - lineA.x, point.y - lineA.y); float lenV1 = sqrt(v1.x * v1.x + v1.y * v1.y); float lenV2 = sqrt(v2.x * v2.x + v2.y * v2.y); float angle = acos((v1.x * v2.x + v1.y * v2.y) / (lenV1 * lenV2)); return sin(angle) * lenV2; } - (NSArray *)douglasPeucker:(NSArray *)points epsilon:(float)epsilon { int count = [points count]; if(count < 3) { return points; } //Find the point with the maximum distance float dmax = 0; int index = 0; for(int i = 1; i < count - 1; i++) { CGPoint point = [[points objectAtIndex:i] CGPointValue]; CGPoint lineA = [[points objectAtIndex:0] CGPointValue]; CGPoint lineB = [[points objectAtIndex:count - 1] CGPointValue]; float d = [self findDistance:point lineA:lineA lineB:lineB]; if(d > dmax) { index = i; dmax = d; } } //If max distance is greater than epsilon, recursively simplify NSArray *resultList; if(dmax > epsilon) { NSArray *recResults1 = [self douglasPeucker:[points subarrayWithRange:NSMakeRange(0, index + 1)] epsilon:epsilon]; NSArray *recResults2 = [self douglasPeucker:[points subarrayWithRange:NSMakeRange(index, count - index)] epsilon:epsilon]; NSMutableArray *tmpList = [NSMutableArray arrayWithArray:recResults1]; [tmpList removeLastObject]; [tmpList addObjectsFromArray:recResults2]; resultList = tmpList; } else { resultList = [NSArray arrayWithObjects:[points objectAtIndex:0], [points objectAtIndex:count - 1],nil]; } return resultList; } - (NSArray *)catmullRomSplineAlgorithmOnPoints:(NSArray *)points segments:(int)segments { int count = [points count]; if(count < 4) { return points; } float b[segments][4]; { // precompute interpolation parameters float t = 0.0f; float dt = 1.0f/(float)segments; for (int i = 0; i < segments; i++, t+=dt) { float tt = t*t; float ttt = tt * t; b[i][0] = 0.5f * (-ttt + 2.0f*tt - t); b[i][1] = 0.5f * (3.0f*ttt -5.0f*tt +2.0f); b[i][2] = 0.5f * (-3.0f*ttt + 4.0f*tt + t); b[i][3] = 0.5f * (ttt - tt); } } NSMutableArray *resultArray = [NSMutableArray array]; { int i = 0; // first control point [resultArray addObject:[points objectAtIndex:0]]; for (int j = 1; j < segments; j++) { CGPoint pointI = [[points objectAtIndex:i] CGPointValue]; CGPoint pointIp1 = [[points objectAtIndex:(i + 1)] CGPointValue]; CGPoint pointIp2 = [[points objectAtIndex:(i + 2)] CGPointValue]; float px = (b[j][0]+b[j][1])*pointI.x + b[j][2]*pointIp1.x + b[j][3]*pointIp2.x; float py = (b[j][0]+b[j][1])*pointI.y + b[j][2]*pointIp1.y + b[j][3]*pointIp2.y; [resultArray addObject:[NSValue valueWithCGPoint:CGPointMake(px, py)]]; } } for (int i = 1; i < count-2; i++) { // the first interpolated point is always the original control point [resultArray addObject:[points objectAtIndex:i]]; for (int j = 1; j < segments; j++) { CGPoint pointIm1 = [[points objectAtIndex:(i - 1)] CGPointValue]; CGPoint pointI = [[points objectAtIndex:i] CGPointValue]; CGPoint pointIp1 = [[points objectAtIndex:(i + 1)] CGPointValue]; CGPoint pointIp2 = [[points objectAtIndex:(i + 2)] CGPointValue]; float px = b[j][0]*pointIm1.x + b[j][1]*pointI.x + b[j][2]*pointIp1.x + b[j][3]*pointIp2.x; float py = b[j][0]*pointIm1.y + b[j][1]*pointI.y + b[j][2]*pointIp1.y + b[j][3]*pointIp2.y; [resultArray addObject:[NSValue valueWithCGPoint:CGPointMake(px, py)]]; } } { int i = count-2; // second to last control point [resultArray addObject:[points objectAtIndex:i]]; for (int j = 1; j < segments; j++) { CGPoint pointIm1 = [[points objectAtIndex:(i - 1)] CGPointValue]; CGPoint pointI = [[points objectAtIndex:i] CGPointValue]; CGPoint pointIp1 = [[points objectAtIndex:(i + 1)] CGPointValue]; float px = b[j][0]*pointIm1.x + b[j][1]*pointI.x + (b[j][2]+b[j][3])*pointIp1.x; float py = b[j][0]*pointIm1.y + b[j][1]*pointI.y + (b[j][2]+b[j][3])*pointIp1.y; [resultArray addObject:[NSValue valueWithCGPoint:CGPointMake(px, py)]]; } } // the very last interpolated point is the last control point [resultArray addObject:[points objectAtIndex:(count - 1)]]; return resultArray; } 

Para lograr esto, necesitamos usar este método. BezierSpline el código está en C # para generar matrices de puntos de control para una spline bezier. Convertí este código en Objective C y funciona de manera shiny para mí.

Para convertir el código de C # a Objective C. comprenda el código de C # línea por línea, incluso si no conoce C #, ¿debe saber C ++ / Java?

Al convertir:

  1. Reemplace la estructura de punto utilizada aquí con CGPoint.

  2. Reemplace la matriz de puntos con NSMutableArray y almacene valores NS que envuelven CGPoints en ella.

  3. Reemplace todas las matrices dobles con NSMutableArrays y almacene NSNumber envolviendo doble en ella.

  4. utilice el método objectAtIndex: en el caso de subíndices para acceder a los elementos de la matriz.

  5. utilice replaceObjectAtIndex: withObject: para almacenar objetos en un índice específico.

    Recuerde que NSMutableArray es una lista enlazada y lo que C # usa son arreglos dynamics, por lo que ya tienen índices existentes. En su caso, en un NSMutableArray si está vacío, no puede almacenar objetos en índices aleatorios como lo hace el código C #. ellos a veces en este código de C #, pueblan el índice 1 antes del índice 0 y pueden hacerlo como el índice 1 existe. en NSMutabelArrays aquí, el índice 1 debería estar allí si desea llamar a replaceObject en él. por lo tanto, antes de almacenar algo, cree un método que agregará n objetos NSNull en NSMutableArray.

ADEMÁS :

bueno, esta lógica tiene un método estático que aceptará una matriz de puntos y le dará dos matrices:

  1. matriz de primeros puntos de control.

  2. conjunto de segundos puntos de control.

Estas matrices mantendrán el primer y segundo punto de control para cada curva entre dos puntos que pase en la primera matriz.

En mi caso, ya tenía todos los puntos y podía dibujar una curva a través de ellos.

En tu caso, al dibujar, necesitarás alguna forma de suministrar un conjunto de puntos a través del cual deseas que pase una curva suave.

y actualice llamando a setNeedsDisplay y dibuje la spline que no es más que UIBezierPath entre dos puntos adyacentes en la primera matriz. y tomar puntos de control de ambas matrices de punto de control en cuanto al índice.

El problema en su caso es que es difícil de entender mientras mueve todos los puntos críticos que debe tomar.

Lo que puedes hacer es: simplemente mientras mueves el dedo sigue dibujando líneas rectas entre el punto anterior y el actual. Las líneas serán tan pequeñas que no será visible a simple vista que son pequeñas líneas rectas a menos que haga un acercamiento.

ACTUALIZAR

Cualquier persona interesada en la implementación de Objective C del enlace de arriba puede referirse a

este repository GitHub.

Hace algún tiempo lo escribí y no es compatible con ARC, pero puede editarlo fácilmente y eliminar algunas llamadas de liberación y liberación automática y hacer que funcione con ARC.

Éste solo genera dos conjuntos de puntos de control para un conjunto de puntos al que se quiere unir utilizando bezier spline.

No necesitas escribir tanto código.

Solo consulte el tutorial ios de dibujo a mano alzada ; realmente suaviza el dibujo, también hay un mecanismo de caché para que el rendimiento no disminuya incluso cuando sigas dibujando continuamente.

Encontré un tutorial bastante bueno que describe una ligera modificación en el dibujo de la curva de Bezier que tiende a suavizar los bordes bastante bien. Esencialmente, a lo que Caleb se refiere anteriormente es a colocar los puntos finales de unión en la misma línea que los puntos de control. Es uno de los mejores tutoriales (sobre cualquier cosa) que he leído en mucho tiempo. Y viene con un proyecto Xcode en pleno funcionamiento.

Intenté todo lo anterior, pero no puedo hacerlo funcionar. Una de las respuestas arroja un resultado roto incluso para mí. Al buscar más encontré esto: https://github.com/sam-keene/uiBezierPath-hermite-curve . No escribí este código, pero lo implementé y funciona muy bien. Solo copie UIBezierPath + Interpolation.m / h y CGPointExtension.m / h. Entonces lo usa así:

 UIBezierPath *path = [UIBezierPath interpolateCGPointsWithHermite:arrayPoints closed:YES]; 

Realmente es una solución robusta y ordenada en general.

Rápido:

  let point1 = CGPoint(x: 50, y: 100) let point2 = CGPoint(x: 50 + 1 * CGFloat(60) * UIScreen.main.bounds.width / 375, y: 200) let point3 = CGPoint(x: 50 + 2 * CGFloat(60) * UIScreen.main.bounds.width / 375, y: 250) let point4 = CGPoint(x: 50 + 3 * CGFloat(60) * UIScreen.main.bounds.width / 375, y: 50) let point5 = CGPoint(x: 50 + 4 * CGFloat(60) * UIScreen.main.bounds.width / 375, y: 100) let points = [point1, point2, point3, point4, point5] let bezier = UIBezierPath() let count = points.count var prevDx = CGFloat(0) var prevDy = CGFloat(0) var prevX = CGFloat(0) var prevY = CGFloat(0) let div = CGFloat(7) for i in 0.. 
    Intereting Posts