SwiftUI 6.0(iOS 18)新增的网格渐变色 MeshGradient 解惑

概述
在 SwiftUI 中,我们可以借助渐变色(Gradient)来实现更加灵动多彩的着色效果。从 SwiftUI 6.0 开始,苹果增加了全新的网格渐变色让我们对其有了更自由的定制度。

因为 gif 格式图片自身的显示能力有限,所以上面的动图无法传神的还原实际的美妙效果。强烈建议大家在模拟器或真机上运行本文中的示例代码。
在本篇博文中,您将学到如下内容:
- 概述
- 1. 渐变色的前世今生
- 2. 动画加持,美轮美奂
- 3. 综合运用
- 总结
闲言少叙,让我们马上进入渐变色的世界吧!
Let‘s dive in!!!😉
1. 渐变色的前世今生
在 SwiftUI 中小伙伴们时常会用渐变色(或称为阶梯色)来装扮我们的界面。

在 SwiftUI 1.0(iOS 13)中有 3 种渐变色类型,它们分别是:线性渐变色 LinearGradient、辐射渐变色 RadialGradient、以及角度渐变色 AngularGradient。

关于使用它们对进行任意视图裁剪的进一步介绍,请小伙伴们移步如下链接观赏精彩的内容:
- SwiftUI用Gradient颜色裁剪任意视图
而在 SwiftUI 3.0(iOS 15)中,苹果又添加了一款椭圆渐变色 EllipticalGradient:

为了能够更加轻松的使用单一颜色的渐变色,苹果从 SwiftUI 4.0(iOS 16)开始干脆将其直接“融入”到 Color 的实例中去了:

我们可以这样使用它:
Text("Hello Panda").foregroundStyle(.red.gradient)
在 WWDC 24 中,苹果再接再厉为 SwiftUI 6.0(iOS 18)添加了全新渐变色风格:网格渐变色(MeshGradient ):

别被它的名字所吓到,其实它只是用纵横交错的方格来进一步细粒度控制颜色渐变的自由度,仅此而已。
使用网格渐变色很简单,我们只需创建一个 MeshGradient 实例即可:
MeshGradient(width: 2,height: 2,points: [.init(x: 0, y: 0),.init(x: 1, y: 0), .init(x: 0, y: 1), .init(x: 1, y: 1)],colors: [.red, .green, .blue, .yellow])
如上代码所示:我们创建了一个 2 x 2 网格渐变色,并将其左上角、右上角、左下角、右下角的颜色依次设置为红色、绿色、蓝色以及黄色:

现在我们“静如处子”的网格渐变色貌似略显“呆滞”。别急,通过适当地调整其内部各个网格边框的基准点(或者颜色),我们可以让它行云流水般的“动如脱兔”。
2. 动画加持,美轮美奂
上面说过,要想动画网格渐变色很简单。我们只需使用若干状态来实时的描述 MeshGradient 中每个网格边框的相对位置以及网格内的颜色即可。
首先,我们创建一个 positions 数组来表示每个网格的边框,注意这是一个 3 x 3 网格:
@State var positions: [SIMD2<Float>] = [.init(x: 0, y: 0), .init(x: 0.2, y: 0), .init(x: 1, y: 0),.init(x: 0, y: 0.7), .init(x: 0.1, y: 0.5), .init(x: 1, y: 0.2),.init(x: 0, y: 1), .init(x: 0.9, y: 1), .init(x: 1, y: 1)]
接下来,我们利用定时器连续调整 positions 里所有非顶角网格边框的相对位置。排除顶角网格的原因是:我们不想让整个网格渐变色在顶点被裁剪:

