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:
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
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)