Dibujo de curvas suaves – Métodos necesarios

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


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

ACTUALIZADO A UNA RESPUESTA ABAJO

#define VALUE(_INDEX_) [NSValue valueWithCGPoint:points[_INDEX_]] #define POINT(_INDEX_) [(NSValue *)[points objectAtIndex:_INDEX_] CGPointValue] - (UIBezierPath*)smoothedPathWithGranularity:(NSInteger)granularity { NSMutableArray *points = [(NSMutableArray*)[self pointsOrdenetworking] 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 < 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; } - (PVPoint *)pointAppendingCGPoint:(CGPoint)CGPoint { PVPoint *newPoint = [[PVPoint alloc] initInsertingIntoManagedObjectContext:[self managedObjectContext]]; [newPoint setCGPoint:CGPoint]; [newPoint setOrder:[NSNumber numberWithUnsignedInteger:[[self points] count]]]; [[self mutableSetValueForKey:@"points"] addObject:newPoint]; [(NSMutableArray *)[self pointsOrdenetworking] addObject:newPoint]; [[self bezierPath] addLineToPoint:CGPoint]; return [newPoint autorelease]; if ([self bezierPath] && [pointsOrdenetworking count] > 3) { PVPoint *control1 = [pointsOrdenetworking objectAtIndex:[pointsOrdenetworking count] - 2]; PVPoint *control2 = [pointsOrdenetworking objectAtIndex:[pointsOrdenetworking count] - 1]; [bezierPath moveToPoint:[[pointsOrdenetworking objectAtIndex:[pointsOrdenetworking 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 pointsOrdenetworking] objectAtIndex:p] CGPoint]]; else [bezierPath addLineToPoint:[(PVPoint *)[[self pointsOrdenetworking] 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 usar splines Bezier. Estos proporcionan una curva muy suave A TRAVÉS de un set de puntos en lugar de un spline bezier 'alnetworkingedor' de puntos.

 // 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 implementó 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 algorithm Catmull-Rom si solo quieres una línea curva. Y el enlace que sugirió hace exactamente eso. Entonces, aquí hay una adición a su respuesta.

El siguiente código NO utiliza el algorithm y granularidad Catmull-Rom, pero dibuja una línea cuádruple curva (los puntos de control se calculan para usted). Esto es esencialmente lo que se hace en el tutorial de dibujo a mano alzada de Ios sugerido por Rakesh, pero en un método independiente que puede soltar en cualquier lugar (o en una categoría UIBezierPath) y get una spline quad-curved de la caja.

Necesita tener una matriz de CGPoint envuelta en NSValue

 + (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: introduzca la descripción de la imagen aquí

La key para get dos curvas bezier para unir sin problemas es que los puntos de control relevantes y los puntos de inicio / final en las curvas deben estar colineales. Piensa en el punto de control y el punto final como formando una línea tangente a la curva en el punto final. Si una curva comienza en el mismo punto donde termina otra, 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 el point2 , path2 comienza en el point2 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 paths 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 están colineales de esa manera).

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

dos caminos unidos sin problemas

Después de mirar su código, la razón por la que su curva es irregulares es que está pensando en puntos de control como puntos en la curva. En una curva de bezier, los puntos de control generalmente no están en la curva. Dado que está tomando los puntos de control de la curva, los puntos de control y el punto de intersección no son colineales, y las routes, por lo tanto, no se unen sin problemas.

Algunas buenas respuestas aquí, aunque creo que están lejos (la respuesta de user1244109 solo admite tangentes horizontales, no es útil para las curvas genéricas), o excesivamente complicada (lo siento, los fanáticos 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 natural que puede hacer es utilizar los puntos de contacto como los puntos de inicio y finalización. ¡No hagas esto! No hay puntos de control apropiados para usar. En cambio, intente esta idea: use los puntos de contacto como puntos de control y los puntos medios como los puntos de inicio / finalización. Está garantizado que tiene las tangentes adecuadas de esta manera, y el código es estúpido simple. Aquí está el algorithm:

  1. El punto "touch down" es el inicio de la ruta y la location tienda en prevPoint .
  2. Para cada location arrastrada, calcule midPoint , el punto entre currentPoint y prevPoint .
    1. Si esta es la primera location arrastrada, agregue currentPoint como un segmento de línea.
    2. Para todos los puntos en el futuro, agregue una curva cuádruple que termine en el midPoint y use el punto prevPoint como punto de control . Esto creará un segmento que curvas suavemente desde el punto anterior hasta el punto actual.
  3. Almacene currentPoint en prevPoint y repita # 2 hasta que finalice el process de arrastrar.
  4. Agregue el punto final como otro segmento recto, para terminar la ruta.

Esto da como resultado curvas muy atractivas, porque el uso de los puntos medios 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<UITouch>, withEvent event: UIEvent?) { let location = touchesSet.first!.locationInView(self) bezierPath.removeAllPoints() bezierPath.moveToPoint(location) prevPoint = location } override func touchesMoved(touchesSet: Set<UITouch>, 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<UITouch>, withEvent event: UIEvent?) { let location = touchesSet.first!.locationInView(self) bezierPath.addLineToPoint(location) } 

