Como desenhar uma sombra sob um UIView?

352

Estou tentando desenhar uma sombra sob a borda inferior de um UIViewno Cocoa Touch. Entendo que devo usar CGContextSetShadow()para desenhar a sombra, mas o guia de programação do Quartz 2D é um pouco vago:

  1. Salve o estado dos gráficos.
  2. Chame a função CGContextSetShadow, passando os valores apropriados.
  3. Execute todo o desenho ao qual deseja aplicar sombras.
  4. Restaurar o estado dos gráficos

Eu tentei o seguinte em uma UIViewsubclasse:

- (void)drawRect:(CGRect)rect {
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    CGContextSaveGState(currentContext);
    CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);
    CGContextRestoreGState(currentContext);
    [super drawRect: rect];
}

..mas isso não funciona para mim e estou um pouco empolgado com (a) para onde ir em seguida e (b) se houver algo que eu precise fazer UIViewpara que isso funcione?

Fraser Speirs
fonte

Respostas:

98

No seu código atual, você salva o GStatecontexto atual, configura-o para desenhar uma sombra .. e restaura-o para o que era antes de configurá-lo para desenhar uma sombra. Finalmente, você invoca a implementação da superclasse de drawRect:.

Qualquer desenho que deva ser afetado pela configuração de sombra precisa ocorrer após

CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);

mas antes

CGContextRestoreGState(currentContext);

Então, se você deseja que as superclasses drawRect:sejam 'encapsuladas' em uma sombra, que tal se você reorganizar seu código dessa maneira?

- (void)drawRect:(CGRect)rect {
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    CGContextSaveGState(currentContext);
    CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);
    [super drawRect: rect];
    CGContextRestoreGState(currentContext);
}
Christian Brunschen
fonte
789

Uma abordagem muito mais fácil é definir alguns atributos da camada da visualização na inicialização:

self.layer.masksToBounds = NO;
self.layer.shadowOffset = CGSizeMake(-15, 20);
self.layer.shadowRadius = 5;
self.layer.shadowOpacity = 0.5;

Você precisa importar o QuartzCore.

#import <QuartzCore/QuartzCore.h>
ollie
fonte
4
Mas lembre-se de que isso só funciona no iOS 3.2 ou superior; portanto, se seu aplicativo funcionar na versão antiga, você deve usar a solução de Christian ou uma imagem estática por trás da visualização, se essa for uma opção.
florianbuerger
119
Esta solução também requer adição #import <QuartzCore/QuartzCore.h>"ao arquivo .h.
MusiGenesis 22/03
26
Definir masksToBoundspara NOnegará o cornerRadius, não?
pixelfreak
4
Ok, para resolver isso, o backgroundColor precisa ser definido na camada e a exibição precisa ser transparente.
pixelfreak
@pixelfreak Como você faz isso? Eu tentei, self.layer.backgroundColor = [[UIColor whiteColor] CGColor];mas sem sorte. Qual visão precisa ser transparente?
Victor Van Hee
232
self.layer.masksToBounds = NO;
self.layer.cornerRadius = 8; // if you like rounded corners
self.layer.shadowOffset = CGSizeMake(-15, 20);
self.layer.shadowRadius = 5;
self.layer.shadowOpacity = 0.5;

Isso desacelerará o aplicativo. Adicionar a seguinte linha pode melhorar o desempenho, desde que sua visualização seja visivelmente retangular:

self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;
ZYiOS
fonte
11
Provavelmente, vale a pena notar que essa otimização só é útil se sua visualização for visivelmente retangular.
Benjamin Dobell
11
self.layer.shadowPath ... em vez de o que? ou apenas adicionando a ele
Chrizzz
2
Basta adicionar essa linha extra além das outras.
christophercotton
11
@NathanGaskin - desenhar sombras é uma operação cara, por exemplo, se seu aplicativo permitir outra orientação da interface e você começar a girar o dispositivo, sem especificar explicitamente o caminho da sombra que deve ser renderizado várias vezes durante a animação, dependendo da a forma, provavelmente visivelmente retardar a animação
Peter Pajchl
9
@BenjaminDobell Essa linha específica funciona apenas para retângulos, mas você também pode criar caminhos não retangulares. Por exemplo, se você tiver um retângulo arredondado, poderá usar bezierPathWithRoundedRect: cornerRadius:
Dan Dyer
160

