Cómo personalizar los controles en iOS
Durante el proceso de desarrollo, a veces los controles estándar de UIKit no pueden satisfacer nuestras necesidades. Por ejemplo, necesita un control que pueda ayudar a los usuarios a seleccionar convenientemente un valor de ángulo entre 0 y 360°. para personalizar el control según sus propias necesidades.
El control para seleccionar valores de ángulo se puede implementar de la siguiente manera: cree un control deslizante circular y el usuario puede seleccionar el valor del ángulo arrastrando el controlador. De hecho, es posible que haya visto controles de este tipo en otras plataformas, pero no existen en UIKit.
Este artículo implementa un control para seleccionar valores de ángulo para introducir la personalización del control. Primero echemos un vistazo a lo que queremos hacer:
1. Subclase UIControl
UIControl es una subclase de UIView, que también es la clase principal de todos los controles UIKit (por ejemplo). UIButton, UISlider, UISwitch, etc.).
La función principal de UIControl es crear la lógica correspondiente para distribuir acciones a los objetivos correspondientes. Además, en 90 casos, dibujará la interfaz de usuario según su propio estado (como Destacado, Seleccionado y). Discapacitados, etc).
A través de UIControl, gestionamos principalmente tres tareas importantes:
Dibujar la interfaz de usuario
Seguimiento de las operaciones del usuario
Modo de acción objetivo
En el control deslizante circular de este artículo, tenemos que hacer lo siguiente:
Personalizar una interfaz de usuario (el control deslizante circular en sí) a través de la cual el usuario puede usar el controlador para interactuar con la interfaz. La operación de interacción del usuario se convertirá en la acción correspondiente al objetivo del control (el control convierte el origen del marco del botón deslizante en un valor entre 0-360 y lo usa en el objetivo/acción).
Se recomienda descargar el proyecto de muestra completo desde el enlace al final del artículo al estudiar este artículo.
Desglosaré las tres tareas importantes enumeradas anteriormente una por una.
Estos pasos son modulares, por lo que si no está interesado en dibujar la interfaz, puede omitir Dibujar la interfaz de usuario y aprender los siguientes pasos directamente.
Abra el archivo TBCircluarSlider.m en el archivo del proyecto. Entonces comience a estudiar el contenido a continuación.
1.1 Dibujando la interfaz de usuario
Prefiero usar Core Graphics. Lo único que usa UIKit es mostrar el valor del control deslizante a través del campo de texto.
Recordatorio: aquí se requieren algunos conocimientos de Core Graphics. No importa si no lo comprende. Intentaré explicar el código en detalle.
Primero echemos un vistazo a los diferentes componentes del control, que serán más propicios para el aprendizaje posterior.
Primero, utiliza un anillo negro como fondo del control deslizante.
El área activa es un efecto degradado de azul a morado.
El usuario selecciona un valor arrastrando el botón de control a continuación:
Finalmente, un TextField que muestra el valor seleccionado. En la próxima versión, planeo permitir al usuario ingresar valores de ángulos a través del teclado.
La función drawRect se usa principalmente para dibujar la interfaz de control. Primero, necesitamos obtener el contexto gráfico utilizado actualmente, como se muestra en el siguiente código:
1
.CGContextRef ctx = UIGraphicsGetCurrentContext();
1.1.1 Dibujar el fondo
El fondo es de 360°, así que simplemente use CGContextAddArc para agregar la ruta correcta al contexto gráfico y establezca el trazo correcto.
El siguiente código puede completar el dibujo de fondo:
//Agregar la ruta del arco
CGContextAddArc(ctx, self.frame.size.width/ 2, self.frame.size.height/2, radio, 0, M_PI *2, 0);
//Establece el color del trazo
[[UIColor blackColor]setStroke] ;
//establecer ancho y límite de línea
CGContextSetLineWidth(ctx, TB_BACKGROUND_WIDTH);
CGContextSetLineCap(ctx, kCGLineCapButt);
// ¡dibújalo!
CGContextDrawPath(ctx, kCGPathStroke);
Los parámetros de la función CGContextArc incluyen el contexto gráfico, el punto de coordenadas central del radián y el radio (que es un variable privada), luego es el ángulo al principio y al final del arco (puede ver algunos métodos de cálculo matemático en el encabezado del archivo TBCircularSlider.m. El último parámetro indica la dirección del dibujo, 0 significa en sentido antihorario).
Las siguientes tres líneas de código se utilizan para establecer cierta información, como el color y el ancho de línea. Finalmente, use el método CGContextDrawPath para completar el dibujo de fondo.
1.1.2 Dibujar el área operable del usuario
Esta parte requiere un poco de habilidad. Aquí dibujamos una imagen de máscara de degradado lineal, echemos un vistazo al principio:
El principio de funcionamiento de la imagen de máscara aquí es que puede ver un agujero en el marco rectangular de degradado original.
El arco dibujado aquí tiene una sombra, que es un efecto de desenfoque que se utiliza al crear el mapa de máscara.
El siguiente es el código relevante para crear una imagen de máscara:
UIGraphicsBeginImageContext(CGSizeMake(320, 320));
CGContextRef imageCtx = UIGraphicsGetCurrentContext();
p>
CGContextAddArc(imageCtx, self.frame.size.width/2, self.frame.size.height/2, radio, 0, ToRad(self.angle), 0);
[[UIColor redColor]set];
//Usa sombra para crear el efecto Desenfoque
CGContextSetShadowWithColor(imageCtx, CGSizeMake(0, 0), self .angle/20, [UIColor blackColor].CGColor);
//definir la ruta
CGContextSetLineWidth(imageCtx, TB_LINE_WIDTH);
CGContextDrawPath(imageCtx, kCGPathStroke);
//guardar el contenido del contexto en la máscara de imagen
CGImageRef mask = CGBitmapContextCreateImage(UIGraphicsGetCurrentContext());
UIGraphicsEndImageContext(); p>
Arriba El código primero crea un contexto gráfico y luego establece la sombra. A través del método CGContextSetShadowWithColor, podemos establecer lo siguiente:
Contexto
Desplazamiento (no es necesario aquí)
Valor difuso (este valor está controlado por parámetros: Usar el ángulo actual dividido por 20 para obtener un valor de desenfoque animado simple cuando el usuario interactúa con este control)
Color
Luego dibuja en función del ángulo actual un arco correspondiente.
Por ejemplo, si la variable de ángulo actual es 360°, entonces dibuja un arco; si es de 90°, dibuja un arco con un radian de 90°. Finalmente, use el método CGBitmapContextCreateImage para obtener una imagen (el arco que acaba de dibujar). Esta imagen es la imagen de máscara que necesitamos.
Contexto de recorte:
Ahora tenemos una máscara de degradado. Luego use la función CGContextClipToMask para recortar el contexto; pase la función la imagen de máscara que acaba de crear arriba.
El código es el siguiente:
CGContextClipToMask(ctx, self.bounds, mask);
Finalmente dibujamos el efecto degradado, el código es el siguiente:
/ /Definir los pasos de color
Componentes CGFloat[8] = {
0.0, 0.0, 1.0, 1.0, // Color inicial - Azul
1.0 , 0.0 , 1.0, 1.0 }; // Color final - Violeta
CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradiente = CGGradientCreateWithColorComponents(baseSpace, componentes, NULL, 2);
//Definir la dirección del gradiente
CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
CGPoint endPoint = CGPointMake(CGRectGetMidX(rect) ), CGRectGetMaxY(rect));
//Elige un espacio de color
CGColorSpaceRelease(baseSpace), baseSpace = NULL;
//Crea y dibuja el gradient
CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint, 0);
CGGradientRelease(gradient), gradient = NULL;
Dibujar efectos de gradiente requiere mucho de procesamiento, pero podemos dividirlo en 4 partes:
Definir el rango de cambios de color
Definir la dirección del degradado
Seleccionar el espacio de color
Crear y dibujar el degradado
El efecto de visualización final (ver parte del rectángulo degradado) es gracias al mapa de máscara creado anteriormente.
Además, para simular el reflejo de la luz en el borde del fondo, agregué algunos efectos de iluminación.
1.1.3 Dibujar el asa
Ahora dibujamos el asa en la posición correcta según el valor del ángulo actual.
En realidad, en el proceso de dibujo, este paso es muy sencillo, lo más complicado es calcular la posición del mango.
Aquí necesitamos usar funciones trigonométricas para convertir un número escalar en un CGPoint. No te preocupes por lo complicado que es, simplemente usa las funciones Sin y Cos.
El código es el siguiente:
-(CGPoint)pointFromAngle: (int)angleInt{
//Definir el centro del círculo
CGPoint centerPoint = CGPointMake(self frame.size.width/2 - TB_LINE_WIDTH/2, self.frame.size.height/2 - TB_LINE_WIDTH/2);
//Definir la posición del punto en la circunferencia
Resultado de CGPoint;
resultado.y = round(centerPoint.y radio * sin(ToRad(-angleInt)));
resultado.x = round(centerPoint.x radio * cos (ToRad(-angleInt)));
return result;
}
En el código anterior, especifique un valor de ángulo y luego calcule el ángulo valor en la circunferencia La posición, por supuesto, requiere el punto central y el radio del círculo.
Uso de la función sin Cuando se utiliza la función sin, se requiere un valor de coordenada Y, mientras que la función cos requiere un valor de coordenada X.
Cabe señalar que el valor devuelto por cada función aquí considera que el radio es 1, por lo que el resultado debe multiplicarse por el radio que especificamos y calcular en relación con el centro del círculo.
Espero que la siguiente fórmula sea útil para tu comprensión:
1
2
punto.y = centro.y ( radio * sin(ángulo));
punto.x = centro.x (radio * cos(ángulo));
A través del cálculo anterior, ahora conocemos los detalles del posición del controlador, así que a continuación, simplemente dibuje el controlador directamente a la posición especificada, como se muestra en el siguiente código:
-(void) drawTheHandle: (CGContextRef)ctx{
CGContextSaveGState( ctx);
//Me encantan las sombras
CGContextSetShadowWithColor(ctx, CGSizeMake(0, 0), 3, [UIColor blackColor].CGColor);
//¡Obtén la posición del controlador!
CGPoint handleCenter = [self pointFromAngle: self.angle];
//¡Dibuja!
[[UIColor colorWithWhite: 1.0 alfa: 0.7]set];
CGContextFillEllipseInRect(ctx, CGRectMake(handleCenter.x, handleCenter.y, TB_LINE_WIDTH, TB_LINE_WIDTH));
CGContextRestoreGState(ctx);
}
Los pasos específicos son los siguientes:
Guardar el contexto actual (cuando se realiza una tarea de dibujo en una función separada, guardar el estado del contexto es programático). buena costumbre).
Establezca algunos efectos de sombra en el mango
Defina el color del mango y luego use CGContextFillEllipseInRect para dibujarlo.
Llamamos al método anterior al final de la función drawRect:
1
[self drawTheHandle: ctx]; En este punto, esto completa la parte de dibujo de la tarea.
1.2 Seguimiento de operaciones de usuario
En la subclase de UIControl, podemos anular 3 métodos especiales para proporcionar un comportamiento de seguimiento personalizado
1.2 .1 Iniciar seguimiento
p>
Cuando ocurre un evento táctil dentro del límite del control, primero se llamará al método beginTrackingWithTouch del control.
Veamos cómo anular este método:
-(BOOL)beginTrackingWithTouch: (UITouch *)touch withEvent: (UIEvent *)event{
[super beginTrackingWithTouch: touch withEvent: event];
//Necesitamos realizar un seguimiento continuo
return YES;
}
The The BOOl El valor devuelto por la función determina si se requiere una respuesta cuando se arrastra el evento táctil.
En nuestro control personalizado aquí, necesitamos realizar un seguimiento del arrastre del usuario, por lo que devolvemos SÍ.
La función anterior tiene dos parámetros: tocar objeto y evento.
1.2.2 Seguimiento continuo
En el método anterior especificamos que el control personalizado aquí necesita rastrear un evento continuo, por lo que cuando el usuario arrastra, se llamará un evento especial Método : continueTrackingWithTouch:
-(BOOL)continueTrackingWithTouch: (UITouch *)touch withEvent: (UIEvent *)event
El valor BOOL devuelto por este método indica si se debe continuar rastreando los eventos táctiles.
Con este método podemos filtrar las operaciones del usuario en función de la ubicación táctil. Por ejemplo, podemos activar el control solo cuando la posición táctil se cruza con la posición del mango. Sin embargo, nuestra lógica de control aquí no es así. Esperamos que el usuario pueda manejar la posición correspondiente del controlador al hacer clic en cualquier lugar.
Este método en este artículo es responsable de actualizar la posición del mango (verá en una sección posterior que pasamos la información de posición al objetivo correspondiente).
El código de anulación para el método anterior es el siguiente:
-(BOOL)continueTrackingWithTouch: (UITouch *)touch withEvent: (UIEvent *)event{
[super continueTrackingWithTouch: touch withEvent: event];
//Obtener ubicación táctil
CGPoint lastPoint = [touch locationInView: self];
//Usar la ubicación para diseñar el Handle
[self movehandle: lastPoint];
//Veremos esta función en la siguiente sección:
[self sendActionsForControlEvents :UIControlEventValueChanged];
return YES;
}
En el código anterior, primero use locationInView para obtener la posición del toque y luego pase la posición to moveHandle Método que convierte el valor pasado en una posición de controlador válida.
¿Qué significa aquí "un puesto válido"?
El controlador de este control solo puede moverse dentro del límite definido por el arco de fondo, pero no queremos obligar al usuario a mover el controlador dentro de un arco muy pequeño, si este es el caso. La experiencia del usuario será muy mala.
La tarea de moveHandle es convertir cualquier valor de posición en un valor móvil para el mango. Además, en esta función, el valor del ángulo del control deslizante especificado también se convierte. El código es el siguiente:
-(void)movehandle: (CGPoint)lastPoint{
//Obtener el centro
CGPoint centerPoint = CGPointMake(self.frame.size.width /2,
self.frame.size.height/2);
//Calcula la dirección desde el punto central hasta una posición arbitraria.
float currentAngle = AngleFromNorth( centerPoint,
lastPoint,
NO);
int angleInt = floor(currentAngle);
//Almacena el nuevo ángulo
self.angle = 360 - angleInt;
//Actualizar el campo de texto
_textField.text = [NSString stringWithFormat:@"d",
self.angle];
//Redibujar
[self setNeedsDisplay];
}
Arriba En el código, las tareas principales en realidad se manejan en el método AngleFromNorth: basándose en dos puntos, se devolverá una relación de ángulo que conecta los dos puntos. La implementación del método AngleFromNorth es la siguiente:
static inline float AngleFromNorth(CGPoint). p1, CGPoint p2, BOOL invertido) {
CGPoint v = CGPointMake(p2.x-p1.x, p2.y-p1.y);
float vmag = sqrt( SQR(v.x) SQR(v.y)), resultado = 0;
v.x /= vmag;
v.y /= vmag;
radianes dobles = atan2( v.y, v.x);
resultado = ToDeg(radianes);
retorno (resultado gt;=0 ? resultado: resultado 360.0);
} p>
Recordatorio: El método angleFromNorth no es mi creación original. Lo tomé directamente del control de reloj de muestra de OSX proporcionado por Apple.
En el código anterior, después de obtener el valor del ángulo, guárdelo en ángulo y luego actualice el valor del campo de texto.
La siguiente llamada a setNeedDisplay es garantizar que se llame a drawRect para realizar las actualizaciones correspondientes en la interfaz lo antes posible.
1.2.3 Finalizar seguimiento
Cuando finaliza el seguimiento, se llamará al siguiente método:
-(void)endTrackingWithTouch: (UITouch *)touch withEvent ( UIEvent *)event{
[super endTrackingWithTouch:touch withEvent:event];
}
En este artículo, no es necesario anular este método. Este método es útil si desea realizar algún procesamiento cuando el usuario completa las operaciones de la interfaz del control.
1.3 Modo de acción de destino
En este punto, el control deslizante circular puede funcionar. Puede arrastrar el controlador y ver el cambio en el valor en el campo de texto.
Enviar acción - evento de control
Si desea que su control personalizado se comporte de manera consistente con UIControl, cuando el valor del control cambie, deberá realizar el procesamiento de notificaciones: use sendActionsForControlEvents método y especifique un tipo de evento específico. El evento correspondiente al cambio de valor generalmente es UIControlEventValueChanged.
Apple ha predefinido muchos tipos de eventos (en Xcode, haga clic con el mouse en UIControlEventValueChanged). Si su control hereda de UITextField, entonces puede que le interese UIControlEventEdigitingDidBegin. Si desea realizar una acción de retoque, puede usar UIControlTouchUpInside.
Si prestas atención, en el método continueTrackingWithTouch de la parte anterior de este artículo, llamamos al método sendActionsForControlEvents:
[self sendActionsForControlEvents:UIControlEventValueChanged];
Procesado así Más tarde, cuando el valor de control cambie, cada objeto (observador - registrado para el evento) recibirá una notificación de respuesta.