ios objective-c uiview swift drawrect

ios - UIView con sombra, esquinas redondeadas y drawRect personalizado



drawrect android (16)

Tengo que crear una UIView personalizada que tendrá esquinas redondeadas, un borde, una sombra y su método drawRect() se reemplaza para proporcionar código de dibujo personalizado con el que se dibujan varias líneas rectas en la vista (necesito usar un lápiz rápido, liviano acercarse aquí ya que muchas de estas vistas pueden ser representadas).

El problema que estoy enfrentando actualmente es que la sombra ya no se aplica a las esquinas redondas tan pronto como anulo drawRect() en la clase de vista (incluso sin ningún código personalizado en ella). Ver la imagen adjunta para la diferencia:

En el controlador de vista estoy usando el siguiente código:

view.layer.cornerRadius = 10; view.layer.masksToBounds = true; view.layer.borderColor = UIColor.grayColor().CGColor; view.layer.borderWidth = 0.5; view.layer.contentsScale = UIScreen.mainScreen().scale; view.layer.shadowColor = UIColor.blackColor().CGColor; view.layer.shadowOffset = CGSizeZero; view.layer.shadowRadius = 5.0; view.layer.shadowOpacity = 0.5; view.layer.masksToBounds = false; view.clipsToBounds = false;

En el drawContext() reemplazado usaría algo como:

var context:CGContext = UIGraphicsGetCurrentContext(); CGContextSetStrokeColorWithColor(context, UIColor.redColor().CGColor); // Draw them with a 2.0 stroke width so they are a bit more visible. CGContextSetLineWidth(context, 2.0); CGContextMoveToPoint(context, 0.0, 0.0); //start at this point CGContextAddLineToPoint(context, 20.0, 20.0); //draw to this point CGContextStrokePath(context);

Pero como se dijo anteriormente, el problema oculto ocurre incluso sin agregar este código.

¿Hay alguna otra forma mejor de dibujar elementos ligeros en una vista que no sea este enfoque que sea compatible con esquinas redondeadas y sombras? No quiero agregar vistas extra innecesarias o contextos de imagen a la vista ya que estos deben ser livianos y funcionales.


Además de la solución de Frederic Adda, no te olvides de colocar la vista que tiene sombra con un relleno en la supervista, donde se puede dibujar la sombra. De lo contrario, la sombra se recortará. Cometí este error en mi celda personalizada, y pensé que la solución era incorrecta hasta que agregué un relleno de 8 píxeles por todas partes.


Aquí está la versión respuesta de , tuve que usarla y encontrarla aquí e hice correcciones generales para XCode 8. ¡Funciona como el encanto!

