Qual é a melhor maneira de adicionar uma sombra projetada ao meu UIView


108

Estou tentando adicionar uma sombra projetada para visualizações que são colocadas em camadas umas sobre as outras, as visualizações colapsam permitindo que o conteúdo em outras visualizações seja visto, neste sentido, eu quero manter view.clipsToBoundsON para que, quando as visualizações colapsarem, seu conteúdo seja cortado.

Isso parece ter dificultado a adição de uma sombra projetada às camadas, pois quando eu ligo clipsToBoundsas sombras também são cortadas.

Tenho tentado manipular view.framee view.boundsadicionar uma sombra projetada ao quadro, mas permitir que os limites sejam grandes o suficiente para englobá-lo, no entanto, não tive sorte com isso.

Aqui está o código que estou usando para adicionar uma sombra (isso só funciona com clipsToBoundsOFF, conforme mostrado)

view.clipsToBounds = NO;
view.layer.shadowColor = [[UIColor blackColor] CGColor];
view.layer.shadowOffset = CGSizeMake(0,5);
view.layer.shadowOpacity = 0.5;

Aqui está uma captura de tela da sombra sendo aplicada à camada superior cinza mais claro. Espero que isso dê uma ideia de como meu conteúdo se sobreporá se clipsToBoundsestiver DESLIGADO.

Aplicação de sombra.

Como posso adicionar uma sombra UIViewe manter meu conteúdo cortado?

Edit: Só queria acrescentar que também brinquei com o uso de imagens de fundo com sombras, o que funciona bem, no entanto, ainda gostaria de saber a melhor solução codificada para isso.

Respostas:


280

Experimente isto:

UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRect:view.bounds];
view.layer.masksToBounds = NO;
view.layer.shadowColor = [UIColor blackColor].CGColor;
view.layer.shadowOffset = CGSizeMake(0.0f, 5.0f);
view.layer.shadowOpacity = 0.5f;
view.layer.shadowPath = shadowPath.CGPath;

Em primeiro lugar : o UIBezierPathusado como shadowPathé crucial. Se você não o usar, pode não notar a diferença no início, mas o olho atento observará um certo atraso ocorrendo durante eventos como girar o dispositivo e / ou similar. É um ajuste de desempenho importante.

Especificamente em relação ao seu problema : A linha importante é view.layer.masksToBounds = NO. Ele desativa o recorte das subcamadas da camada de visualização que se estendem além dos limites da visualização.

Para aqueles que estão se perguntando qual é a diferença entre masksToBounds(na camada) e a clipToBoundspropriedade da própria visualização : Não existe nenhuma. Alternar um terá efeito sobre o outro. Apenas um nível diferente de abstração.


Swift 2.2:

override func layoutSubviews()
{
    super.layoutSubviews()

    let shadowPath = UIBezierPath(rect: bounds)
    layer.masksToBounds = false
    layer.shadowColor = UIColor.blackColor().CGColor
    layer.shadowOffset = CGSizeMake(0.0, 5.0)
    layer.shadowOpacity = 0.5
    layer.shadowPath = shadowPath.CGPath
}

Swift 3:

override func layoutSubviews()
{
    super.layoutSubviews()

    let shadowPath = UIBezierPath(rect: bounds)
    layer.masksToBounds = false
    layer.shadowColor = UIColor.black.cgColor
    layer.shadowOffset = CGSize(width: 0.0, height: 5.0)
    layer.shadowOpacity = 0.5
    layer.shadowPath = shadowPath.cgPath
}

1
Obrigado, tentei seu código e depois tentei adicionar masksToBounds = NO;ao meu original - com ambas as tentativas eu mantive clipsToBounds = YES;ON - ambos falharam em cortar o conteúdo. aqui está uma captura de tela
Wez

1
Alguma ideia de como fazer com que a sombra projetada abrace um canto arredondado em vez de renderizar como se o canto ainda fosse um quadrado?
John Erck

7
@JohnErck tente o seguinte: UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds cornerRadius:5.0];. Não testado, mas deve produzir o resultado desejado.
pkluz

1
Aprendi que, ao animar a vista, a sombra deixa rastros cinzentos enquanto se move. Remover as linhas do shadowPath resolveu isso
ishahak

1
@shoe porque qualquer hora que o tamanho da vista muda, layoutSubviews()é chamado. E se não compensarmos nesse local ajustando o shadowPathpara refletir o tamanho atual, teríamos uma visão redimensionada, mas a sombra ainda teria o tamanho antigo (inicial) e não mostraria ou espiaria onde não deveria .
pkluz

65

A resposta de Wasabii no Swift 2.3:

let shadowPath = UIBezierPath(rect: view.bounds)
view.layer.masksToBounds = false
view.layer.shadowColor = UIColor.blackColor().CGColor
view.layer.shadowOffset = CGSize(width: 0, height: 0.5)
view.layer.shadowOpacity = 0.2
view.layer.shadowPath = shadowPath.CGPath

E em Swift 3/4/5:

let shadowPath = UIBezierPath(rect: view.bounds)
view.layer.masksToBounds = false
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOffset = CGSize(width: 0, height: 0.5)
view.layer.shadowOpacity = 0.2
view.layer.shadowPath = shadowPath.cgPath

Coloque este código em layoutSubviews () se você estiver usando AutoLayout.

No SwiftUI, isso é muito mais fácil:

Color.yellow  // or whatever your view
    .shadow(radius: 3)
    .frame(width: 200, height: 100)

11
Obrigado por observarlayoutSubviews
Islam Q.

1
ou ainda melhor em viewDidLayoutSubviews ()
Max MacLeod

1
preferem initialisers rápida struct sobre Marca variantes, ou sejaCGSize(width: 0, height: 0.5)
Oliver Atkinson

Eu adicionei este código a layoutSubviews (), ele ainda não foi renderizado em IB. Existem outras configurações que eu devo ativar?
burro

Obrigado. Eu estava usando o Auto Layout e pensei comigo mesmo - bem ... view.boundsnão foi criado de forma tão óbvia que shadowPathnão pode fazer referência ao retângulo da visualização, mas sim a alguns CGRectZero. Enfim, colocar isso layoutSubviewsresolveu meu problema finalmente!
devdc

13

O truque é definir a masksToBoundspropriedade da camada de sua visualização corretamente:

view.layer.masksToBounds = NO;

e deve funcionar.

( Fonte )


13

Você pode criar uma extensão para UIView para acessar esses valores no editor de design

Opções de sombra no editor de design

extension UIView{

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

    @IBInspectable var shadowColor: UIColor{
        get{
            return UIColor(cgColor: self.layer.shadowColor!)
        }
        set{
            self.layer.shadowColor = newValue.cgColor
        }
    }

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

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

como se referir a essa extensão no construtor de interface
Amr Angry

IBInspectable o torna disponível no construtor de interface
Nitesh

Posso conseguir isso no Objective C também?
Harish Pathak

2
se você quiser ter uma visualização em tempo real (no construtor de interface), aqui você pode encontrar um bom artigo sobre isso spin.atomicobject.com/2017/07/18/swift-interface-builder
medskill

7

Você pode definir a sombra para a sua visão do storyboard também

insira a descrição da imagem aqui


2

Em viewWillLayoutSubviews:

override func viewWillLayoutSubviews() {
    sampleView.layer.masksToBounds =  false
    sampleView.layer.shadowColor = UIColor.darkGrayColor().CGColor;
    sampleView.layer.shadowOffset = CGSizeMake(2.0, 2.0)
    sampleView.layer.shadowOpacity = 1.0
}

Usando a extensão do UIView:

extension UIView {

    func addDropShadowToView(targetView:UIView? ){
        targetView!.layer.masksToBounds =  false
        targetView!.layer.shadowColor = UIColor.darkGrayColor().CGColor;
        targetView!.layer.shadowOffset = CGSizeMake(2.0, 2.0)
        targetView!.layer.shadowOpacity = 1.0
    }
}

Uso:

sampleView.addDropShadowToView(sampleView)

1
Apenas aplique targetView: UIViewe remova a franja.
bluebamboo

6
Por que não usar self? Parece muito mais conveniente para mim.
LinusGeffarth

3
A melhor maneira de fazer isso é remover targetView como um parâmetro e usar self em vez de targetView. Isso elimina a redundância e permite chamá-lo assim: sampleView.addDropShadowToView ()
maxcodes

Os comentários de Max Nelson são ótimos. Minha sugestão é usar if letsintaxe em vez de!
Johnny

0

Portanto, sim, você deve preferir a propriedade shadowPath para desempenho, mas também: Do arquivo de cabeçalho de CALayer.shadowPath

Especificar o caminho explicitamente usando essa propriedade geralmente * melhorará o desempenho de renderização, assim como compartilhará o mesmo caminho * de referência em várias camadas

Um truque menos conhecido é compartilhar a mesma referência em várias camadas. Claro que eles têm que usar a mesma forma, mas isso é comum com células de visualização de tabela / coleção.

Não sei por que fica mais rápido se você compartilhar instâncias, acho que ele armazena em cache a renderização da sombra e pode reutilizá-la para outras instâncias no modo de exibição. Eu me pergunto se isso é ainda mais rápido com

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.