A mesma solução, mas apenas para lembrá-lo: você pode definir a sombra diretamente no storyboard.

Ex:

insira a descrição da imagem aqui

Antzi
fonte
2
Infelizmente, acho que o CGColor está fora do alcance do storyboard :(
Antzi
11
apenas definir uma categoria para UIViewou CGLayerque é um UIColorwrapper para a CGColorpropriedade;)
DeFrenZ
11
Este link descreve como fazer isso: cocoadventures.org/post/104137872754/…
Kamchatka
8
Para facilitar a cópia e colar das pessoas: layer.masksToBound, layer.shadowOffset, layer.shadowRadius, layer.shadowOpacity. Erros de digitação vão te matar aqui.
etayluz
3
O valor de layer.shadowOpacity deve ser 0,5 e não 0,5
Desert Rose
43

Você pode tentar isso .... você pode jogar com os valores. O shadowRadiusdita a quantidade de desfoque. shadowOffsetdita para onde vai a sombra.

Swift 2.0

let radius: CGFloat = demoView.frame.width / 2.0 //change it to .height if you need spread for height
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 2.1 * radius, height: demoView.frame.height))
//Change 2.1 to amount of spread you need and for height replace the code for height

demoView.layer.cornerRadius = 2
demoView.layer.shadowColor = UIColor.blackColor().CGColor
demoView.layer.shadowOffset = CGSize(width: 0.5, height: 0.4)  //Here you control x and y
demoView.layer.shadowOpacity = 0.5
demoView.layer.shadowRadius = 5.0 //Here your control your blur
demoView.layer.masksToBounds =  false
demoView.layer.shadowPath = shadowPath.CGPath

Swift 3.0

let radius: CGFloat = demoView.frame.width / 2.0 //change it to .height if you need spread for height 
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 2.1 * radius, height: demoView.frame.height)) 
//Change 2.1 to amount of spread you need and for height replace the code for height

demoView.layer.cornerRadius = 2
demoView.layer.shadowColor = UIColor.black.cgColor
demoView.layer.shadowOffset = CGSize(width: 0.5, height: 0.4)  //Here you control x and y
demoView.layer.shadowOpacity = 0.5
demoView.layer.shadowRadius = 5.0 //Here your control your blur
demoView.layer.masksToBounds =  false
demoView.layer.shadowPath = shadowPath.cgPath

Exemplo com spread

Exemplo com spread

Para criar uma sombra básica

    demoView.layer.cornerRadius = 2
    demoView.layer.shadowColor = UIColor.blackColor().CGColor
    demoView.layer.shadowOffset = CGSizeMake(0.5, 4.0); //Here your control your spread
    demoView.layer.shadowOpacity = 0.5 
    demoView.layer.shadowRadius = 5.0 //Here your control your blur

Exemplo básico de sombra no Swift 2.0

RESULTADO

O-mkar
fonte
19

Solução simples e limpa usando o Interface Builder

Adicione um arquivo chamado UIView.swift ao seu projeto (ou cole-o em qualquer arquivo):

import UIKit

@IBDesignable extension UIView {

    /* The color of the shadow. Defaults to opaque black. Colors created
    * from patterns are currently NOT supported. Animatable. */
    @IBInspectable var shadowColor: UIColor? {
        set {
            layer.shadowColor = newValue!.CGColor
        }
        get {
            if let color = layer.shadowColor {
                return UIColor(CGColor:color)
            }
            else {
                return nil
            }
        }
    }