O bien, si tiene una variedad de puntos y desea build UIBezierPath de una sola vez:

 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) } 

Estas son mis notas:

ejemplo de algoritmo

Debemos observar algo antes de aplicar cualquier algorithm en los puntos capturados.

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

Ahora para get una línea fluida, hay tantas forms.

Algunas veces podemos lograrlo aplicando algorithms de polinomio de segundo grado o de polinomio 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, debemos 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 a Objective C y funciona shinymente para mí.

Para convertir el código de C # en el Objetivo C. Comprende el código C # línea por línea, incluso si no sabes C #, debes saber C ++ / Java?

Mientras se convierte:

  1. Reemplace Point struct utilizado aquí con CGPoint.

  2. Reemplace la matriz de puntos con NSMutableArray y almacene NSvalues ​​envolviendo CGPoints en ella.

  3. Reemplace todas las matrices dobles con NSMutableArrays y almacene el doble de NSNumber en él.

  4. use objectAtIndex: método en caso de subíndice para acceder a los elementos de la matriz.

  5. use replaceObjectAtIndex: withObject: para almacenar objects en un índice específico.

    Recuerde que NSMutableArray es una list vinculada y lo que C # usa son matrices dinámicas para que ya tengan índices existentes. En su caso, en un NSMutableArray si está vacío, no puede almacenar objects en índices aleatorios como lo hace el código C #. a veces en este código C #, pueblan el índice 1 antes del índice 0 y pueden hacerlo a medida que el índice 1 existe. en NSMutabelArrays aquí, el índice 1 debería estar allí si quieres llamar a replaceObject. así que antes de almacenar cualquier cosa, haga un método que agregue n objects NSNull en NSMutableArray.

ADEMÁS :

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

  1. matriz de primeros puntos de control.

  2. matriz de segundos puntos de control.

Estas matrices mantendrán primero 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 atravesarlos.

En tu caso mientras dibujas, necesitarás algunos cómo suministrar un set 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 tomando puntos de control tanto del arrays de puntos de control como del índice.

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

Lo que puede hacer es: simplemente mientras mueve el dedo, siga dibujando líneas rectas entre el punto anterior y el actual. Las líneas serán tan pequeñas que no serán visibles a simple vista, ya que son pequeñas líneas rectas pequeñas a less que se acerque.

ACTUALIZAR

Cualquier persona interesada en una implementación de Objective C del enlace anterior puede referirse a

este repository GitHub.

Lo escribí hace algún time y no es compatible con ARC, pero puedes editarlo fácilmente y eliminar algunas llamadas de liberación y autorelease y hacerlo funcionar con ARC.

Este solo genera dos matrices de puntos de control para un set de puntos a los que uno quiere unir usando bezier spline.

No necesito escribir mucho código.

Simplemente consulte el tutorial de dibujo a mano alzada de ios ; Realmente suaviza el dibujo, también hay un mecanismo de caching para que el performance no disminuya incluso cuando sigues dibujando continuamente.

Encontré un bonito tutorial que describe una ligera modificación en el dibujo de curva de Bezier que tiende a suavizar los bordes bastante bien. Es básicamente lo que Caleb se refiere a lo anterior acerca de 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 time. Y viene con un proyecto Xcode completamente funcional.

Probé todo lo anterior, pero no puedo hacer que funcione. Una de las respuestas produce un resultado roto para mí incluso. Al search 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 usas así:

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

Es realmente una solución robusta y orderada 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..<count { let x = points[i].x let y = points[i].y var dx = CGFloat(0) var dy = CGFloat(0) if (i == 0) { bezier.move(to: points[0]) let nextX = points[i + 1].x let nextY = points[i + 1].y prevDx = (nextX - x) / div prevDy = (nextY - y) / div prevX = x prevY = y } else if (i == count - 1) { dx = (x - prevX) / div dy = (y - prevY) / div } else { let nextX = points[i + 1].x let nextY = points[i + 1].y dx = (nextX - prevX) / div; dy = (nextY - prevY) / div; } bezier.addCurve(to: CGPoint(x: x, y: y), controlPoint1: CGPoint(x: prevX + prevDx, y: prevY + prevDy), controlPoint2: CGPoint(x: x - dx, y: y - dy)) prevDx = dx; prevDy = dy; prevX = x; prevY = y; }