Я пытаюсь создать кнопку с закругленными углами и падающей тенью . Независимо от того, как я переключаюсь, кнопка не будет отображаться правильно. Я пробовал masksToBounds = false
и masksToBounds = true
, но либо радиус угла работает, а тень нет, либо тень работает, а радиус угла не срезает углы кнопки.
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
- не очень хорошая идея. Лучше их вставитьinitWithCoder
.Ответы:
Следующий код Swift 5 / iOS 12 показывает, как установить подкласс,
UIButton
который позволяет создавать экземпляры с закругленными углами и тенью вокруг него: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 } } }
В соответствии с вашими потребностями вы можете добавить
UIButton
в свою раскадровку и установить для него класс,CustomButton
или вы можете создать экземплярCustomButton
программного обеспечения. СледующаяUIViewController
реализация показывает, как создавать и использоватьCustomButton
экземпляр программно: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]) } }
Предыдущий код создает изображение ниже в симуляторе iPhone:
источник
masksToBounds = YES
закругленные углы, не будет ли тень также обрезана?Моя пользовательская кнопка с некоторой тенью и закругленными углами , я использую ее непосредственно внутри,
Storyboard
без необходимости прикасаться к ней программно.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 } }
источник
layoutSubviews()
.Чтобы расширить сообщение Иману, можно программно добавить слой тени в пользовательский класс кнопки.
@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) } }
источник
Альтернативный способ сделать кнопку более удобной и последовательной.
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
источник
Swift 5 и нет необходимости в "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
источник
UIBezierPath
- это соображения производительности, если вам нужно повторно использовать это конкретное представление.Отредактировал это, чтобы поддержать любую точку зрения. Подклассифицируйте свой вид от этого, и он должен иметь закругленные углы. Если вы добавите что-то вроде UIVisualEffectView в качестве подпредставления к этому представлению, вам, вероятно, потребуется использовать те же закругленные углы в этом UIVisualEffectView, иначе у него не будет закругленных углов.
/// 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) } } }
источник
Чтобы улучшить ответ PiterPan и показать настоящую тень (а не просто фон без размытия) с помощью круглой кнопки в 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) }
источник
Вот решение, которое будет работать!
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 }
Наконец, чтобы применить тень с помощью вызова радиуса угла, как показано ниже:
viewRounded.applyShadowWithCornerRadius(color: .gray, opacity: 1, radius: 15, edge: AIEdge.All, shadowSpace: 15)
Изображение результата
ОБНОВЛЕНИЕ : если вы не видите ожидаемого результата, попробуйте вызвать метод расширения из основного потока , это точно сработает!
DispatchQueue.main.async { viewRounded.applyShadowWithCornerRadius(color: .gray, opacity: 1, radius: 15, edge: AIEdge.All, shadowSpace: 15) }
источник
Если кому-то нужно добавить тени к закругленным кнопкам в Swift 3.0, вот хороший способ сделать это.
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) }
Используйте этот метод,
func viewDidLayoutSubviews()
как показано ниже:override func viewDidLayoutSubviews() { addShadowForRoundedButton(view: self.view, button: button, shadowColor: .black, shadowOffset: CGSize(width: 2, height: 2), opacity: 0.5) }
Эффект от этого метода:
источник
Расширение до падающей тени и углового радиуса
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) } }
источник
Точное решение для синтаксиса 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 } }
Обратите внимание, что
UIButton
, к сожалению, это сильно отличается отUIView
iOS.В целом комбинации тени / округления - настоящая боль в iOS. Подобные решения:
https://stackoverflow.com/a/57465440/294884 - изображение + закругленные + тени
https://stackoverflow.com/a/41553784/294884 - проблема с двумя углами
https://stackoverflow.com/a/59092828/294884 - проблема «тени + дыра» или «светящийся ящик»
https://stackoverflow.com/a/57400842/294884 - проблема «граница И разрыв»
https://stackoverflow.com/a/57514286/294884 - базовое «добавление» Безье
источник
Вы можете создать протокол и согласовать его с вашими UIView, UIButton, Cell или чем угодно, например:
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) } }
источник
Используя CAShapeLayer и UIBezierPath, мы можем легко добавить тень и радиус угла в UIView и производный от него класс, такой как UIButton, UIImageView, UILabel и т. Д.
Мы добавим расширение UIView, и это хорошо, что для этого вам нужно написать всего одну строку кода.
Вы также можете закруглить определенный угол. Просто добавьте в свой проект расширение UIView:
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) } }
Теперь просто напишите ниже код, чтобы добавить тень и радиус угла.
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)
источник
Угловой радиус с тенью
Короткий и простой способ !!!!!
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 } }
Использование кода
btn.layer.applyCornerRadiusShadow(color: .black, alpha: 0.38, x: 0, y: 3, blur: 10, spread: 0, cornerRadiusValue: 24)
Убедитесь, что clipsToBounds неверно.
ВЫВОД
источник