Estou tentando criar um botão com cantos arredondados e uma sombra projetada . Não importa como eu mude para cima, o botão não será exibido corretamente. Eu tentei masksToBounds = false
e masksToBounds = true
, mas ou o raio do canto funciona e a sombra não ou a sombra funciona e o raio do canto não corta os cantos do botão.
import UIKit
import QuartzCore
@IBDesignable
class Button : UIButton
{
@IBInspectable var masksToBounds: Bool = false {didSet{updateLayerProperties()}}
@IBInspectable var cornerRadius : CGFloat = 0 {didSet{updateLayerProperties()}}
@IBInspectable var borderWidth : CGFloat = 0 {didSet{updateLayerProperties()}}
@IBInspectable var borderColor : UIColor = UIColor.clearColor() {didSet{updateLayerProperties()}}
@IBInspectable var shadowColor : UIColor = UIColor.clearColor() {didSet{updateLayerProperties()}}
@IBInspectable var shadowOpacity: CGFloat = 0 {didSet{updateLayerProperties()}}
@IBInspectable var shadowRadius : CGFloat = 0 {didSet{updateLayerProperties()}}
@IBInspectable var shadowOffset : CGSize = CGSizeMake(0, 0) {didSet{updateLayerProperties()}}
override func drawRect(rect: CGRect)
{
updateLayerProperties()
}
func updateLayerProperties()
{
self.layer.masksToBounds = masksToBounds
self.layer.cornerRadius = cornerRadius
self.layer.borderWidth = borderWidth
self.layer.borderColor = borderColor.CGColor
self.layer.shadowColor = shadowColor.CGColor
self.layer.shadowOpacity = CFloat(shadowOpacity)
self.layer.shadowRadius = shadowRadius
self.layer.shadowOffset = shadowOffset
}
}
drawRect
não é uma ideia muito boa. Melhor colocá-losinitWithCoder
.Respostas:
O seguinte código do Swift 5 / iOS 12 mostra como definir uma subclasse de
UIButton
que permite criar instâncias com cantos arredondados e sombra ao redor:import UIKit final class CustomButton: UIButton { private var shadowLayer: CAShapeLayer! override func layoutSubviews() { super.layoutSubviews() if shadowLayer == nil { shadowLayer = CAShapeLayer() shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 12).cgPath shadowLayer.fillColor = UIColor.white.cgColor shadowLayer.shadowColor = UIColor.darkGray.cgColor shadowLayer.shadowPath = shadowLayer.path shadowLayer.shadowOffset = CGSize(width: 2.0, height: 2.0) shadowLayer.shadowOpacity = 0.8 shadowLayer.shadowRadius = 2 layer.insertSublayer(shadowLayer, at: 0) //layer.insertSublayer(shadowLayer, below: nil) // also works } } }
De acordo com suas necessidades, você pode adicionar um
UIButton
em seu Storyboard e definir sua classeCustomButton
ou pode criar uma instância deCustomButton
programaticamente. AUIViewController
implementação a seguir mostra como criar e usar umaCustomButton
instância programaticamente:import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let button = CustomButton(type: .system) button.setTitle("Button", for: .normal) view.addSubview(button) button.translatesAutoresizingMaskIntoConstraints = false let horizontalConstraint = button.centerXAnchor.constraint(equalTo: view.centerXAnchor) let verticalConstraint = button.centerYAnchor.constraint(equalTo: view.centerYAnchor) let widthConstraint = button.widthAnchor.constraint(equalToConstant: 100) let heightConstraint = button.heightAnchor.constraint(equalToConstant: 100) NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint]) } }
O código anterior produz a imagem abaixo no simulador do iPhone:
fonte
masksToBounds = YES
sombra é uma subcamada da própria camada do botão, que deve mostrar cantos arredondados, a sombra também não será cortada?Meu botão personalizado com alguma sombra e cantos arredondados , uso-o diretamente no
Storyboard
sem necessidade de tocá- lo de forma programática.class RoundedButtonWithShadow: UIButton { override func awakeFromNib() { super.awakeFromNib() self.layer.masksToBounds = false self.layer.cornerRadius = self.frame.height/2 self.layer.shadowColor = UIColor.black.cgColor self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.layer.cornerRadius).cgPath self.layer.shadowOffset = CGSize(width: 0.0, height: 3.0) self.layer.shadowOpacity = 0.5 self.layer.shadowRadius = 1.0 } }
fonte
layoutSubviews()
.Para expandir a postagem de Imanou, é possível adicionar programaticamente a camada de sombra na classe de botão personalizado
@IBDesignable class CustomButton: UIButton { var shadowAdded: Bool = false @IBInspectable var cornerRadius: CGFloat = 0 { didSet { layer.cornerRadius = cornerRadius layer.masksToBounds = cornerRadius > 0 } } override func drawRect(rect: CGRect) { super.drawRect(rect) if shadowAdded { return } shadowAdded = true let shadowLayer = UIView(frame: self.frame) shadowLayer.backgroundColor = UIColor.clearColor() shadowLayer.layer.shadowColor = UIColor.darkGrayColor().CGColor shadowLayer.layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: self.cornerRadius).CGPath shadowLayer.layer.shadowOffset = CGSize(width: 1.0, height: 1.0) shadowLayer.layer.shadowOpacity = 0.5 shadowLayer.layer.shadowRadius = 1 shadowLayer.layer.masksToBounds = true shadowLayer.clipsToBounds = false self.superview?.addSubview(shadowLayer) self.superview?.bringSubviewToFront(self) } }
fonte
Uma forma alternativa de obter um botão mais usável e consistente.
Swift 2:
func getImageWithColor(color: UIColor, size: CGSize, cornerRadius:CGFloat) -> UIImage { let rect = CGRectMake(0, 0, size.width, size.height) UIGraphicsBeginImageContextWithOptions(size, false, 1) UIBezierPath( roundedRect: rect, cornerRadius: cornerRadius ).addClip() color.setFill() UIRectFill(rect) let image: UIImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image } let button = UIButton(type: .Custom) button.frame = CGRectMake(20, 20, 200, 50) button.setTitle("My Button", forState: UIControlState.Normal) button.setTitleColor(UIColor.blackColor(), forState: UIControlState.Normal) self.addSubview(button) let image = getImageWithColor(UIColor.whiteColor(), size: button.frame.size, cornerRadius: 5) button.setBackgroundImage(image, forState: UIControlState.Normal) button.layer.shadowRadius = 5 button.layer.shadowColor = UIColor.blackColor().CGColor button.layer.shadowOpacity = 0.5 button.layer.shadowOffset = CGSizeMake(0, 1) button.layer.masksToBounds = false
Swift 3:
func getImageWithColor(_ color: UIColor, size: CGSize, cornerRadius:CGFloat) -> UIImage? { let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) UIGraphicsBeginImageContextWithOptions(size, false, 0) color.setFill() UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).addClip() color.setFill() UIRectFill(rect) let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()! UIGraphicsEndImageContext() return image } let button = UIButton(type: .custom) button.frame = CGRect(x:20, y:20, width:200, height:50) button.setTitle("My Button", for: .normal) button.setTitleColor(UIColor.black, for: .normal) self.addSubview(button) if let image = getImageWithColor(UIColor.white, size: button.frame.size, cornerRadius: 5) { button.setBackgroundImage(image, for: .normal) } button.layer.shadowRadius = 5 button.layer.shadowColor = UIColor.black.cgColor button.layer.shadowOpacity = 0.5 button.layer.shadowOffset = CGSize(width:0, height:1) button.layer.masksToBounds = false
fonte
Swift 5 e sem necessidade de "UIBezierPath"
view.layer.cornerRadius = 15 view.clipsToBounds = true view.layer.masksToBounds = false view.layer.shadowRadius = 7 view.layer.shadowOpacity = 0.6 view.layer.shadowOffset = CGSize(width: 0, height: 5) view.layer.shadowColor = UIColor.red.cgColor
fonte
UIBezierPath
é por motivos de desempenho no caso de precisar reutilizar aquela vista em particular.Reformulado este para apoiar qualquer ponto de vista. Subclasse sua visualização a partir disso e ela deve ter cantos arredondados. Se você adicionar algo como UIVisualEffectView como uma subvisualização para esta visão, provavelmente precisará usar os mesmos cantos arredondados naquele UIVisualEffectView ou não terá cantos arredondados.
/// Inspiration: https://stackoverflow.com/a/25475536/129202 class ViewWithRoundedcornersAndShadow: UIView { private var theShadowLayer: CAShapeLayer? override func layoutSubviews() { super.layoutSubviews() if self.theShadowLayer == nil { let rounding = CGFloat.init(22.0) let shadowLayer = CAShapeLayer.init() self.theShadowLayer = shadowLayer shadowLayer.path = UIBezierPath.init(roundedRect: bounds, cornerRadius: rounding).cgPath shadowLayer.fillColor = UIColor.clear.cgColor shadowLayer.shadowPath = shadowLayer.path shadowLayer.shadowColor = UIColor.black.cgColor shadowLayer.shadowRadius = CGFloat.init(3.0) shadowLayer.shadowOpacity = Float.init(0.2) shadowLayer.shadowOffset = CGSize.init(width: 0.0, height: 4.0) self.layer.insertSublayer(shadowLayer, at: 0) } } }
fonte
Para melhorar a resposta de PiterPan e mostrar uma sombra real (não apenas um fundo sem desfoque) com um botão circular no Swift 3:
override func viewDidLoad() { super.viewDidLoad() myButton.layer.masksToBounds = false myButton.layer.cornerRadius = myButton.frame.height/2 myButton.clipsToBounds = true } override func viewDidLayoutSubviews() { addShadowForRoundedButton(view: self.view, button: myButton, opacity: 0.5) } func addShadowForRoundedButton(view: UIView, button: UIButton, opacity: Float = 1) { let shadowView = UIView() shadowView.backgroundColor = UIColor.black shadowView.layer.opacity = opacity shadowView.layer.shadowRadius = 5 shadowView.layer.shadowOpacity = 0.35 shadowView.layer.shadowOffset = CGSize(width: 0, height: 0) shadowView.layer.cornerRadius = button.bounds.size.width / 2 shadowView.frame = CGRect(origin: CGPoint(x: button.frame.origin.x, y: button.frame.origin.y), size: CGSize(width: button.bounds.width, height: button.bounds.height)) self.view.addSubview(shadowView) view.bringSubview(toFront: button) }
fonte
Aqui está a solução que funcionará!
extension UIView { func applyShadowWithCornerRadius(color:UIColor, opacity:Float, radius: CGFloat, edge:AIEdge, shadowSpace:CGFloat) { var sizeOffset:CGSize = CGSize.zero switch edge { case .Top: sizeOffset = CGSize(width: 0, height: -shadowSpace) case .Left: sizeOffset = CGSize(width: -shadowSpace, height: 0) case .Bottom: sizeOffset = CGSize(width: 0, height: shadowSpace) case .Right: sizeOffset = CGSize(width: shadowSpace, height: 0) case .Top_Left: sizeOffset = CGSize(width: -shadowSpace, height: -shadowSpace) case .Top_Right: sizeOffset = CGSize(width: shadowSpace, height: -shadowSpace) case .Bottom_Left: sizeOffset = CGSize(width: -shadowSpace, height: shadowSpace) case .Bottom_Right: sizeOffset = CGSize(width: shadowSpace, height: shadowSpace) case .All: sizeOffset = CGSize(width: 0, height: 0) case .None: sizeOffset = CGSize.zero } self.layer.cornerRadius = self.frame.size.height / 2 self.layer.masksToBounds = true; self.layer.shadowColor = color.cgColor self.layer.shadowOpacity = opacity self.layer.shadowOffset = sizeOffset self.layer.shadowRadius = radius self.layer.masksToBounds = false self.layer.shadowPath = UIBezierPath(roundedRect:self.bounds, cornerRadius:self.layer.cornerRadius).cgPath } } enum AIEdge:Int { case Top, Left, Bottom, Right, Top_Left, Top_Right, Bottom_Left, Bottom_Right, All, None }
Finalmente, para aplicar sombra com raio de canto, chame conforme abaixo:
viewRounded.applyShadowWithCornerRadius(color: .gray, opacity: 1, radius: 15, edge: AIEdge.All, shadowSpace: 15)
Imagem de Resultado
ATUALIZAÇÃO : Se você não vê a saída esperada, tente chamar o método de extensão do Main Thread , isso funcionará com certeza!
DispatchQueue.main.async { viewRounded.applyShadowWithCornerRadius(color: .gray, opacity: 1, radius: 15, edge: AIEdge.All, shadowSpace: 15) }
fonte
Se alguém precisar adicionar sombras aos botões arredondados no Swift 3.0, aqui está um bom método para fazê-lo.
func addShadowForRoundedButton(view: UIView, button: UIButton, shadowColor: UIColor, shadowOffset: CGSize, opacity: Float = 1) { let shadowView = UIView() shadowView.backgroundColor = shadowColor shadowView.layer.opacity = opacity shadowView.layer.cornerRadius = button.bounds.size.width / 2 shadowView.frame = CGRect(origin: CGPoint(x: button.frame.origin.x + shadowOffset.width, y: button.frame.origin.y + shadowOffset.height), size: CGSize(width: button.bouds.width, height: button.bounds.height)) self.view.addSubview(shadowView) view.bringSubview(toFront: button) }
Use este método
func viewDidLayoutSubviews()
conforme abaixo:override func viewDidLayoutSubviews() { addShadowForRoundedButton(view: self.view, button: button, shadowColor: .black, shadowOffset: CGSize(width: 2, height: 2), opacity: 0.5) }
O efeito deste método é:
fonte
Extensão para sombra e raio de canto
extension UIView { func dropShadow(color: UIColor, opacity: Float = 0.5, offSet: CGSize, shadowRadius: CGFloat = 1, scale: Bool = true, cornerRadius: CGFloat) { let shadowLayer = CAShapeLayer() shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius).cgPath shadowLayer.fillColor = UIColor.white.cgColor shadowLayer.shadowColor = color.cgColor shadowLayer.shadowPath = shadowLayer.path shadowLayer.shadowOffset = offSet shadowLayer.shadowOpacity = opacity shadowLayer.shadowRadius = shadowRadius layer.insertSublayer(shadowLayer, at: 0) } }
fonte
Solução exata para a sintaxe de 2020
import UIKit class ColorAndShadowButton: UIButton { override init(frame: CGRect) { super.init(frame: frame), common() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder), common() } private func common() { // UIButton is tricky: you MUST set the clear bg in bringup; NOT in layout backgroundColor = .clear clipsToBounds = false self.layer.insertSublayer(backgroundAndShadow, below: layer) } lazy var colorAndShadow: CAShapeLayer = { let s = CAShapeLayer() // set your button color HERE (NOT on storyboard) s.fillColor = UIColor.black.cgColor // now set your shadow color/values s.shadowColor = UIColor.red.cgColor s.shadowOffset = CGSize(width: 0, height: 10) s.shadowOpacity = 1 s.shadowRadius = 10 // now add the shadow layer.insertSublayer(s, at: 0) return s }() override func layoutSubviews() { super.layoutSubviews() // you MUST layout these two EVERY layout cycle: colorAndShadow = bounds colorAndShadow = UIBezierPath(roundedRect: bounds, cornerRadius: 12).cgPath } }
Observe que,
UIButton
infelizmente, é bem diferente doUIView
iOS.Em geral, combos de sombras / arredondamentos são uma verdadeira dor no iOS. Soluções semelhantes:
https://stackoverflow.com/a/57465440/294884 - imagem + sombras + arredondadas
https://stackoverflow.com/a/41553784/294884 - problema de dois cantos
https://stackoverflow.com/a/59092828/294884 - problema de "sombras + buraco" ou "caixa de luz"
https://stackoverflow.com/a/57400842/294884 - problema de "borda E lacuna"
https://stackoverflow.com/a/57514286/294884 - "adição" básica Beziers
fonte
Você pode criar um protocolo e adaptá-lo ao seu UIView, UIButton, Cell ou o que você quiser assim:
protocol RoundedShadowable: class { var shadowLayer: CAShapeLayer? { get set } var layer: CALayer { get } var bounds: CGRect { get } } extension RoundedShadowable { func applyShadowOnce(withCornerRadius cornerRadius: CGFloat, andFillColor fillColor: UIColor) { if self.shadowLayer == nil { let shadowLayer = CAShapeLayer() shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius).cgPath shadowLayer.fillColor = fillColor.cgColor shadowLayer.shadowColor = UIColor.black.cgColor shadowLayer.shadowPath = shadowLayer.path shadowLayer.shadowOffset = CGSize(width: 0.0, height: 2.0) shadowLayer.shadowOpacity = 0.2 shadowLayer.shadowRadius = 3 self.layer.insertSublayer(shadowLayer, at: 0) self.shadowLayer = shadowLayer } } } class RoundShadowView: UIView, RoundedShadowable { var shadowLayer: CAShapeLayer? private let cornerRadius: CGFloat private let fillColor: UIColor init(cornerRadius: CGFloat, fillColor: UIColor) { self.cornerRadius = cornerRadius self.fillColor = fillColor super.init(frame: .zero) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() self.applyShadowOnce(withCornerRadius: self.cornerRadius, andFillColor: self.fillColor) } } class RoundShadowButton: UIButton, RoundedShadowable { var shadowLayer: CAShapeLayer? private let cornerRadius: CGFloat private let fillColor: UIColor init(cornerRadius: CGFloat, fillColor: UIColor) { self.cornerRadius = cornerRadius self.fillColor = fillColor super.init(frame: .zero) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() self.applyShadowOnce(withCornerRadius: self.cornerRadius, andFillColor: self.fillColor) } }
fonte
Usando CAShapeLayer e UIBezierPath, podemos facilmente adicionar sombra e raio de canto a UIView e a classe derivada dele, como UIButton, UIImageView, UILabel etc.
Adicionaremos a extensão UIView e o bom ponto disso é que você só precisa escrever uma linha de código para conseguir isso.
Você também pode contornar um canto específico. Basta adicionar a extensão UIView em seu projeto:
extension UIView { func addShadow(shadowColor: UIColor, offSet: CGSize, opacity: Float, shadowRadius: CGFloat, cornerRadius: CGFloat, corners: UIRectCorner, fillColor: UIColor = .white) { let shadowLayer = CAShapeLayer() let size = CGSize(width: cornerRadius, height: cornerRadius) let cgPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: size).cgPath //1 shadowLayer.path = cgPath //2 shadowLayer.fillColor = fillColor.cgColor //3 shadowLayer.shadowColor = shadowColor.cgColor //4 shadowLayer.shadowPath = cgPath shadowLayer.shadowOffset = offSet //5 shadowLayer.shadowOpacity = opacity shadowLayer.shadowRadius = shadowRadius self.layer.addSublayer(shadowLayer) } }
Agora basta escrever o código abaixo para adicionar sombra e raio de canto
self.myView.addShadow(shadowColor: .black, offSet: CGSize(width: 2.6, height: 2.6), opacity: 0.8, shadowRadius: 5.0, cornerRadius: 20.0, corners: [.topRight, .topLeft], fillColor: .red)
fonte
Raio de canto com sombra
Caminho curto e simples !!!!!
extension CALayer { func applyCornerRadiusShadow( color: UIColor = .black, alpha: Float = 0.5, x: CGFloat = 0, y: CGFloat = 2, blur: CGFloat = 4, spread: CGFloat = 0, cornerRadiusValue: CGFloat = 0) { cornerRadius = cornerRadiusValue shadowColor = color.cgColor shadowOpacity = alpha shadowOffset = CGSize(width: x, height: y) shadowRadius = blur / 2.0 if spread == 0 { shadowPath = nil } else { let dx = -spread let rect = bounds.insetBy(dx: dx, dy: dx) shadowPath = UIBezierPath(rect: rect).cgPath } }
Uso de código
btn.layer.applyCornerRadiusShadow(color: .black, alpha: 0.38, x: 0, y: 3, blur: 10, spread: 0, cornerRadiusValue: 24)
Verifique se clipsToBounds é falso.
RESULTADO
fonte