如何为单个UIView添加双重阴影?iOS开发技术求助
如何给UIView添加双重阴影效果?
我尝试了三种方法都没能实现UIView的双重阴影效果,具体情况如下:
方法一:插入单个阴影子层(无效)
通过创建独立的CALayer添加阴影,但代码存在变量不匹配等问题,最终没有效果:
// 创建UIView let testView = UIView() testView.backgroundColor = UIColor.gray testView.layer.borderWidth = 1 testView.translatesAutoresizingMaskIntoConstraints = false // 创建阴影层(变量名不匹配:调用createShadowDown但方法是createShadowUp,参数是buttonMenu而非testView) let shadowUp = createShadowDown(buttonMenu) // 插入子层(变量名不匹配:shadowUp未使用,插入的是未定义的layerOne) testView.layer.insertSublayer(layerOne, at: 0) // 创建阴影的方法 func createShadowUp(_ size: UIView) -> CALayer { let layer = CALayer() layer.frame = size.bounds layer.shadowColor = UIColor.red.cgColor layer.shadowOpacity = 1 layer.shadowRadius = 10 layer.shadowOffset = CGSize(width: 0, height: 20) return layer }
方法二:直接设置视图layer的阴影(仅支持单个阴影)
这种方法能实现单个阴影,但重复设置会覆盖原有参数,无法添加第二个阴影:
// 创建UIView let testView = UIView() testView.backgroundColor = UIColor.gray testView.layer.borderWidth = 1 testView.translatesAutoresizingMaskIntoConstraints = false // 创建阴影(调用多了一个右括号) createShadowTest(testView)) // 创建阴影的方法 func createShadowTest(_ view: UIView) { view.layer.shadowColor = UIColor.red.cgColor view.layer.shadowOpacity = 1 view.layer.shadowRadius = 10 view.layer.shadowOffset = CGSize(width: 0, height: 20) }
方法三:参考外部方案(无效)
尝试添加两个阴影子层,但因参数传递、视图边界未正确获取等问题失效:
// 创建UIView let testView = UIView() testView.backgroundColor = UIColor.gray testView.layer.borderWidth = 1 testView.translatesAutoresizingMaskIntoConstraints = false // 创建阴影(调用多了一个右括号,且方法定义无参数却传了testView) addDropShadow(testView)) // 添加阴影的方法 func addDropShadow() { let topLayer = createShadowLayer(color: .red, offset: CGSize(width: 0, height: -20)) let bottomLayer = createShadowLayer(color: .green, offset: CGSize(width: 0, height: 20)) self.testView.layer.insertSublayer(topLayer, at: 0) self.testView.layer.insertSublayer(bottomLayer, at: 1) } // 创建阴影层的方法 func createShadowLayer(color: UIColor, offset: CGSize) -> CALayer { let shadowLayer = CALayer() shadowLayer.masksToBounds = false shadowLayer.shadowColor = color.cgColor shadowLayer.shadowOpacity = 1 shadowLayer.shadowOffset = offset shadowLayer.shadowRadius = 10 shadowLayer.shouldRasterize = true // 此时testView的bounds可能因AutoLayout未确定,导致阴影路径错误 shadowLayer.shadowPath = UIBezierPath(roundedRect: testView.bounds, cornerRadius: 10).cgPath return shadowLayer }
可行的实现方案
要实现双重阴影,核心是创建两个独立的阴影层,添加到目标视图的layer底层,并且在视图布局更新时同步更新阴影层的frame和阴影路径。以下是完整可运行的代码:
方案1:在ViewController中实现
import UIKit class ViewController: UIViewController { let testView = UIView() // 保存阴影层引用,方便布局更新时修改 private var topShadowLayer: CALayer! private var bottomShadowLayer: CALayer! override func viewDidLoad() { super.viewDidLoad() setupTestView() setupDoubleShadows() } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() // 视图布局完成后更新阴影层的frame和路径 updateShadowLayersFrame() } private func setupTestView() { view.addSubview(testView) testView.backgroundColor = .gray testView.layer.borderWidth = 1 testView.layer.cornerRadius = 10 // 和阴影路径的圆角保持一致 testView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ testView.centerXAnchor.constraint(equalTo: view.centerXAnchor), testView.centerYAnchor.constraint(equalTo: view.centerYAnchor), testView.widthAnchor.constraint(equalToConstant: 200), testView.heightAnchor.constraint(equalToConstant: 100) ]) } private func setupDoubleShadows() { // 创建顶部阴影层(向上偏移) topShadowLayer = createShadowLayer(color: .red, offset: CGSize(width: 0, height: -20)) // 创建底部阴影层(向下偏移) bottomShadowLayer = createShadowLayer(color: .green, offset: CGSize(width: 0, height: 20)) // 将阴影层插入到testView.layer的最底层,避免遮挡视图内容 testView.layer.insertSublayer(topShadowLayer, at: 0) testView.layer.insertSublayer(bottomShadowLayer, at: 1) } private func createShadowLayer(color: UIColor, offset: CGSize) -> CALayer { let shadowLayer = CALayer() shadowLayer.masksToBounds = false shadowLayer.shadowColor = color.cgColor shadowLayer.shadowOpacity = 1 shadowLayer.shadowOffset = offset shadowLayer.shadowRadius = 10 // 开启光栅化并设置正确的缩放比例,提升性能同时避免模糊 shadowLayer.shouldRasterize = true shadowLayer.rasterizationScale = UIScreen.main.scale return shadowLayer } private func updateShadowLayersFrame() { let bounds = testView.bounds // 更新阴影层的frame topShadowLayer.frame = bounds bottomShadowLayer.frame = bounds // 更新阴影路径,和testView的圆角保持一致 let shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: testView.layer.cornerRadius).cgPath topShadowLayer.shadowPath = shadowPath bottomShadowLayer.shadowPath = shadowPath } }
方案2:封装成UIView扩展(更通用)
如果需要给任意UIView添加双重阴影,可以封装成扩展:
import UIKit extension UIView { func addDoubleShadows(topShadowColor: UIColor, topOffset: CGSize, bottomShadowColor: UIColor, bottomOffset: CGSize, shadowRadius: CGFloat = 10, opacity: Float = 1) { // 移除已有的阴影层(避免重复添加) layer.sublayers?.filter { $0.name == "doubleShadowLayer" }.forEach { $0.removeFromSuperlayer() } let topLayer = createShadowLayer(color: topShadowColor, offset: topOffset, radius: shadowRadius, opacity: opacity) topLayer.name = "doubleShadowLayer" let bottomLayer = createShadowLayer(color: bottomShadowColor, offset: bottomOffset, radius: shadowRadius, opacity: opacity) bottomLayer.name = "doubleShadowLayer" layer.insertSublayer(topLayer, at: 0) layer.insertSublayer(bottomLayer, at: 1) // 添加布局更新监听 addObserver(self, forKeyPath: "bounds", options: [.new], context: nil) } private func createShadowLayer(color: UIColor, offset: CGSize, radius: CGFloat, opacity: Float) -> CALayer { let layer = CALayer() layer.masksToBounds = false layer.shadowColor = color.cgColor layer.shadowOpacity = opacity layer.shadowOffset = offset layer.shadowRadius = radius layer.shouldRasterize = true layer.rasterizationScale = UIScreen.main.scale return layer } override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if keyPath == "bounds" { updateDoubleShadows() } } private func updateDoubleShadows() { let bounds = self.bounds let shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius).cgPath layer.sublayers?.filter { $0.name == "doubleShadowLayer" }.forEach { $0.frame = bounds $0.shadowPath = shadowPath } } deinit { removeObserver(self, forKeyPath: "bounds") } }
使用扩展的示例:
// 在ViewController中使用 let testView = UIView() testView.backgroundColor = .gray testView.layer.borderWidth = 1 testView.layer.cornerRadius = 10 testView.addDoubleShadows(topShadowColor: .red, topOffset: CGSize(width: 0, height: -20), bottomShadowColor: .green, bottomOffset: CGSize(width: 0, height: 20))
关键注意点
- 阴影层的插入顺序:要将阴影层插入到视图layer的底层(
at:0、at:1),避免遮挡视图自身的内容。 - 布局更新同步:使用AutoLayout时,视图的bounds在
viewDidLoad时还未确定,必须在viewDidLayoutSubviews(ViewController中)或监听bounds变化(扩展中)时更新阴影层的frame和shadowPath。 - 光栅化设置:开启
shouldRasterize时必须设置rasterizationScale = UIScreen.main.scale,否则阴影会模糊。 - 阴影路径匹配:
shadowPath要和视图的圆角、边界保持一致,确保阴影形状和视图完全贴合。
内容的提问来源于stack exchange,提问作者Steven Kirke
相关产品推荐
相关产品推荐

