You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为单个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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 00:14:54