    /* The opacity of the shadow. Defaults to 0. Specifying a value outside the
    * [0,1] range will give undefined results. Animatable. */
    @IBInspectable var shadowOpacity: Float {
        set {
            layer.shadowOpacity = newValue
        }
        get {
            return layer.shadowOpacity
        }
    }

    /* The shadow offset. Defaults to (0, -3). Animatable. */
    @IBInspectable var shadowOffset: CGPoint {
        set {
            layer.shadowOffset = CGSize(width: newValue.x, height: newValue.y)
        }
        get {
            return CGPoint(x: layer.shadowOffset.width, y:layer.shadowOffset.height)
        }
    }

    /* The blur radius used to create the shadow. Defaults to 3. Animatable. */
    @IBInspectable var shadowRadius: CGFloat {
        set {
            layer.shadowRadius = newValue
        }
        get {
            return layer.shadowRadius
        }
    }
}

Em seguida, isso estará disponível no Interface Builder para todas as visualizações no Painel de utilitários> Inspetor de atributos:

Painel Utilitários

Você pode facilmente definir a sombra agora.

Notas:
- A sombra não aparecerá no IB, apenas no tempo de execução.
- Como Mazen Kasser disse

Para aqueles que falharam em fazer isso funcionar, [...] verifique se o Clip Subviews ( clipsToBounds) não está ativado

