2008-10-15 19 views
57

Estoy tratando de dibujar imágenes en el iPhone con esquinas redondeadas, al igual que las imágenes de contacto en la aplicación Contactos. Tengo un código que generalmente funciona, pero ocasionalmente se cuelga dentro de las rutinas de dibujo de UIImage con un EXEC_BAD_ACCESS - KERN_INVALID_ADDRESS. Pensé que esto podría estar relacionado con el cropping question que pedí hace unas semanas, pero creo que estoy configurando el trazado de recorte correctamente.Esquinas redondeadas en UIImage

Aquí está el código que estoy usando: cuando no falla, el resultado se ve bien y cualquiera que busque obtener una apariencia similar puede tomar prestado el código.

- (UIImage *)borderedImageWithRect: (CGRect)dstRect radius:(CGFloat)radius { 
    UIImage *maskedImage = nil; 

    radius = MIN(radius, .5 * MIN(CGRectGetWidth(dstRect), CGRectGetHeight(dstRect))); 
    CGRect interiorRect = CGRectInset(dstRect, radius, radius); 

    UIGraphicsBeginImageContext(dstRect.size); 
    CGContextRef maskedContextRef = UIGraphicsGetCurrentContext(); 
    CGContextSaveGState(maskedContextRef); 

    CGMutablePathRef borderPath = CGPathCreateMutable(); 
    CGPathAddArc(borderPath, NULL, CGRectGetMinX(interiorRect), CGRectGetMinY(interiorRect), radius, PNDegreeToRadian(180), PNDegreeToRadian(270), NO); 
    CGPathAddArc(borderPath, NULL, CGRectGetMaxX(interiorRect), CGRectGetMinY(interiorRect), radius, PNDegreeToRadian(270.0), PNDegreeToRadian(360.0), NO); 
    CGPathAddArc(borderPath, NULL, CGRectGetMaxX(interiorRect), CGRectGetMaxY(interiorRect), radius, PNDegreeToRadian(0.0), PNDegreeToRadian(90.0), NO); 
    CGPathAddArc(borderPath, NULL, CGRectGetMinX(interiorRect), CGRectGetMaxY(interiorRect), radius, PNDegreeToRadian(90.0), PNDegreeToRadian(180.0), NO); 

    CGContextBeginPath(maskedContextRef); 
    CGContextAddPath(maskedContextRef, borderPath); 
    CGContextClosePath(maskedContextRef); 
    CGContextClip(maskedContextRef); 

    [self drawInRect: dstRect]; 

    maskedImage = UIGraphicsGetImageFromCurrentImageContext(); 
    CGContextRestoreGState(maskedContextRef); 
    UIGraphicsEndImageContext(); 

    return maskedImage; 
} 

y aquí está el registro de fallos. Se ve igual cada vez que tengo uno de estos accidentes

 
Exception Type: EXC_BAD_ACCESS (SIGSEGV) 
Exception Codes: KERN_INVALID_ADDRESS at 0x6e2e6181 
Crashed Thread: 0 

Thread 0 Crashed: 
0 com.apple.CoreGraphics   0x30fe56d8 CGGStateGetRenderingIntent + 4 
1 libRIP.A.dylib     0x33c4a7d8 ripc_RenderImage + 104 
2 libRIP.A.dylib     0x33c51868 ripc_DrawImage + 3860 
3 com.apple.CoreGraphics   0x30fecad4 CGContextDelegateDrawImage + 80 
4 com.apple.CoreGraphics   0x30feca40 CGContextDrawImage + 368 
5 UIKit       0x30a6a708 -[UIImage drawInRect:blendMode:alpha:] + 1460 
6 UIKit       0x30a66904 -[UIImage drawInRect:] + 72 
7 MyApp       0x0003f8a8 -[UIImage(PNAdditions) borderedImageWithRect:radius:] (UIImage+PNAdditions.m:187) 
+0

¿Por qué haces CGContextSaveGState() y CGContextRestoreGState()? Al leer la documentación, me da la impresión de que el estado es interno al contexto, y que el contexto completo se descarta de todos modos. –

+0

¿Es necesario llamar a CGContextBeginPath() y CGContextClosePath() como lo hace? Descubrí que puedo crear una ruta y llamar a CGContextAddPath() y que las cosas funcionen bien. Además, los documentos para CGContextClosePath() dicen que "cuando llenas o recortas una ruta abierta, Quartz cierra implícitamente el subespacio por ti", lo que sugiere que no hay necesidad de cerrarlo tú mismo. – camdez