@IBDesignable class RoundRectView: UIView { @IBInspectable var cornerRadius: CGFloat = 0.0 @IBInspectable var borderColor: UIColor = UIColor.black @IBInspectable var borderWidth: CGFloat = 0.5 private var customBackgroundColor = UIColor.white override var backgroundColor: UIColor?{ didSet { customBackgroundColor = backgroundColor! super.backgroundColor = UIColor.clear } } func setup() { layer.shadowColor = UIColor.black.cgColor; layer.shadowOffset = CGSize.zero layer.shadowRadius = 5.0; layer.shadowOpacity = 0.5; super.backgroundColor = UIColor.clear } override init(frame: CGRect) { super.init(frame: frame) self.setup() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.setup() } override func draw(_ rect: CGRect) { customBackgroundColor.setFill() UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius ?? 0).fill() let borderRect = bounds.insetBy(dx: borderWidth/2, dy: borderWidth/2) let borderPath = UIBezierPath(roundedRect: borderRect, cornerRadius: cornerRadius - borderWidth/2) borderColor.setStroke() borderPath.lineWidth = borderWidth borderPath.stroke() // whatever else you need drawn } }


En Swift 4.1. Para hacer esquina redondeada de UIView, he creado la extensión de UIView de la siguiente manera.

override func viewDidLoad() { super.viewDidLoad() viewTest.roundedAllCorner() // For rounding all corners //viewTest.roundedLeft() // For rounding only left corners } extension UIView{ func roundedLeft(){ let bezierPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: [.topLeft, .bottomLeft], cornerRadii: CGSize(width: 15, height: 15)) let maskLayer = CAShapeLayer() maskLayer.frame = bounds maskLayer.path = bezierPath.cgPath layer.mask = maskLayer } func roundedAllCorner(){ let bezierPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: [.topRight , .bottomRight , .topLeft , .bottomLeft], cornerRadii: CGSize(width: 35, height: 35)) let maskLayer = CAShapeLayer() maskLayer.frame = bounds maskLayer.path = bezierPath.cgPath layer.mask = maskLayer } }


En Swift. Lo que sí funcionó para mí fue agregar:

self.noteImage.layer.masksToBounds = false

Entonces, el código completo es:

self.noteImage.layer.masksToBounds = false self.noteImage.layer.shadowColor = UIColor.redColor().CGColor self.noteImage.layer.shadowOpacity = 0.5 self.noteImage.layer.shadowOffset = CGSize(width: 2, height: 2) self.noteImage.layer.shadowRadius = 1 self.noteImage.layer.shadowPath = UIBezierPath(rect: noteImage.bounds).CGPath self.noteImage.layer.shouldRasterize = true


Encuentro útil el siguiente enlace para comprender la configuración de dropshadow:

Cómo agregar una sombra a una UIView

Para configurar la esquina redonda para UIVIEW, simplemente configure el valor de layer.cornerRadius en el constructor de interfaz. Marque la captura de pantalla.


Escribí una pequeña extensión a UIView para administrar ambas esquinas redondeadas Y sombra paralela. Como las variables son @IBInspectable, ¡todo se puede configurar directamente en el guión gráfico!

// // UIView extensions.swift // // Created by Frédéric ADDA on 25/07/2016. // Copyright © 2016 Frédéric ADDA. All rights reserved. // import UIKit extension UIView { @IBInspectable var shadow: Bool { get { return layer.shadowOpacity > 0.0 } set { if newValue == true { self.addShadow() } } } @IBInspectable var cornerRadius: CGFloat { get { return self.layer.cornerRadius } set { self.layer.cornerRadius = newValue // Don''t touch the masksToBound property if a shadow is needed in addition to the cornerRadius if shadow == false { self.layer.masksToBounds = true } } } func addShadow(shadowColor: CGColor = UIColor.black.cgColor, shadowOffset: CGSize = CGSize(width: 1.0, height: 2.0), shadowOpacity: Float = 0.4, shadowRadius: CGFloat = 3.0) { layer.shadowColor = shadowColor layer.shadowOffset = shadowOffset layer.shadowOpacity = shadowOpacity layer.shadowRadius = shadowRadius } }

Y así es como se ve en el guión gráfico:

El resultado :

Hay un requisito: NO toque clipToBounds en la vista (en código o en IB) o masksToBound en la capa.

NB: un caso en el que no funcionará: tableViews. Como UITableView activa automáticamente clipToBounds bajo el capó, no podemos tener una sombra clipToBounds .

EDITAR: como Claudia Fitero acertadamente notó, debes dejar un pequeño espacio alrededor de la vista a la que estás agregando una sombra, de lo contrario, la sombra no será visible. Un relleno de 2px es suficiente en general (dependiendo de su radio de sombra).


Esta es mi solución. Si tiene múltiples tipos de vistas, como UIView, UIControl, UITableView, etc., y no desea crear subclases de cada una de ellas, o si desea agregar este efecto con los cambios más pequeños a su código, entonces esto podría ser lo que estan buscando.

Objetivo-Ch

#import <UIKit/UIKit.h> @interface UIView (CornerAndShadow) - (void)setCornerAndShadow; @end

Objetivo-Cm

#import "UIView+CornerAndShadow.h" #import <Masonry.h> @implementation UIView (CornerAndShadow) - (void)setCornerAndShadow { // constants CGFloat fCornerRadius = 9.f; // only work for views with superview if (self.superview == nil) { return; } // set corner self.layer.cornerRadius = fCornerRadius; self.layer.masksToBounds = YES; // create and configure shadowView UIView *shadowView = [UIView new]; shadowView.backgroundColor = self.backgroundColor; // just to make shadow visible shadowView.layer.cornerRadius = fCornerRadius; shadowView.layer.shadowColor = [UIColor redColor].CGColor; shadowView.layer.shadowOffset = CGSizeMake(0, 3.f); shadowView.layer.shadowOpacity = 0.5f; shadowView.layer.shadowRadius = 5.f; // put shadowView into superview right below self [self.superview insertSubview:shadowView belowSubview:self]; // set shadowView''s frame equal to self [shadowView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(self); }]; // use this if you''re not using autolayout, and can get real frame here // shadowView.frame = self.frame; } @end


Esta es una pregunta anterior, pero hubiera hecho todo lo posible en su método de sorteo personalizado como se muestra a continuación.

Normalmente haré esto si sé que deseo aplicar una sombra masksToBounds a mi vista redondeada (lo que por supuesto significa que no quiero usar masksToBounds )

Tampoco tiene que agregar una "vista en sombra" adicional a la jerarquía.

@IBDesignable class RoundedView: UIView { @IBInspectable var cornerRadius: CGFloat = 0 override func draw(_ rect: CGRect) { guard let context = UIGraphicsGetCurrentContext() else { return } // You could use custom IBInspectable attributes // for the stroke and fill color. context.setFillColor(UIColor.white.cgColor) context.setStrokeColor(UIColor.orange.cgColor) // Add a clipping path to get the rounded look // you want. UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).addClip() // Fill and stroke your background. let background = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius) background.lineWidth = 2 background.fill() background.stroke() } private func shadow() { layer.shadowColor = UIColor.black.cgColor layer.shadowRadius = 5 layer.shadowOpacity = 0.5 layer.shadowOffset = CGSize.zero } override func awakeFromNib() { super.awakeFromNib() shadow() } }


Esto es complicado. UIView para UIView de clipsToBounds son necesarios para obtener las esquinas redondeadas. Pero las CALayer de masksToBounds tienen que ser false para que la sombra sea visible. De alguna manera, todo funciona si drawRect no se reemplaza, pero en realidad no debería.

La solución es crear una supervista para proporcionar la sombra (en la demostración a continuación esta es la vista de shadowView ). Puedes probar lo siguiente en el patio de recreo:

class MyView : UIView { override func drawRect(rect: CGRect) { let c = UIGraphicsGetCurrentContext() CGContextAddRect(c, CGRectMake(10, 10, 80, 80)) CGContextSetStrokeColorWithColor(c , UIColor.redColor().CGColor) CGContextStrokePath(c) } } let superview = UIView(frame: CGRectMake(0, 0, 200, 200)) let shadowView = UIView(frame: CGRectMake(50, 50, 100, 100)) shadowView.layer.shadowColor = UIColor.blackColor().CGColor shadowView.layer.shadowOffset = CGSizeZero shadowView.layer.shadowOpacity = 0.5 shadowView.layer.shadowRadius = 5 let view = MyView(frame: shadowView.bounds) view.backgroundColor = UIColor.whiteColor() view.layer.cornerRadius = 10.0 view.layer.borderColor = UIColor.grayColor().CGColor view.layer.borderWidth = 0.5 view.clipsToBounds = true shadowView.addSubview(view) superview.addSubview(shadowView)

Resultado:


La solución parece mucho más fácil de lo que el problema podría sugerir. Tuve esto con uno de mis puntos de vista y usé la parte central de la respuesta de @Hodit para que funcione. Esto es todo lo que necesitas en realidad:

- (void) drawRect:(CGRect)rect { // make sure the background is set to a transparent color using IB or code // e.g.: self.backgroundColor = [UIColor clearColor]; // draw a rounded rect in the view [[UIColor whiteColor] setFill]; [[UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:5.0] fill]; // apply shadow if you haven''t already self.layer.masksToBounds = NO; self.layer.shadowColor = [[UIColor blackColor] CGColor]; self.layer.shadowOffset = CGSizeMake(0.0,3.0); self.layer.shadowRadius= 1.0; self.layer.shadowOpacity = 0.1; // more code here }

Tenga en cuenta que esto no incluye subvistas. Cualquier cosa posicionada en 0,0 en la vista se superpondrá a la esquina superior izquierda visible superior.


La sombra se deja caer desde lo que esté dentro de la capa de la vista. Cuando deshabilita el recorte, el rectángulo de la capa completa se llena con backgroundColor por defecto para que la sombra también se vuelva rectangular. En lugar de cortarlo con una máscara redondeada, solo debe redondear el contenido de la capa, dibujarlos usted mismo. Y el borde de la layer se dibuja alrededor de sus límites, por lo que también debe dibujarlo.

Por ejemplo, en backgroundColor setter establece el color de fondo real para clearColor y utiliza el color pasado en drawRect para dibujar un rectángulo redondeado con.

En el siguiente ejemplo, declaro las propiedades como IBInspectable y toda la clase como IBDesignable , de modo que todo se puede establecer en el guión gráfico. De esta forma, incluso puede usar el selector de fondo predeterminado para cambiar su color recto redondeado.

Rápido

@IBDesignable class RoundRectView: UIView { @IBInspectable var cornerRadius: CGFloat = 0.0 @IBInspectable var borderColor: UIColor = UIColor.blackColor() @IBInspectable var borderWidth: CGFloat = 0.5 private var customBackgroundColor = UIColor.whiteColor() override var backgroundColor: UIColor?{ didSet { customBackgroundColor = backgroundColor! super.backgroundColor = UIColor.clearColor() } } func setup() { layer.shadowColor = UIColor.blackColor().CGColor; layer.shadowOffset = CGSizeZero; layer.shadowRadius = 5.0; layer.shadowOpacity = 0.5; super.backgroundColor = UIColor.clearColor() } override init(frame: CGRect) { super.init(frame: frame) self.setup() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.setup() } override func drawRect(rect: CGRect) { customBackgroundColor.setFill() UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius ?? 0).fill() let borderRect = CGRectInset(bounds, borderWidth/2, borderWidth/2) let borderPath = UIBezierPath(roundedRect: borderRect, cornerRadius: cornerRadius - borderWidth/2) borderColor.setStroke() borderPath.lineWidth = borderWidth borderPath.stroke() // whatever else you need drawn } }

Swift 3

@IBDesignable class RoundedView: UIView { @IBInspectable var cornerRadius: CGFloat = 0.0 @IBInspectable var borderColor: UIColor = UIColor.black @IBInspectable var borderWidth: CGFloat = 0.5 private var customBackgroundColor = UIColor.white override var backgroundColor: UIColor?{ didSet { customBackgroundColor = backgroundColor! super.backgroundColor = UIColor.clear } } func setup() { layer.shadowColor = UIColor.black.cgColor layer.shadowOffset = CGSize.zero layer.shadowRadius = 5.0 layer.shadowOpacity = 0.5 super.backgroundColor = UIColor.clear } override init(frame: CGRect) { super.init(frame: frame) self.setup() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.setup() } override func draw(_ rect: CGRect) { customBackgroundColor.setFill() UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius ?? 0).fill() let borderRect = bounds.insetBy(dx: borderWidth/2, dy: borderWidth/2) let borderPath = UIBezierPath(roundedRect: borderRect, cornerRadius: cornerRadius - borderWidth/2) borderColor.setStroke() borderPath.lineWidth = borderWidth borderPath.stroke() // whatever else you need drawn } }

Objective-C .h

IB_DESIGNABLE @interface RoundRectView : UIView @property IBInspectable CGFloat cornerRadius; @property IBInspectable UIColor *borderColor; @property IBInspectable CGFloat borderWidth; @end

Objective-C .m

@interface RoundRectView() @property UIColor *customBackgroundColor; @end @implementation RoundRectView -(void)setup{ self.layer.shadowColor = [UIColor blackColor].CGColor; self.layer.shadowOffset = CGSizeZero; self.layer.shadowRadius = 5.0; self.layer.shadowOpacity = 0.5; [super setBackgroundColor:[UIColor clearColor]]; } - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self setup]; } return self; } - (instancetype)initWithCoder:(NSCoder *)coder { self = [super initWithCoder:coder]; if (self) { [self setup]; } return self; } -(void)setBackgroundColor:(UIColor *)backgroundColor{ self.customBackgroundColor = backgroundColor; super.backgroundColor = [UIColor clearColor]; } -(void)drawRect:(CGRect)rect{ [self.customBackgroundColor setFill]; [[UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:self.cornerRadius] fill]; CGFloat borderInset = self.borderWidth/2; CGRect borderRect = CGRectInset(self.bounds, borderInset, borderInset); UIBezierPath *borderPath = [UIBezierPath bezierPathWithRoundedRect:borderRect cornerRadius:self.cornerRadius - borderInset]; [self.borderColor setStroke]; borderPath.lineWidth = self.borderWidth; [borderPath stroke]; // whatever else you need drawn } @end

Result


Prueba esto, es trabajo para mí ...

yourView.layer.shadowColor = UIColor.blackColor().CGColor yourView.layer.shadowOpacity = 0.5 yourView.layer.shadowOffset = CGSize(width: 3, height: 3) yourView.layer.shadowRadius = 05 yourView.layer.shadowPath = UIBezierPath(rect: yourView.bounds).CGPath yourView.layer.shouldRasterize = true


Swift 3

Hice una extensión de UIView y básicamente es la misma idea sugerida por Mundi:

extension UIView { func addShadowView() { //Remove previous shadow views superview?.viewWithTag(119900)?.removeFromSuperview() //Create new shadow view with frame let shadowView = UIView(frame: frame) shadowView.tag = 119900 shadowView.layer.shadowColor = UIColor.black.cgColor shadowView.layer.shadowOffset = CGSize(width: 2, height: 3) shadowView.layer.masksToBounds = false shadowView.layer.shadowOpacity = 0.3 shadowView.layer.shadowRadius = 3 shadowView.layer.shadowPath = UIBezierPath(rect: bounds).cgPath shadowView.layer.rasterizationScale = UIScreen.main.scale shadowView.layer.shouldRasterize = true superview?.insertSubview(shadowView, belowSubview: self) }}

Utilizar:

class MyCVCell: UICollectionViewCell { @IBOutlet weak var containerView: UIView! override func awakeFromNib() { super.awakeFromNib() } override func draw(_ rect: CGRect) { super.draw(rect) containerView.addShadowView() }}


Yo uso esta extensión para UIView :

Import UIKit extension UIView { /// A property that accesses the backing layer''s opacity. @IBInspectable open var opacity: Float { get { return layer.opacity } set(value) { layer.opacity = value } } /// A property that accesses the backing layer''s shadow @IBInspectable open var shadowColor: UIColor? { get { guard let v = layer.shadowColor else { return nil } return UIColor(cgColor: v) } set(value) { layer.shadowColor = value?.cgColor } } /// A property that accesses the backing layer''s shadowOffset. @IBInspectable open var shadowOffset: CGSize { get { return layer.shadowOffset } set(value) { layer.shadowOffset = value } } /// A property that accesses the backing layer''s shadowOpacity. @IBInspectable open var shadowOpacity: Float { get { return layer.shadowOpacity } set(value) { layer.shadowOpacity = value } } /// A property that accesses the backing layer''s shadowRadius. @IBInspectable open var shadowRadius: CGFloat { get { return layer.shadowRadius } set(value) { layer.shadowRadius = value } } /// A property that accesses the backing layer''s shadowPath. @IBInspectable open var shadowPath: CGPath? { get { return layer.shadowPath } set(value) { layer.shadowPath = value } } /// A property that accesses the layer.cornerRadius. @IBInspectable open var cornerRadius: CGFloat { get { return layer.cornerRadius } set(value) { layer.cornerRadius = value } } /// A property that accesses the layer.borderWith. @IBInspectable open var borderWidth: CGFloat { get { return layer.borderWidth } set(value) { layer.borderWidth = value } } /// A property that accesses the layer.borderColor property. @IBInspectable open var borderColor: UIColor? { get { guard let v = layer.borderColor else { return nil } return UIColor(cgColor: v) } set(value) { layer.borderColor = value?.cgColor } } }


puedes usar esta función para todas tus vistas.

extension UIView{ func radiusAndBorder(radius:CGFloat, color:UIColor = UIColor.clear) -> UIView{ var rounfView:UIView = self rounfView.layer.cornerRadius = CGFloat(radius) rounfView.layer.borderWidth = 1 rounfView.layer.borderColor = color.cgColor rounfView.clipsToBounds = true return rounfView } }


Solución SWIFT 3

Adaptado de la respuesta de Mundi

class MyView : UIView { override func draw(_ rect: CGRect) { let c = UIGraphicsGetCurrentContext() c!.addRect(CGRect(x: 10, y: 10, width: 80, height: 80)) c!.setStrokeColor(UIColor.red.cgColor) c!.strokePath() } } let superview = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200)) let shadowView = UIView(frame: CGRect(x: 50, y: 50, width: 100, height: 100)) shadowView.layer.shadowColor = UIColor.black.cgColor shadowView.layer.shadowOffset = CGSize.zero shadowView.layer.shadowOpacity = 0.5 shadowView.layer.shadowRadius = 5 let view = MyView(frame: shadowView.bounds) view.backgroundColor = UIColor.white view.layer.cornerRadius = 10.0 view.layer.borderColor = UIColor.gray.cgColor view.layer.borderWidth = 0.5 view.clipsToBounds = true shadowView.addSubview(view) superview.addSubview(shadowView)