具体实现代码如下所示:
let timer = Timer.publish(every: 1/9, on: .current, in: .common).autoconnect()let colors: [Color] = [.purple, .red, .yellow,.blue, .green, .orange,.indigo, .teal, .cyan
]func randomizePosition(currentPosition: SIMD2<Float>,xRange: (min: Float, max: Float),yRange: (min: Float, max: Float)
) -> SIMD2<Float> {let updateDistance: Float = 0.01let newX = if Bool.random() {min(currentPosition.x + updateDistance, xRange.max)} else {max(currentPosition.x - updateDistance, xRange.min)}let newY = if Bool.random() {min(currentPosition.y + updateDistance, yRange.max)} else {max(currentPosition.y - updateDistance, yRange.min)}return .init(x: newX, y: newY)
}MeshGradient(width: 3,height: 3,points: positions,colors: colors).animation(.bouncy, value: positions).onReceive(timer, perform: { _ inpositions[1] = randomizePosition(currentPosition: positions[1],xRange: (min: 0.2, max: 0.9),yRange: (min: 0, max: 0))positions[3] = randomizePosition(currentPosition: positions[3],xRange: (min: 0, max: 0),yRange: (min: 0.2, max: 0.8))positions[4] = randomizePosition(currentPosition: positions[4],xRange: (min: 0.3, max: 0.8),yRange: (min: 0.3, max: 0.8))positions[5] = randomizePosition(currentPosition: positions[5],xRange: (min: 1, max: 1),yRange: (min: 0.1, max: 0.9))positions[7] = randomizePosition(currentPosition: positions[7],xRange: (min: 0.1, max: 0.9),yRange: (min: 1, max: 1))}).animation(.bouncy, value: positions).ignoresSafeArea()
编译并在 Xcode 预览中运行一见分晓:

再次重申:上面动图“颗粒感”很强是因为 gif 图片本身对颜色限制(最多显示 256 种颜色)的原因,实际效果会相当丝滑顺畅。
现在,我们不但可以恣意描绘静态渐变色,利用些许动画我们还可以让它活灵活现的呈现效果“秾姿故薰欲醉眼,芳信暗传尝苦心”。棒棒哒!💯
想要系统学习最新 Swift 语言如何美美哒的进行苹果开发的小伙伴们,可以到我的《Swift语言开发精讲》专栏来逛一逛哦:

- Swift 语言开发精讲
3. 综合运用
下面是一个将网格渐变色溶入到我们实际应用中的演示代码。在代码中我们做了这样几件事:
- 用不同状态控制不同的动画效果
- 使用 mask 将网格渐变色嵌入到文本视图中
- 扩展 View 以实现更简洁的视图方法
全部源代码在此:
import SwiftUIextension View {@ViewBuilderfunc scaleEffect(_ ratio: CGFloat) -> some View {scaleEffect(x: ratio, y: ratio)}
}struct ContentView: View {@State var bgAnimStart = false@State var shadowAnimStart = false@State var positions: [SIMD2<Float>] = [.init(x: 0, y: 0), .init(x: 0.2, y: 0), .init(x: 1, y: 0),.init(x: 0, y: 0.7), .init(x: 0.1, y: 0.5), .init(x: 1, y: 0.2),.init(x: 0, y: 1), .init(x: 0.9, y: 1), .init(x: 1, y: 1)]let timer = Timer.publish(every: 1/9, on: .current, in: .common).autoconnect()let colors1: [Color] = [.purple, .red, .yellow,.blue, .green, .orange,.indigo, .teal, .cyan]let colors2: [Color] = [.black, .red, .blue,.black, .teal, .blue,.blue, .red, .black]func randomizePosition(currentPosition: SIMD2<Float>,xRange: (min: Float, max: Float),yRange: (min: Float, max: Float)) -> SIMD2<Float> {let updateDistance: Float = 0.01let newX = if Bool.random() {min(currentPosition.x + updateDistance, xRange.max)} else {max(currentPosition.x - updateDistance, xRange.min)}let newY = if Bool.random() {min(currentPosition.y + updateDistance, yRange.max)} else {max(currentPosition.y - updateDistance, yRange.min)}return .init(x: newX, y: newY)}func createMeshGradientView(_ colors: [Color]) -> some View {MeshGradient(width: 3,height: 3,points: positions,colors: colors).animation(.bouncy, value: positions).onReceive(timer, perform: { _ inpositions[1] = randomizePosition(currentPosition: positions[1],xRange: (min: 0.2, max: 0.9),yRange: (min: 0, max: 0))positions[3] = randomizePosition(currentPosition: positions[3],xRange: (min: 0, max: 0),yRange: (min: 0.2, max: 0.8))positions[4] = randomizePosition(currentPosition: positions[4],xRange: (min: 0.3, max: 0.8),yRange: (min: 0.3, max: 0.8))positions[5] = randomizePosition(currentPosition: positions[5],xRange: (min: 1, max: 1),yRange: (min: 0.1, max: 0.9))positions[7] = randomizePosition(currentPosition: positions[7],xRange: (min: 0.1, max: 0.9),yRange: (min: 1, max: 1))})}let text = Text("Hello Panda").font(.system(size: 108, weight: .heavy, design: .rounded)).foregroundStyle(.red.gradient)var body: some View {NavigationStack {ZStack {createMeshGradientView(colors1)//.blur(radius: 30.0).opacity(0.8)text.frame(maxWidth: .infinity, maxHeight: .infinity).opacity(0.01).background {createMeshGradientView(colors2).mask {text.scaleEffect(bgAnimStart ? 1.1 : 1.0).rotationEffect(.degrees(bgAnimStart ? -10 : 0))}.shadow(color: shadowAnimStart ? .green : .black, radius: 10)}}.ignoresSafeArea().navigationTitle("Mesh Gradient 演示").toolbar {Text("大熊猫侯佩 @ \(Text("CSDN").foregroundStyle(.red))").foregroundStyle(.primary.secondary).font(.headline)}}.task {withAnimation(.easeInOut(duration: 0.5).repeatForever(autoreverses: true)) {shadowAnimStart = true}withAnimation(.snappy(duration: 0.66, extraBounce: 15.0).repeatForever(autoreverses: true)) {bgAnimStart = true}}}
}#Preview {ContentView()
}
总结
在本篇博文中,我们讨论了 SwiftUI 6.0(iOS 18)中全新网格渐变色 MeshGradient 的使用,并随后介绍如何利用酷炫的动画升华它的动态效果。
感谢观看,再会啦!😎
相关文章:
SwiftUI 6.0(iOS 18)新增的网格渐变色 MeshGradient 解惑
概述 在 SwiftUI 中,我们可以借助渐变色(Gradient)来实现更加灵动多彩的着色效果。从 SwiftUI 6.0 开始,苹果增加了全新的网格渐变色让我们对其有了更自由的定制度。 因为 gif 格式图片自身的显示能力有限,所以上面的…...
【计算机网络】详谈TCP协议确认应答机制捎带应答机制超市重传机制连接管理机制流量管理机制滑动窗口拥塞控制延迟应答
一、TCP 协议段格式 1.1、4位首部长度 4位首部长度的基本单位是4字节,也就是说如果4位首部长度填6,那报头长度就是24字节。报头长度的取值范围为[0,60]字节,也就是说选项的最大长度为40字节。 二、确认应答机制 发送数据和发送应答&#x…...
rk3566开发之rknn npu 部署
目录 NPU使用 RKNN 模型 非 RKNN 模型 RKNN-Toolkit2工具 RKNN NPU 测试代码如下 main.cc ssd.cc 调用 ssd模型进行目标检测测试 ssd.h qt 中调用 rknn npu 接口 NPU使用 RK3566 内置 NPU 模块。使用该NPU需要下载RKNN SDK,RKNN SDK 为带有 NPU 的 RK3566/RK3568 芯片…...
项目生产经理需要具备哪些技能和素质
一、专业技能 1、技术知识 熟悉项目所涉及的工程领域专业知识,包括施工工艺、技术规范、质量标准等。能够准确理解设计图纸,指导施工人员进行正确的施工操作。掌握工程测量、材料检验、工程试验等基本技能,确保工程质量符合要求。 利用进度猫…...
Java数据类型常量
目录 一、数据类型 1.1分类 1.2关键字&内存占用&范围 1.3包装类 1.4说明 1.5类型转换 1.6类型提升 二、常量 2.1java中的常量 2.2定义常量 2.3分类 一、数据类型 1.1分类 1.2关键字&内存占用&范围 数据类型关键字内存占用范围字节型byte1字节-128…...
如何提高浮点类型计算的精度
把下面这篇文章的表达方式改成像正常的人类作者写的,而不是AI写的。 —————— 如何提高浮点类型计算的精度 在后端开发中,浮点数的计算一直一个常见难题,特别是在需要与GPU协作进行复杂计算时,浮点精度的偏差可能带来预期之…...
RabbitMQ简介及安装类
RabbitMQ概述-MQ介绍 RabbitMQ是一个开源的消息代理和队列服务器,它支持多种消息协议,并且可以轻松地与多种编程语言和框架集成。RabbitMQ是使用Erlang语言编写的,因此它具有高并发和高可用性的特点。以下是RabbitMQ的一些关键特性和概念 消息…...
游戏服务器防御策略:防止玩家因DDoS攻击而掉线
在网络游戏环境中,玩家体验至关重要。然而,DDoS(分布式拒绝服务)攻击是导致玩家在游戏中频繁掉线的一个重要原因。本文将探讨如何通过一系列技术和策略来减轻DDoS攻击的影响,保障玩家的游戏体验。 一、引言 DDoS攻击是…...
Django学习笔记二:数据库操作详解
Django框架提供了一个功能强大的ORM(对象关系映射)系统,使得开发者可以使用Python代码来操作数据库,而无需编写复杂的SQL语句。以下是Django数据库操作的一些基本概念和方法: 模型定义 在Django中,模型是…...
Spring Boot 应用开发案例:在线书籍管理系统
Spring Boot 应用开发案例,涵盖从项目搭建到具体实现,包括常用的数据库操作、Restful API 开发、Spring Security 安全认证以及前后端分离架构。这个案例将以开发一个简单的“在线书籍管理系统”为例,介绍如何从头开始搭建项目、实现增删改查功能,并通过 JWT 进行用户认证。…...
阿里140滑块-滑块验证码逆向分析思路学习
一、声明! 原创文章,请勿转载! 本文内容仅限于安全研究,不公开具体源码。维护网络安全,人人有责。 文章中所有内容仅供学习交流使用,不用于其他任何目的,均已做脱敏处…...
使用 Linux 搭建并配置一个 NFS 服务器
目录 1. 安装相关软件包2. 启动并启用相关服务3. NFS 配置文件3.1 /etc/exports 文件3.2 共享目录3.3 客户端列表3.4 参数选项3.4.1 访问权限选项3.4.2 数据写入硬盘模式3.4.3 root 用户权限3.4.4 子树检查3.4.5 匿名用户3.4.6 安全性(特权端口)3.4.7 网络传输3.4.x 更多 (More…...
python发包
Python 中我们经常会用到第三方的包,默认情况下,用到的第三方工具包基本都是从 Pypi.org 里面下载。这些第三方的包都是开发者们发布的自己的库。我们有自己的想法,或者有一些常用的方法想要分享出去,就可以发布自己的库ÿ…...
农行1面:说说 final,finally,finalize的区别
你好,我是猿java。 在 Java中,“final”、“finally”和“finalize”是三个不同的关键字或方法,尽管它们的名字相似,但在功能和用途上却有显著的区别,这篇文章我们继续分析一篇农行1面的题目:说说 final&a…...
ChatGPT实时语音将于本周向免费用户推出:OpenAI DevDay 2024详细解读
大家好,我是木易,一个持续关注AI领域的互联网技术产品经理,国内Top2本科,美国Top10 CS研究生,MBA。我坚信AI是普通人变强的“外挂”,专注于分享AI全维度知识,包括但不限于AI科普,AI工…...
一个月学会Java 第7天 字符串与键盘输入
Day7 字符串与键盘输入 字符串作为所有程序都很重要的东西,这个东西必须好好的学习,在Java中String会比较好学习,比起C和C里面会更加的简单, 在Java之中也是很好理解的,因为底层已经封装好了char数组,也就是…...
Java后端面试题(day16)
目录 java常见的引用类型java中深拷贝和浅拷贝如何设计一个秒杀系统?谈一下对高并发的理解,平时怎么处理高并发问题?Comparable和Comparator区别?解决hash冲突有哪些方法?Synchronized锁的升级过程 java常见的引用类型 java的引用类型一般分…...
Auto-Animate:是一款零配置、即插即用的动画工具,可以为您的 Web 应用添加流畅的过渡效果
嗨,大家好,我是小华同学,关注我们获得“最新、最全、最优质”开源项目和高效工作学习方法 用户体验成为了检验产品成功与否的关键因素。而动画效果,作为提升用户体验的重要手段,在网页和应用开发中扮演着举足轻重的角色…...
k8s之ingress-nginx-controller安装
作者:程序那点事儿 日期:2024/01/30 01:25 要在master节点上安装 helm repo add ingress-nginx Welcome - Ingress-Nginx Controller helm search repo ingress-nginx helm pull ingress-nginx/ingress-nginx --version 4.4.2 mv ingress-nginx-4.4.…...
力扣SQL仅数据库(1098~1132)
1098 小众书籍 需求 编写解决方案,筛选出过去一年中订单总量 少于 10 本 的 书籍,并且 不考虑 上架距今销售 不满一个月 的书籍 。假设今天是 2019-06-23 。 返回结果表 无顺序要求 。 数据准备 Create table If Not Exists Books (book_id int, nam…...
泰金新能科创板上市:市值79亿 预计第一季净利降幅超45%
雷递网 雷建平 3月31日西安泰金新能科技股份有限公司(简称:“泰金新能”,股票代码:“688813”)今日在上交所上市。泰金新能发行价为26.28元/股,发行4000万股,募资总额为10.51亿元。泰金新能开盘…...
前端开发者的福音:5分钟用Mergely.js给你的网页加个在线文本对比器
零成本打造专业级文本对比工具:Mergely.js全攻略 在代码审查、合同修订或是配置管理场景中,文本差异对比是个高频刚需。传统方案要么需要后端支持,要么功能简陋。现在,只需5分钟和几行JavaScript代码,你就能为Web项目嵌…...
WeClaw_42_Agent工具注册全链路:从BaseTool到意图识别的标准化接入
WeClaw_42_Agent工具注册全链路:从BaseTool到意图识别的标准化接入作者: WeClaw 开发团队 日期: 2026-03-29 版本: v1.0 标签: Agent 工具、BaseTool、意图识别、渐进式暴露、延迟注入📖 摘要 本文系统讲解 WeClaw Agent 工具注册的完整链路。当需要将一…...
自学嵌入式第三天
选择排序 先找再换,每轮先找最小的再做交换每一轮的第一个数作为最小,从第二个数开始比较选出最小值交换到第一个位置。插入排序 一列数从第二个开始作为要插入的数a,逐个向前比较,比a大的数向后移一位,直到遇见比a小…...
开源新形态:从代码到Prompt的转变
【导语:3月末,开源作者yetone发布新项目voice-input-src,以独特方式“开源”,即用自然语言Prompt生成代码,此做法引发讨论,或预示开源模式新转变。】AI驱动的语音输入法开源项目开源作者yetone在GitHub上发…...
探索Univer:构建企业级文档协作系统的全栈框架
探索Univer:构建企业级文档协作系统的全栈框架 【免费下载链接】univer Build AI-native spreadsheets. Univer is a full-stack framework for creating and editing spreadsheets on both web and server. With Univer Platform, Univer Spreadsheets is driven d…...
2025届必备的六大AI辅助写作神器解析与推荐
Ai论文网站排名(开题报告、文献综述、降aigc率、降重综合对比) TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 处于人工智能技术基础之上的智能辅助系统,是可给学术研究者送去高效、规范的开题…...
告别手动调参:Neural MHE如何让无人机在风扰中‘稳如老狗’
Neural MHE:无人机抗风扰控制的智能调参革命 四旋翼无人机在物流配送、农业喷洒、电力巡检等场景的应用日益广泛,但突发的风场扰动始终是飞控系统面临的严峻挑战。传统移动视界估计(MHE)虽能有效处理状态估计问题,却困在手动调参的泥潭中——…...
ESXI系统安装全流程详解:从U盘启动到网络配置
1. 制作ESXI系统U盘启动盘 准备一个容量至少8GB的U盘,建议使用USB3.0接口的高速U盘,这样写入速度会快很多。我实测过,用USB2.0的U盘写入一个ESXI镜像可能需要20分钟,而USB3.0通常5分钟就能搞定。 首先需要下载两个关键文件&#x…...
clusterProfiler进阶指南:如何利用R语言进行多组学数据的功能富集分析与可视化
clusterProfiler进阶指南:如何利用R语言进行多组学数据的功能富集分析与可视化 在生物信息学领域,功能富集分析是将高通量组学数据转化为生物学洞见的关键步骤。作为R/Bioconductor生态中的明星工具,clusterProfiler以其强大的分析能力和丰富…...
