我正在尝试创建一个带有圆角和投影的按钮。无论我如何切换,按钮都不会正确显示。我试过 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
}
}
}
根据您的需要,您可以在 Storyboard 中添加一个 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 模拟器中生成下面的图像:
https://i.stack.imgur.com/jWkAf.png
我的自定义按钮带有一些阴影和圆角,我直接在Storyboard
中使用它,无需触摸 em> 以编程方式。
斯威夫特 4
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
}
}
https://i.stack.imgur.com/bubPM.png
layoutSubviews()
中时,它就成功了。
为了扩展 Imanou 的帖子,可以在自定义按钮类中以编程方式添加阴影层
@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)
}
}
获得更多可用和一致按钮的另一种方法。
斯威夫特 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
斯威夫特 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
的原因是出于性能原因,以防您需要重用该特定视图。
重构 this 以支持任何视图。从这个子类你的视图,它应该有圆角。如果您将 UIVisualEffectView 之类的内容作为子视图添加到此视图,您可能需要在该 UIVisualEffectView 上使用相同的圆角,否则它不会有圆角。
https://i.stack.imgur.com/ztnb4.png
/// 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)
}
}
}
带阴影的拐角半径
简短而简单的方法!!!!
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)
不需要 maskToBound
请验证 clipsToBounds 是否为假。
输出
https://i.stack.imgur.com/uy1Tx.png
为了改进 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)
}
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(colorAndShadow, 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.frame = bounds
colorAndShadow.path = UIBezierPath(roundedRect: bounds, cornerRadius: 12).cgPath
}
}
请注意,这里非常古老的最佳答案是正确的,但有一个严重错误
请注意,不幸的是 UIButton 与 iOS 中的 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 - 基本的“添加”贝塞尔曲线
backgroundAndShadow
是什么?
投影和角半径的扩展
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)
}
}
这是可行的解决方案!
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)
结果图像
https://i.stack.imgur.com/vKBqX.png
更新:如果您没有看到预期的输出,请尝试从主线程调用扩展方法,这肯定会起作用!
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)
}
https://i.stack.imgur.com/hkS6E.png
您可以创建一个协议并使其符合您的 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)
}
}
不定期副业成功案例分享
masksToBounds = YES
表示圆角,那阴影不也被剪掉了吗?