Axel Guilmin
fonte
Esta é a resposta correta - configuração sobre o código para interfaces futuras
Crake
Esta solução me leva a um comportamento não trabalho com a mensagem de aviso seguinte (um para cada propriedade):Failed to set (shadowColor) user defined inspected property on (UICollectionViewCell): [<UICollectionViewCell> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key shadowColor.
Xvolks
11
Eu tive que importar UIKitpara fazê-lo funcionar, a Foundationimportação básica criada pelo XCode não é suficiente, mas compila bem. Eu deveria ter copiado o código fonte inteiro. Obrigado Axel por esta ótima solução.
Xvolks
13

Eu uso isso como parte dos meus utils. Com isso, não podemos apenas definir sombra, mas também podemos obter um canto arredondado para qualquer um UIView. Além disso, você pode definir a sombra da cor que preferir. Normalmente, o preto é o preferido, mas às vezes, quando o fundo não é branco, você pode querer outra coisa. Aqui está o que eu uso -

in utils.m
+ (void)roundedLayer:(CALayer *)viewLayer 
              radius:(float)r 
              shadow:(BOOL)s
{
    [viewLayer setMasksToBounds:YES];
    [viewLayer setCornerRadius:r];        
    [viewLayer setBorderColor:[RGB(180, 180, 180) CGColor]];
    [viewLayer setBorderWidth:1.0f];
    if(s)
    {
        [viewLayer setShadowColor:[RGB(0, 0, 0) CGColor]];
        [viewLayer setShadowOffset:CGSizeMake(0, 0)];
        [viewLayer setShadowOpacity:1];
        [viewLayer setShadowRadius:2.0];
    }
    return;
}

Para usar isso, precisamos chamar isso - [utils roundedLayer:yourview.layer radius:5.0f shadow:YES];

Srikar Appalaraju
fonte
7

Swift 3

extension UIView {
    func installShadow() {
        layer.cornerRadius = 2
        layer.masksToBounds = false
        layer.shadowColor = UIColor.black.cgColor
        layer.shadowOffset = CGSize(width: 0, height: 1)
        layer.shadowOpacity = 0.45
        layer.shadowPath = UIBezierPath(rect: bounds).cgPath
        layer.shadowRadius = 1.0
    }
}
neoneye
fonte
se você estava indo este método que eu iria considerar a adição de parâmetros para que possa ajustar os valores que a sua dinâmica
Josh O'Connor
Não está me dando cantos arredondados, estou fazendo algo errado?
Nikhil Manapure
@NikhilManapure nada acontecendo, isso pode ser porque a função installShadow()não é invocada a partir de viewDidLoad()ouviewWillAppear()
neoneye
Eu estava chamando do drawmétodo sobrecarregado de visão. A sombra está vindo corretamente, mas a esquina não.
Nikhil Manapure
@NikhilManapure sem bordas arredondadas, isso pode ser porque a exibição é UIStackViewapenas uma layout e não tem exibição. Você pode ter que inserir um regular UIViewcomo um contêiner para tudo.
neoneye
6

Se você deseja usar o StoryBoard e não deseja continuar digitando os atributos de tempo de execução, é possível criar facilmente uma extensão para as visualizações e torná-las utilizáveis ​​no storyboard.

Etapa 1. criar extensão

extension UIView {

@IBInspectable var shadowRadius: CGFloat {
    get {
        return layer.shadowRadius
    }
    set {
        layer.shadowRadius = newValue
    }
}

@IBInspectable var shadowOpacity: Float {
    get {
        return layer.shadowOpacity
    }
    set {
        layer.shadowOpacity = newValue
    }
}

@IBInspectable var shadowOffset: CGSize {
    get {
        return layer.shadowOffset
    }
    set {
        layer.shadowOffset = newValue
    }
}

@IBInspectable var maskToBound: Bool {
    get {
        return layer.masksToBounds
    }
    set {
        layer.masksToBounds = newValue
    }
}
}

etapa 2. agora você pode usar esses atributos no storyboardimagem de storyboard

king_T
fonte
3

Para aqueles que falharam em fazer isso funcionar (como eu!) Depois de tentar todas as respostas aqui, verifique se o Clip Subviews não está ativado no inspetor de Atributos ...

Mazen Kasser
fonte
1

Swift 3

self.paddingView.layer.masksToBounds = false
self.paddingView.layer.shadowOffset = CGSize(width: -15, height: 10)
self.paddingView.layer.shadowRadius = 5
self.paddingView.layer.shadowOpacity = 0.5
Josh O'Connor
fonte
1

Você pode usar minha função de utilitário criada para o raio da sombra e do canto, como abaixo:

- (void)addShadowWithRadius:(CGFloat)shadowRadius withShadowOpacity:(CGFloat)shadowOpacity withShadowOffset:(CGSize)shadowOffset withShadowColor:(UIColor *)shadowColor withCornerRadius:(CGFloat)cornerRadius withBorderColor:(UIColor *)borderColor withBorderWidth:(CGFloat)borderWidth forView:(UIView *)view{

    // drop shadow
    [view.layer setShadowRadius:shadowRadius];
    [view.layer setShadowOpacity:shadowOpacity];
    [view.layer setShadowOffset:shadowOffset];
    [view.layer setShadowColor:shadowColor.CGColor];

    // border radius
    [view.layer setCornerRadius:cornerRadius];

    // border
    [view.layer setBorderColor:borderColor.CGColor];
    [view.layer setBorderWidth:borderWidth];
}

Espero que ajude você !!!

Sandip Patel - SM
fonte
1

Tudo Responda bem, mas quero acrescentar mais um ponto

Se você encontrar um problema quando tiver células da tabela, ao remover uma célula nova, há uma incompatibilidade na sombra, portanto, nesse caso, você precisará colocar seu código de sombra no método layoutSubviews para que ele se comporte bem em todas as condições.

-(void)layoutSubviews{
    [super layoutSubviews];

    [self.contentView setNeedsLayout];
    [self.contentView layoutIfNeeded];
    [VPShadow applyShadowView:self];
}

ou no ViewControllers para exibição específica, coloque o código de sombra dentro do método a seguir para que funcione bem

-(void)viewDidLayoutSubviews{
    [super viewDidLayoutSubviews];

    [self.viewShadow layoutIfNeeded];
    [VPShadow applyShadowView:self.viewShadow];
}

Modifiquei minha implementação de sombra para novos desenvolvedores para uma forma mais generalizada, ex:

/*!
 @brief Add shadow to a view.

 @param layer CALayer of the view.

 */
+(void)applyShadowOnView:(CALayer *)layer OffsetX:(CGFloat)x OffsetY:(CGFloat)y blur:(CGFloat)radius opacity:(CGFloat)alpha RoundingCorners:(CGFloat)cornerRadius{
    UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:layer.bounds cornerRadius:cornerRadius];
    layer.masksToBounds = NO;
    layer.shadowColor = [UIColor blackColor].CGColor;
    layer.shadowOffset = CGSizeMake(x,y);// shadow x and y
    layer.shadowOpacity = alpha;
    layer.shadowRadius = radius;// blur effect
    layer.shadowPath = shadowPath.CGPath;
}
Vishal16
fonte
1