Respuesta

163

Aquí es un método aún más sencillo que está disponible en el iPhone 3.0 o superior. Cada objeto basado en la vista tiene una capa asociada. Cada capa puede tener un conjunto de radio de esquina, esto le dará justo lo que desea:

UIImageView * roundedView = [[UIImageView alloc] initWithImage: [UIImage imageNamed:@"wood.jpg"]]; 
// Get the Layer of any view 
CALayer * l = [roundedView layer]; 
[l setMasksToBounds:YES]; 
[l setCornerRadius:10.0]; 

// You can even add a border 
[l setBorderWidth:4.0]; 
[l setBorderColor:[[UIColor blueColor] CGColor]]; 
+0

Fantástico, ¡gracias por esto! –

+0

Obviamente, esta no fue la respuesta cuando hice la pregunta (menos de 2.0), pero parece ser una gran respuesta en el orden mundial actual (es decir, 3.x). Tiene el beneficio adicional de trabajar con cualquier UIView, que es lo que me trajo aquí. – Jablair

+19

Como se menciona a continuación, no se olvide de incluir #import

4

no puedo ofrecer ninguna información sobre su accidente, pero yo pensaba que iba a ofrecer otra opción para redondear las esquinas. Tuve un problema similar en una aplicación en la que estaba trabajando. En lugar de escribir cualquier código, estoy sobreponiendo otra imagen que enmascara las esquinas.

+0

Intenté enmascarar con una imagen y nunca pude hacer que funcione de manera confiable/en absoluto. ¿Tiene algún consejo sobre cómo lo hizo funcionar? Fue hace un tiempo, así que no recuerdo exactamente lo que probé, pero creo que pasé por CGImageCreateWithMask, CGImageMaskCreate y CGContextClipToMask. – Jablair

+0

No estoy enmascarando el código. Simplemente estoy dibujando otro UIImage encima de la imagen que quiero enmascarar. El UIImage tiene una sección transparente en el medio y bordes negros (mi color de fondo) que quiero "enmascarar". – Lounges

1

Si solo falla algunas veces, averigüe qué tienen en común los casos de colisión. ¿Es dstRect el mismo todo el tiempo? ¿Alguna vez las imágenes tienen un tamaño diferente?

Además, necesita CGPathRelease(borderPath), aunque dudo que la fuga esté causando su problema.

+0

Gracias por el recordatorio de lanzamiento. Lo había comentado accidentalmente con algún otro código de depuración, que luego recorté antes de publicarlo. dstRect es el mismo, pero las imágenes pueden ser diferentes porque se descargan de la web. Estoy obteniendo estos de un probador. No he reprobado esto yo mismo. – Jablair

3

La manera más fácil es insertar un botón deshabilitado [!] Redondo-rect [no personalizado!] En su vista (incluso puede hacerlo todo en Interface Builder) y asociar su imagen con él. El mensaje de configuración de imagen es diferente para UIButton (en comparación con UIImageView), pero el kludge general funciona como un amuleto. Use setImage: forState: si desea un icono centrado o setBackgroundImage: forState: si desea que se corte toda la imagen con las esquinas (como Contactos). Por supuesto, si desea mostrar muchas de estas imágenes en su drawRect, este no es el enfoque correcto, pero es más probable que una vista incrustada sea exactamente lo que necesita de todos modos ...

4

Si llama a su método (borderedImageWithRect) en una cadena de fondo, pueden producirse bloqueos ya que las funciones de UIGraphics no son seguras para subprocesos. En tal caso, debe crear un contexto usando CGBitmapContextCreate() - vea el código de muestra "Reflection" del SDK.

+0

fjoachim, ¿realmente has hecho esto? Tengo un problema al dibujar en un contexto de mapa de bits en una cadena de fondo. Ver http://stackoverflow.com/questions/702914/using-core-graphics-cocoa-can-you-draw-to-a-bitmap-context-from-a-background-th – philsquared

2

Reitero fjoachim's answer: tenga cuidado al intentar dibujar mientras se ejecuta en un hilo separado, o puede obtener EXC_BAD_ACCESS errores.

