ios - Dibujando UIBezierPath en el código UIView generado
iphone objective-c (6)
Tengo un UIView
agregado en el código en tiempo de ejecución.
Quiero dibujar un UIBezierPath
en él, pero ¿esto significa que tengo que anular drawRect
para UIView?
¿O hay otra forma de atraerlo en el UIView
personalizado?
Aquí está el código para generar UIView
:
UIView* shapeView = [[UIView alloc]initWithFrame:CGRectMake(xOrigin,yOrigin+(i*MENU_BLOCK_FRAME_HEIGHT), self.shapeScroll.frame.size.width, MENU_BLOCK_FRAME_HEIGHT)];
shapeView.clipsToBounds = YES;
Y aquí está la función para crear y devolver un UIBezierPath
:
- (UIBezierPath*)createPath
{
UIBezierPath* path = [[UIBezierPath alloc]init];
[path moveToPoint:CGPointMake(100.0, 50.0)];
[path addLineToPoint:CGPointMake(200.0,50.0)];
[path addLineToPoint:CGPointMake(200.0, 200.0)];
[path addLineToPoint:CGPointMake(100.0, 200.0)];
[path closePath];
return path;
}
No hace mucho tiempo que ni siquiera sabía cómo pronunciar Bézier, y mucho menos saber cómo usar las rutas de Bézier para crear una forma personalizada. Lo siguiente es lo que he aprendido. Resulta que no son tan atemorizantes como parecen al principio.
Cómo dibujar una ruta Bézier en una vista personalizada
Estos son los pasos principales:
- Diseña el contorno de la forma que quieras.
- Divida la ruta del contorno en segmentos de líneas, arcos y curvas.
- Construye ese camino programáticamente.
- Dibuje la ruta ya sea en
drawRect
o usando unCAShapeLayer
.
Diseño de contorno de forma
Podrías hacer cualquier cosa, pero como ejemplo, he elegido la forma a continuación. Podría ser una tecla emergente en un teclado.
Divide el camino en segmentos
Mire hacia atrás en su diseño de forma y divídalo en elementos más simples de líneas (para líneas rectas), arcos (para círculos y esquinas redondeadas) y curvas (para cualquier otra cosa).
Aquí está el aspecto de nuestro diseño de ejemplo:
- Negro son segmentos de línea
- Azul claro son segmentos de arco
- Rojo son curvas
- Los puntos anaranjados son los puntos de control para las curvas
- Los puntos verdes son los puntos entre los segmentos de ruta
- Las líneas punteadas muestran el rectángulo delimitador
- Los números azul oscuro son los segmentos en el orden en que se agregarán mediante programación
Construye el camino programáticamente
Arbitrariamente comenzaremos en la esquina inferior izquierda y trabajaremos en el sentido de las agujas del reloj. Usaré la cuadrícula en la imagen para obtener los valores xey de los puntos. Codificaré todo aquí, pero por supuesto no harías eso en un proyecto real.
El proceso básico es:
- Crea un nuevo
UIBezierPath
- Elija un punto de inicio en la ruta con
moveToPoint
- Agregue segmentos a la ruta
- línea:
addLineToPoint
- arco:
addArcWithCenter
- curva:
addCurveToPoint
- línea:
- Cierre el camino con
closePath
Aquí está el código para hacer la ruta en la imagen de arriba.
func createBezierPath() -> UIBezierPath {
// create a new path
let path = UIBezierPath()
// starting point for the path (bottom left)
path.move(to: CGPoint(x: 2, y: 26))
// *********************
// ***** Left side *****
// *********************
// segment 1: line
path.addLine(to: CGPoint(x: 2, y: 15))
// segment 2: curve
path.addCurve(to: CGPoint(x: 0, y: 12), // ending point
controlPoint1: CGPoint(x: 2, y: 14),
controlPoint2: CGPoint(x: 0, y: 14))
// segment 3: line
path.addLine(to: CGPoint(x: 0, y: 2))
// *********************
// ****** Top side *****
// *********************
// segment 4: arc
path.addArc(withCenter: CGPoint(x: 2, y: 2), // center point of circle
radius: 2, // this will make it meet our path line
startAngle: CGFloat(M_PI), // π radians = 180 degrees = straight left
endAngle: CGFloat(3*M_PI_2), // 3π/2 radians = 270 degrees = straight up
clockwise: true) // startAngle to endAngle goes in a clockwise direction
// segment 5: line
path.addLine(to: CGPoint(x: 8, y: 0))
// segment 6: arc
path.addArc(withCenter: CGPoint(x: 8, y: 2),
radius: 2,
startAngle: CGFloat(3*M_PI_2), // straight up
endAngle: CGFloat(0), // 0 radians = straight right
clockwise: true)
// *********************
// ***** Right side ****
// *********************
// segment 7: line
path.addLine(to: CGPoint(x: 10, y: 12))
// segment 8: curve
path.addCurve(to: CGPoint(x: 8, y: 15), // ending point
controlPoint1: CGPoint(x: 10, y: 14),
controlPoint2: CGPoint(x: 8, y: 14))
// segment 9: line
path.addLine(to: CGPoint(x: 8, y: 26))
// *********************
// **** Bottom side ****
// *********************
// segment 10: line
path.close() // draws the final line to close the path
return path
}
Nota: Algunos de los códigos anteriores se pueden reducir agregando una línea y un arco en un solo comando (ya que el arco tiene un punto de inicio implícito). Mira aquí para más detalles.
Dibuja el camino
Podemos dibujar el camino en una capa o en drawRect
.
Método 1: dibujar la ruta en una capa
Nuestra clase personalizada se ve así. CAShapeLayer
nuestra ruta de Bezier a un nuevo CAShapeLayer
cuando la vista se inicializa.
import UIKit
class MyCustomView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
// Create a CAShapeLayer
let shapeLayer = CAShapeLayer()
// The Bezier path that we made needs to be converted to
// a CGPath before it can be used on a layer.
shapeLayer.path = createBezierPath().cgPath
// apply other properties related to the path
shapeLayer.strokeColor = UIColor.blue.cgColor
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.lineWidth = 1.0
shapeLayer.position = CGPoint(x: 10, y: 10)
// add the new layer to our custom view
self.layer.addSublayer(shapeLayer)
}
func createBezierPath() -> UIBezierPath {
// see previous code for creating the Bezier path
}
}
Y creando nuestra vista en View Controller de esta manera
override func viewDidLoad() {
super.viewDidLoad()
// create a new UIView and add it to the view controller
let myView = MyCustomView()
myView.frame = CGRect(x: 100, y: 100, width: 50, height: 50)
myView.backgroundColor = UIColor.yellow
view.addSubview(myView)
}
Obtenemos...
Hmm, eso es un poco pequeño porque codifiqué todos los números. Sin embargo, puedo escalar el tamaño de la ruta, así:
let path = createBezierPath()
let scale = CGAffineTransform(scaleX: 2, y: 2)
path.apply(scale)
shapeLayer.path = path.cgPath
Método 2: dibujar camino en draw
Usar draw
es más lento que dibujar en la capa, por lo que este no es el método recomendado si no lo necesitas.
Aquí está el código revisado para nuestra vista personalizada:
import UIKit
class MyCustomView: UIView {
override func draw(_ rect: CGRect) {
// create path (see previous code)
let path = createBezierPath()
// fill
let fillColor = UIColor.white
fillColor.setFill()
// stroke
path.lineWidth = 1.0
let strokeColor = UIColor.blue
strokeColor.setStroke()
// Move the path to a new location
path.apply(CGAffineTransform(translationX: 10, y: 10))
// fill and stroke the path (always do these last)
path.fill()
path.stroke()
}
func createBezierPath() -> UIBezierPath {
// see previous code for creating the Bezier path
}
}
que nos da el mismo resultado ...
Estudio adicional
Realmente recomiendo mirar los siguientes materiales. Son lo que finalmente hizo que los caminos de Bézier fueran comprensibles para mí. (Y me enseñó a pronunciarlo: / bɛ zi eɪ /.)
- Pensando como un camino Bézier (Todo lo que he leído de este autor es bueno y la inspiración para mi ejemplo anterior vino de aquí).
- Codificación matemática: Episodio 19 - Bezier Curves (ilustraciones visuales entretenidas y buenas)
- Bezier Curves (cómo se usan en aplicaciones gráficas)
- Bezier Curves (buena descripción de cómo se derivan las fórmulas matemáticas)
Como señalaron los otros carteles, usar una capa de forma es una buena forma de hacerlo.
Las capas de forma a le darán un mejor rendimiento que la anulación de drawRect.
Si desea dibujar su ruta usted mismo, entonces sí, necesita anular drawRect para su clase de vista personalizada.
Sí, tienes que anular el drawrect si quieres dibujar algo. Crear un UIBezierPath se puede hacer en cualquier lugar, pero para dibujar algo tienes que hacerlo dentro del método drawrect
Debería llamar a setNeedsDisplay
si anula drawRect en una subclase de UIView que es básicamente una vista personalizada que dibuja algo en la pantalla, como líneas, imágenes, rectángulos.
Sería más fácil si usara CAShapeLayer
, así:
CAShapeLayer *shapeView = [[CAShapeLayer alloc] init];
Y establece su path
:
[shapeView setPath:[self createPath].CGPath];
Finalmente agrégalo:
[[self.view layer] addSublayer:shapeView];
Puede usar CAShapeLayer
para hacer esto.
Me gusta esto...
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [self createPath].CGPath;
shapeLayer.strokeColor = [UIColor redColor].CGColor; //etc...
shapeLayer.lineWidth = 2.0; //etc...
shapeLayer.position = CGPointMake(100, 100); //etc...
[self.layer addSublayer:shapeLayer];
Esto luego agregará y dibujará la ruta sin tener que anular drawRect
.
Hay múltiples formas de lograr lo que deseas. Los que más he visto son: anula drawRect, dibuja tu forma en un CAShapeLayer y luego agrégala como una subcapa a tu vista, o dibuja tu camino en otro contexto , guárdalo como una imagen y luego agrégalo a tu ver.
Todas estas son elecciones razonables, y cuál es la mejor depende de muchos otros factores, como por ejemplo, agregar continuamente formas, con qué frecuencia se lo llama, etc.