UIBezierPath es una clase de UIKit que encapsula una ruta vectorial para gráficos 2D en iOS. Combina la descripción geométrica del contorno con capacidades de dibujo, permitiendo crear líneas, curvas Bezier, arcos y rectángulos. A diferencia de CGPath, UIBezierPath contiene sus propios métodos de dibujo directamente en el contexto gráfico actual. Según Apple Developer Documentation, 2026, UIBezierPath admite todas las operaciones estándar de Quartz 2D en un envoltorio orientado a objetos.
Puntos clave
UIBezierPath es una clase del framework UIKit que hereda de NSObject e implementa NSCopying, NSSecureCoding. Proporciona una API Objective-C/Swift de alto nivel para crear y dibujar contornos vectoriales. Internamente, UIBezierPath almacena una referencia a CGPathRef — un objeto de Core Graphics — pero añade métodos convenientes para dibujo, modificación de geometría y gestión de estilos.
La principal ventaja de UIBezierPath sobre el uso directo de CGPath son los métodos de dibujo integrados. Llamar a fill() o stroke() en una instancia de UIBezierPath configura automáticamente el contexto y dibuja el trazado. La clase también admite las propiedades lineWidth, lineCapStyle, lineJoinStyle, miterLimit y flatness, permitiendo controlar la apariencia sin un objeto separado para almacenar estilos.
UIBezierPath se utiliza en la mayoría de las aplicaciones iOS para dibujo personalizado dentro de UIView: desde iconos simples hasta gráficos animados complejos. El método draw(_:) de la clase UIView es el lugar principal donde los desarrolladores crean y dibujan UIBezierPath. La clase es totalmente compatible con Core Animation y se puede usar para animación de trazados a través de CAShapeLayer y CABasicAnimation.
El método move(to:) mueve el punto de inicio del trazado a las coordenadas especificadas sin dibujar. Esta es una llamada obligatoria antes de construir cualquier segmento. addLine(to:) añade un segmento recto desde la posición actual hasta el punto indicado. El método close() cierra el contorno dibujando una línea desde el último punto hasta el primer punto del último move(to:).
UIBezierPath proporciona varios inicializadores convenientes para formas predefinidas. init(rect:) crea un rectángulo, init(roundedRect:cornerRadius:) crea un rectángulo con esquinas redondeadas. El parámetro UIRectCorner permite redondear solo las esquinas seleccionadas, por ejemplo topLeft y bottomRight, dejando las demás rectas. Esto se usa ampliamente para crear botones y tarjetas personalizados.
init(ovalIn:) crea una elipse inscrita en el rectángulo especificado. Si el rectángulo es un cuadrado, la elipse se convierte en un círculo. init(roundedRect:byRoundingCorners:cornerRadii:) es la opción más flexible, permitiendo diferentes radios para cada esquina. Este inicializador apareció en iOS 6 y sigue siendo el estándar para crear contenedores redondeados.
let path = UIBezierPath()
path.move(to: CGPoint(x: 50, y: 50))
path.addLine(to: CGPoint(x: 200, y: 50))
path.addLine(to: CGPoint(x: 200, y: 150))
path.addLine(to: CGPoint(x: 125, y: 200))
path.addLine(to: CGPoint(x: 50, y: 150))
path.close()
addQuadCurve(to:controlPoint:) añade una curva Bezier cuadrática con un punto de control. Este tipo de curva se utiliza para curvaturas suaves simples cuando se requiere minimizar los cálculos. Una curva cuadrática es siempre una parábola y está garantizado que se encuentra dentro del triángulo formado por el punto inicial, el de control y el final.
addCurve(to:controlPoint1:controlPoint2:) añade una curva Bezier cúbica con dos puntos de control. Las curvas cúbicas se utilizan en todos los editores vectoriales modernos y fuentes TrueType. Dos puntos de control proporcionan más grados de libertad para un control preciso de la forma. Este método se recomienda para construir contornos orgánicos y complejos.
Al construir curvas, es importante mantener el principio de continuidad — la suavidad en el punto de unión de los segmentos. Para continuidad C1 (tangente), el punto de control de la siguiente curva debe estar en la misma línea que el punto de control de la anterior. Para continuidad C2 (curvatura), se requiere una coordinación más compleja, que se proporciona automáticamente en CAShapeLayer al animar entre dos UIBezierPath con el mismo número de puntos de control.
| Método | Tipo | Puntos de control | Grado |
|---|---|---|---|
| addQuadCurve | Bezier cuadrática | 1 | 2 |
| addCurve | Bezier cúbica | 2 | 3 |
| addArc | Arco | — | — |
addArc(withCenter:radius:startAngle:endAngle:clockwise:) añade un arco circular con los parámetros especificados. Los ángulos se indican en radianes respecto al centro del círculo. El ángulo cero corresponde a la dirección hacia la derecha (las 3 en punto en una esfera de reloj). El parámetro clockwise determina la dirección: true para sentido horario, false para antihorario. Los arcos son indispensables para gráficos circulares, velocímetros y anillos de progreso interactivos.
Para una circunferencia completa, usa startAngle = 0 y endAngle = .pi * 2. Para crear un sector (como parte de un gráfico circular), usa addArc seguido de addLine(to:) hacia el centro y close(). UIBezierPath no tiene un método addCircle incorporado — un círculo se crea mediante init(ovalIn:) con un rectángulo cuadrado o mediante addArc con un ángulo completo.
El método addArc(withCenter:radius:startAngle:endAngle:clockwise:) ejecuta automáticamente move(to:) hacia el punto de inicio del arco si la posición actual no coincide. Si la posición actual difiere, se dibuja una línea recta entre ella y el inicio del arco. Este comportamiento difiere de Core Graphics, donde se requiere un move(to:) explícito antes de cada nuevo contorno.
let center = CGPoint(x: 150, y: 150)
let slice = UIBezierPath()
slice.move(to: center)
slice.addArc(
withCenter: center,
radius: 100,
startAngle: 0,
endAngle: .pi * 0.75,
clockwise: true
)
slice.close()
fill() rellena el interior del trazado con el color fillColor actual o el color establecido en el contexto gráfico mediante setFill(). Para trazados con autointersecciones, la regla de relleno se determina por la propiedad usesEvenOddFillRule. Si es true, se usa la regla par-impar: un punto se considera dentro de la figura si un rayo desde él cruza el contorno un número impar de veces.
stroke() dibuja el contorno del trazado con las configuraciones actuales de lineWidth, lineCapStyle y lineJoinStyle. lineCapStyle define la forma de los extremos de los segmentos abiertos: .butt (corte recto), .round (semicircular), .square (cuadrado con saliente). lineJoinStyle define la forma de las uniones de las esquinas: .miter (agudo), .round (redondeado), .bevel (biselado).
Para líneas discontinuas se usa la propiedad setLineDash(_:count:phase:). Acepta un array de longitudes alternas de trazos y espacios, el número de elementos y la fase inicial. La representación del discontinuo se realiza durante la rasterización del trazado — los patrones complejos no aumentan la complejidad geométrica del contorno. CAShapeLayer admite la animación de la propiedad strokeEnd para un efecto de “dibujo” de principio a fin.
override func draw(_ rect: CGRect) {
let path = UIBezierPath(ovalIn: CGRect(x: 50, y: 50, width: 200, height: 200))
UIColor.blue.setFill()
UIColor.black.setStroke()
path.lineWidth = 4
path.fill()
path.stroke()
}
Vamos a crear un indicador de progreso personalizado en forma de anillo usando UIBezierPath. El anillo se construye a partir de dos arcos del mismo radio con diferentes colores de trazo. El primer arco es un círculo completo en gris (fondo), el segundo es un segmento que muestra el progreso. Para la animación se usan CAShapeLayer y CABasicAnimation.
class RingView: UIView {
private let progressLayer = CAShapeLayer()
override func layoutSubviews() {
super.layoutSubviews()
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let radius = bounds.width * 0.4
let lineWidth: CGFloat = 12
let backgroundRing = UIBezierPath()
backgroundRing.addArc(withCenter: center, radius: radius,
startAngle: -.pi / 2, endAngle: .pi * 1.5, clockwise: true)
progressLayer.path = backgroundRing.cgPath
progressLayer.strokeColor = UIColor.systemBlue.cgColor
progressLayer.lineWidth = lineWidth
progressLayer.lineCap = .round
progressLayer.fillColor = UIColor.clear.cgColor
progressLayer.strokeEnd = 0
layer.addSublayer(progressLayer)
}
func animateProgress(to value: CGFloat) {
CABasicAnimation(keyPath: "strokeEnd").apply {
$0.toValue = value
$0.duration = 1.5
$0.timingFunction = CAMediaTimingFunction(name: .easeOut)
progressLayer.add($0, forKey: nil)
}
progressLayer.strokeEnd = value
}
}
En este ejemplo, UIBezierPath se usa para crear un contorno circular, que luego se pasa a CAShapeLayer a través de la propiedad cgPath. La animación de strokeEnd de 0 a 1 visualiza el llenado del anillo. Este enfoque se usa en indicadores de carga, rastreadores de fitness y widgets de progreso. El método layoutSubviews garantiza la reconstrucción del trazado cuando cambia el tamaño de la View.
lineCap = .round añade extremos redondeados al arco, dando al indicador un aspecto más cuidado. La propiedad strokeEnd es una propiedad animable de CAShapeLayer que no requiere redibujar todo el contorno en cada fotograma. La animación se ejecuta completamente en la GPU a través de Core Animation, garantizando 60 FPS sin carga en la CPU.
Preguntas frecuentes
UIBezierPath es un envoltorio orientado a objetos sobre CGPath que contiene métodos incorporados fill() y stroke(). CGPath es un objeto C inmutable de Core Graphics que requiere dibujo explícito a través de CGContext. UIBezierPath es más conveniente para UIKit, CGPath es más eficiente para reutilización.
Usa init(roundedRect:byRoundingCorners:cornerRadii:) con los parámetros .topLeft y .topRight para roundedRect. Este es el único método de UIBezierPath que permite redondear selectivamente esquinas sin crear un trazado compuesto complejo.
La propiedad cgPath devuelve un CGPathRef inmutable que se puede pasar a CAShapeLayer o usar con diferentes colores y grosores. Las modificaciones de UIBezierPath (movimiento de puntos) no afectan al cgPath obtenido previamente hasta que se actualice explícitamente.
Verifica usesEvenOddFillRule — si el contorno tiene autointersecciones, la regla par-impar puede dejar áreas sin rellenar. Establece usesEvenOddFillRule = false para la regla de relleno estándar non-zero winding.
Usa CABasicAnimation con la clave “path” en CAShapeLayer. Ambos estados del trazado deben contener el mismo número de segmentos y puntos de control — de lo contrario, la animación será entrecortada o no funcionará.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también