Mi solución fue algo como esto: (. En mi caso fue el cambio de tamaño/escala UIImages)

UIImage *originalImage = [UIImage imageNamed:@"OriginalImage.png"] 
[self performSelectorOnMainThread:@selector(displayImageWithRoundedCorners:) withObject:originalImage waitUntilDone:YES]; 

2

que en realidad tenía la oportunidad de hablar de esto con alguien de Apple en el iPhone Tech Talk en Nueva York. Cuando hablamos de eso, él estaba bastante seguro de que no era un subproceso emitido. En cambio, pensó que necesitaba retener el contexto gráfico que se generó al llamar al UIGraphicsBeginImageContext. Esto parece violar las reglas generales que tratan de retener reglas y esquemas de nombres, pero este tipo estaba bastante seguro de haber visto el problema anteriormente.

Si la memoria estaba siendo garabateada, quizás por otro hilo, eso ciertamente explicaría por qué solo estaba viendo el problema ocasionalmente.

No he tenido tiempo de revisar el código y probar la solución, pero el comentario de PCheese me hizo darme cuenta de que no había publicado la información aquí.

... a menos que escribió que bajar mal y UIGraphicsBeginImageContext debería haber sido CGBitmapContextCreate ...

21

Si appIconImage es un UIImageView, entonces:

appIconImage.image = [UIImage imageWithContentsOfFile:@"image.png"]; 
appIconImage.layer.masksToBounds = YES; 
appIconImage.layer.cornerRadius = 10.0; 
appIconImage.layer.borderWidth = 1.0; 
appIconImage.layer.borderColor = [[UIColor grayColor] CGColor]; 

Y también recuerda:

#import <QuartzCore/QuartzCore.h> 
24

Voy a seguir adelante y responder la pregunta en el título.

Pruebe esta categoría.

UIImage + additions.h

#import <UIKit/UIKit.h> 

@interface UIImage (additions) 
-(UIImage*)makeRoundCornersWithRadius:(const CGFloat)RADIUS; 
@end 

UIImage + additions.m

#import "UIImage+additions.h" 

@implementation UIImage (additions) 
-(UIImage*)makeRoundCornersWithRadius:(const CGFloat)RADIUS { 
    UIImage *image = self; 

    // Begin a new image that will be the new image with the rounded corners 
    // (here with the size of an UIImageView) 
    UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale); 

    const CGRect RECT = CGRectMake(0, 0, image.size.width, image.size.height); 
    // Add a clip before drawing anything, in the shape of an rounded rect 
    [[UIBezierPath bezierPathWithRoundedRect:RECT cornerRadius:RADIUS] addClip]; 
    // Draw your image 
    [image drawInRect:RECT]; 

    // Get the image, here setting the UIImageView image 
    //imageView.image 
    UIImage* imageNew = UIGraphicsGetImageFromCurrentImageContext(); 

    // Lets forget about that we were drawing 
    UIGraphicsEndImageContext(); 

    return imageNew; 
} 
@end 
+0

Me confunde que esta pregunta tenga tantos éxitos hasta el día de hoy. También me confunde pensar que no teníamos cosas como UIBezierPath cuando hice la pregunta (iOS 2.0). Hombre, hemos recorrido un largo camino. – Jablair

+0

@Jablair - Bueno, a partir de que Apple decida que las imágenes de contacto deben estar en una vista circular en iOS 7, ahora necesitamos muchos UIImages circulares. – ArtOfWarfare

+0

Esto se debe marcar como la respuesta correcta, todas las demás respuestas son incorrectas si UIImageView no utiliza el modo de contenido UIViewContentModeScaleToFill. Esto funciona correctamente con cualquier modo de contenido. – SPQR3

-1

establecer la imagen en (anchura de la imagen y 41x41 altura) xib o guión.

FirstViewController.h

@interface.... 

IBOutlet UIImageView *testImg; 

@end 

FirstViewController.m

-(void)viewDidLoad{ 
     testImg.layer.backgroundColor=[[UIColor clearColor] CGColor]; 
     testImg.layer.cornerRadius=20; 
     testImg.layer.masksToBounds = YES; 
    } 
0
UIImage *originalImage = [UIImage imageNamed:@"OriginalImage.png"] 
[self performSelectorOnMainThread:@selector(displayImageWithRoundedCorners:) withObject:originalImage waitUntilDone:YES];