Para outros Xamarians, a versão Xamarin.iOS / C # da resposta seria semelhante à seguinte:

public override void DrawRect(CGRect area, UIViewPrintFormatter formatter)
{
    CGContext currentContext = UIGraphics.GetCurrentContext();
    currentContext.SaveState();
    currentContext.SetShadow(new CGSize(-15, 20), 5);
    base.DrawRect(area, formatter);
    currentContext.RestoreState();                
}

A principal diferença é que você adquire uma instância CGContextna qual chama diretamente os métodos apropriados.

Martin Zikmund
fonte
1

Você pode usar isso Extensionpara adicionar sombra

extension UIView {

    func addShadow(offset: CGSize, color: UIColor, radius: CGFloat, opacity: Float)
    {
        layer.masksToBounds = false
        layer.shadowOffset = offset
        layer.shadowColor = color.cgColor
        layer.shadowRadius = radius
        layer.shadowOpacity = opacity

        let backgroundCGColor = backgroundColor?.cgColor
        backgroundColor = nil
        layer.backgroundColor =  backgroundCGColor
    }
}

você pode chamar assim

your_Custom_View.addShadow(offset: CGSize(width: 0, height: 1), color: UIColor.black, radius: 2.0, opacity: 1.0)
pigeon_39
fonte
0

Esboçar sombra usando o IBDesignable e o IBInspectable no Swift 4

COMO USÁ-LO

DEMO

SKETCH E XCODE LADO A LADO

Shadow Exampl

CÓDIGO

@IBDesignable class ShadowView: UIView {

    @IBInspectable var shadowColor: UIColor? {
        get {
            if let color = layer.shadowColor {
                return UIColor(cgColor: color)
            }
            return nil
        }
        set {
            if let color = newValue {
                layer.shadowColor = color.cgColor
            } else {
                layer.shadowColor = nil
            }
        }
    }

    @IBInspectable var shadowOpacity: Float {
        get {
            return layer.shadowOpacity
        }
        set {
            layer.shadowOpacity = newValue
        }
    }

    @IBInspectable var shadowOffset: CGPoint {
        get {
            return CGPoint(x: layer.shadowOffset.width, y:layer.shadowOffset.height)
        }
        set {
            layer.shadowOffset = CGSize(width: newValue.x, height: newValue.y)
        }

     }

    @IBInspectable var shadowBlur: CGFloat {
        get {
            return layer.shadowRadius
        }
        set {
            layer.shadowRadius = newValue / 2.0
        }
    }

    @IBInspectable var shadowSpread: CGFloat = 0 {
        didSet {
            if shadowSpread == 0 {
                layer.shadowPath = nil
            } else {
                let dx = -shadowSpread
                let rect = bounds.insetBy(dx: dx, dy: dx)
                layer.shadowPath = UIBezierPath(rect: rect).cgPath
            }
        }
    }
}

RESULTADO

SAÍDA DE DEMONSTRAÇÃO

O-mkar
fonte
Você pode melhorar o código removendo os prefixos de sombra dos atributos. O objetivo do ShadowView é claro sobre a operação de sombra. Mais uma coisa é que você pode adicionar raio de canto a esta classe. (Hoje, a maioria das visualizações de sombra envolve raio de canto)
mathema