iphone objective-c uibezierpath

iphone - Ruta de resta UIBezierPath



objective-c (7)

Con la respuesta de @Patrick Pijnappel, puede preparar el área de juegos de prueba para una prueba rápida

import UIKit import PlaygroundSupport let view = UIView(frame: CGRect(x: 0, y: 0, width: 375, height: 647)) view.backgroundColor = UIColor.green let someView = UIView(frame: CGRect(x:50, y: 50, width:250, height:250)) someView.backgroundColor = UIColor.red view.addSubview(someView) let shapeLayer = CAShapeLayer() shapeLayer.frame = someView.bounds shapeLayer.path = UIBezierPath(roundedRect: someView.bounds, byRoundingCorners: [UIRectCorner.bottomLeft,UIRectCorner.bottomRight] , cornerRadii: CGSize(width: 5.0, height: 5.0)).cgPath someView.layer.mask = shapeLayer someView.layer.masksToBounds = true let rect = CGRect(x:0, y:0, width:200, height:100) let cornerRadius:CGFloat = 5 let subPathSideSize:CGFloat = 25 let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius) let leftSubPath = UIBezierPath(arcCenter: CGPoint(x:0, y:rect.height / 2), radius: subPathSideSize / 2, startAngle: CGFloat(M_PI_2), endAngle: CGFloat(M_PI + M_PI_2), clockwise: false) leftSubPath.close() let rightSubPath = UIBezierPath(arcCenter: CGPoint(x:rect.width, y:rect.height / 2), radius: subPathSideSize / 2, startAngle: CGFloat(M_PI_2), endAngle: CGFloat(M_PI + M_PI_2), clockwise: true) rightSubPath.close() path.append(leftSubPath) path.append(rightSubPath.reversing()) path.append(path) let mask = CAShapeLayer() mask.frame = shapeLayer.bounds mask.path = path.cgPath someView.layer.mask = mask view PlaygroundPage.current.liveView = view

Al usar [UIBezierPath bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:] , puedo crear una vista redondeada, como esta:

¿Cómo podría restar otra ruta de esta (o de alguna otra manera), para crear una ruta como esta:

¿Hay alguna manera de que pueda hacer algo como esto? Pseudocódigo

UIBezierPath *bigMaskPath = [UIBezierPath bezierPathWithRoundedRect:bigView.bounds byRoundingCorners:(UIRectCornerTopLeft|UIRectCornerTopRight) cornerRadii:CGSizeMake(18, 18)]; UIBezierPath *smallMaskPath = [UIBezierPath bezierPathWithRoundedRect:smalLView.bounds byRoundingCorners:(UIRectCornerTopLeft|UIRectCornerTopRight) cornerRadii:CGSizeMake(18, 18)]; UIBezierPath *finalPath = [UIBezierPath pathBySubtractingPath:smallMaskPath fromPath:bigMaskPath];


En lugar de restar, pude resolver creando un CGPath utilizando CGPathAddLineToPoint y CGPathAddArcToPoint . Esto también se puede hacer con un UIBiezerPath con código similar.


En realidad, hay una forma mucho más sencilla para la mayoría de los casos, por ejemplo en Swift:

path.append(cutout.reversing())

Esto funciona porque la regla de relleno predeterminada es la regla de devanado que no es cero .


Esto debería hacerlo, ajusta los tamaños a tu gusto:

CGRect outerRect = {0, 0, 200, 200}; CGRect innerRect = CGRectInset(outerRect, 30, 30); UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:outerRect cornerRadius:10]; [path appendPath:[UIBezierPath bezierPathWithRoundedRect:innerRect cornerRadius:5]]; path.usesEvenOddFillRule = YES; [[UIColor orangeColor] set]; [path fill];

Otra forma realmente simple de obtener el efecto que está buscando es simplemente dibujar el redondeo exterior, cambiar los colores y dibujar el interior sobre él.


