一、前言

在iOS开发中,UI阴影是非常高频的UI视觉需求,常用于卡片、弹窗、自定义按钮、商品列表等View。很多开发者直接使用CALayer原生阴影属性,但是不优化的阴影极其卡顿、渲染耗时严重

本文从iOS原生阴影底层逻辑入手,深度分析阴影卡顿原因、贝塞尔曲线高性能优化方案,并全程使用最新 Swift 规范写法,代码简洁通用,适合生产项目直接复用。

二、iOS原生阴影基础写法

先看最基础、未优化的原生阴影写法,也是新手最容易写出的错误代码:

// 错误写法:没有 shadowPath,严重卡顿
view.layer.masksToBounds = false
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOffset = CGSize(width: 0, height: 5)
view.layer.shadowOpacity = 0.5

2.1 阴影核心属性解析

  1. masksToBounds = false:禁止裁剪边界,阴影必须开启false,否则阴影被裁切看不见;

  2. shadowColor:阴影颜色,一般黑色、深灰色;

  3. shadowOffset:阴影偏移,宽高代表横向、纵向偏移;

  4. shadowOpacity:阴影透明度(0~1);

  5. shadowPath:指定阴影渲染路径,优化卡顿最关键属性

三、iOS 阴影底层原理与开发痛点

3.1 不加 shadowPath 会发生什么?

很多开发者仅配置阴影颜色、透明度、偏移,忽略 shadowPath

// 错误写法!不加 shadowPath 极度卡顿
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOffset = CGSize(width: 0, height: 5)
view.layer.shadowOpacity = 0.5

底层原理:

如果不手动指定shadowPath,iOS 会自动计算当前 View 所有透明像素,遍历每一个像素生成阴影。如果页面存在大量卡片、列表 Cell,帧率会直接掉到 30fps 以下,滑动卡顿、CPU占用飙升。

3.2 shadowPath 优化作用

手动给 shadowPath 设置贝塞尔路径,系统不再自动遍历像素,直接根据固定路径渲染阴影,渲染效率提升数倍,列表滑动丝滑不卡顿。

3.3 masksToBounds 注意事项

  • masksToBounds = true:裁剪边界,阴影消失;

  • masksToBounds = false:不裁剪边界,阴影正常显示;

重点:阴影 & 圆角不能同时使用 masksToBounds,圆角需要单独配置贝塞尔裁切。

四、Swift 最新规范实现(完整版)

下面提供Swift 5.0+ 最新写法,包含:普通矩形阴影、圆角阴影、扩展工具类、生产级可直接复制。

4.1 基础矩形阴影(标准优化写法)

import UIKit

// 基础阴影添加
func addNormalShadow(to view: UIView) {
    let shadowPath = UIBezierPath(rect: view.bounds)
    view.layer.masksToBounds = false
    view.layer.shadowColor = UIColor.black.cgColor
    view.layer.shadowOffset = CGSize(width: 0, height: 5)
    view.layer.shadowOpacity = 0.5
    view.layer.shadowPath = shadowPath.cgPath
}

4.2 开发常用:圆角阴影(企业最常用)

实际项目中卡片几乎都是圆角+阴影,搭配贝塞尔曲线裁切,完美解决圆角+阴影共存问题:

// 圆角 + 阴影 组合(生产常用)
func addCornerShadow(to view: UIView, radius: CGFloat) {
    // 圆角裁切
    let cornerPath = UIBezierPath(roundedRect: view.bounds, cornerRadius: radius)
    view.layer.cornerRadius = radius
    view.layer.masksToBounds = false
    
    // 阴影配置
    view.layer.shadowColor = UIColor.black.cgColor
    view.layer.shadowOffset = CGSize(width: 0, height: 4)
    view.layer.shadowOpacity = 0.35
    view.layer.shadowRadius = 8
    view.layer.shadowPath = cornerPath.cgPath
}

4.3 高级封装:UIView 扩展(全局一键调用)

封装为扩展,整个项目任意View一键添加阴影,极简开发:

import UIKit

extension UIView {
    /// 快速添加阴影
    /// - Parameters:
    ///   - radius: 圆角
    ///   - shadowOffset: 阴影偏移
    ///   - shadowOpacity: 阴影透明度
    ///   - shadowRadius: 阴影模糊半径
    func addShadow(cornerRadius: CGFloat,
                   shadowOffset: CGSize = CGSize(width: 0, height: 5),
                   shadowOpacity: Float = 0.5,
                   shadowRadius: CGFloat = 6) {
        self.layer.masksToBounds = false
        self.layer.cornerRadius = cornerRadius
        self.layer.shadowColor = UIColor.black.cgColor
        self.layer.shadowOffset = shadowOffset
        self.layer.shadowOpacity = shadowOpacity
        self.layer.shadowRadius = shadowRadius
        self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: cornerRadius).cgPath
    }
}

4.4 外部调用示例

// 一行代码添加阴影
cardView.addShadow(cornerRadius: 12)

五、shadowRadius 模糊效果补充

很多开发者忽略 shadowRadius 属性:

  • shadowRadius:阴影模糊半径;

  • 数值越大阴影越柔和、质感越强;

  • 默认值为3,常用范围 4~10。

配合 shadowPath 使用,不会产生任何卡顿。

六、开发避坑注意事项(面试+生产必看)

6.1 性能优化

  • 必须手动赋值 shadowPath,列表Cell不加必卡顿;

  • 阴影不要使用透明背景颜色,会加重渲染计算。

6.2 圆角+阴影共存

  • masksToBounds = true 只能单独做圆角,不能做阴影;

  • 圆角阴影统一使用UIBezierPath 裁切路径

6.3 View 动态适配问题

如果View约束动态变化、自动布局刷新,需要在 layoutSubviews 中重新赋值 shadowPath:

override func layoutSubviews() {
    super.layoutSubviews()
    self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: 12).cgPath
}

6.4 颜色建议

不要使用纯黑阴影,UI生硬不美观,推荐柔和深灰色:

UIColor(red: 0, green: 0, blue: 0, alpha: 0.15).cgColor

七、总结

本文围绕iOS原生阴影渲染机制,讲解了阴影卡顿根源、贝塞尔曲线优化原理,并提供Swift最新工程级封装

核心总结:

  1. shadowPath 是阴影防卡顿核心,列表控件必须加;

  2. 阴影必须设置 masksToBounds = false

  3. 圆角阴影统一使用 UIBezierPath 生成路径;

  4. 动态布局View需在layoutSubviews刷新阴影路径;

  5. 开发中建议封装UIView扩展,一行代码快速生成高质量阴影。

更多推荐