He estado golpeando mi cabeza contra la pared tratando de averiguar cómo hacer esto con múltiples CGPaths superpuestos. Si se superpone más de una vez, se rellena con las soluciones proporcionadas anteriormente. Resulta que la manera de lograr realmente un efecto de ''resta'' con múltiples rutas superpuestas es simplemente configurar el modo de fusión del contexto para que se borre.

CGContextSetBlendMode(ctx, kCGBlendModeClear);


Patrick proporcionó una mejora / alternativa a la respuesta del usuario NSResponder utilizando la regla de devanado que no es cero . Aquí hay una implementación completa en Swift para cualquiera que esté buscando una respuesta ampliada.

UIGraphicsBeginImageContextWithOptions(CGSize(width: 200, height: 200), false, 0.0) let context = UIGraphicsGetCurrentContext() let rectPath = UIBezierPath(roundedRect: CGRectMake(0, 0, 200, 200), cornerRadius: 10) var cutoutPath = UIBezierPath(roundedRect: CGRectMake(30, 30, 140, 140), cornerRadius: 10) rectPath.appendPath(cutoutPath.bezierPathByReversingPath()) UIColor.orangeColor().set() outerForegroundPath.fill() let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext()

Aquí hay un resumen de esto.

Puedes poner esto en un Guión gráfico para ver y jugar con la salida.


Si quieres trazar el camino restado, estás por tu cuenta. Apple no proporciona una API que devuelve (o solo traza) la resta de una ruta de otra.

Si solo desea completar el camino restado (como en su imagen de ejemplo), puede hacerlo usando el camino de recorte. Aunque tienes que usar un truco. Cuando agrega un camino al trazado de recorte, el nuevo trazado de recorte es la intersección del trazado de recorte anterior y el camino agregado. Entonces, si solo agrega smallMaskPath al trazado de recorte, terminará llenando solo la región dentro de smallMaskPath , que es lo opuesto a lo que desea.

Lo que debe hacer es intersectar el trazado de recorte existente con el inverso de smallMaskPath . Afortunadamente, puedes hacerlo fácilmente usando la regla de liquidación incluso impar. Puede leer acerca de la regla impar en la Guía de programación de cuarzo 2D .

La idea básica es que creamos una ruta compuesta con dos rutas secundarias: su smallMaskPath y un enorme rectángulo que encierre completamente su smallMaskPath y cada píxel que desee rellenar. Debido a la regla impar, cada píxel dentro de smallMaskPath se tratará como fuera de la ruta compuesta, y cada píxel fuera de smallMaskPath se tratará como dentro de la ruta compuesta.

Así que vamos a crear este camino compuesto. Comenzaremos con el gran rectángulo. Y no hay ningún rectángulo más grande que el rectángulo infinito:

UIBezierPath *clipPath = [UIBezierPath bezierPathWithRect:CGRectInfinite];

Ahora lo convertimos en una ruta compuesta agregándole smallMaskPath :

[clipPath appendPath:smallMaskPath];

A continuación, configuramos la ruta para utilizar la regla de impar par:

clipPath.usesEvenOddFillRule = YES;

Antes de recortar a esta ruta, debemos guardar el estado de los gráficos para que podamos deshacer el cambio en la ruta de recorte cuando hayamos terminado:

CGContextSaveGState(UIGraphicsGetCurrentContext()); {

Ahora podemos modificar el trazado de recorte:

[clipPath addClip];

y podemos llenar bigMaskPath :

[[UIColor orangeColor] setFill]; [bigMaskPath fill];

Finalmente restauramos el estado de los gráficos, deshaciendo el cambio en el trazado de recorte:

} CGContextRestoreGState(UIGraphicsGetCurrentContext());

Aquí está el código completo en caso de que quiera copiarlo / pegarlo:

UIBezierPath *clipPath = [UIBezierPath bezierPathWithRect:CGRectInfinite]; [clipPath appendPath:smallMaskPath]; clipPath.usesEvenOddFillRule = YES; CGContextSaveGState(UIGraphicsGetCurrentContext()); { [clipPath addClip]; [[UIColor orangeColor] setFill]; [bigMaskPath fill]; } CGContextRestoreGState(UIGraphicsGetCurrentContext());