SwiftUI 6.0(iOS 18)ScrollView 全新的滚动位置(ScrollPosition)揭秘
概览
在只有方寸之间大小的手持设备上要想体面的向用户展示海量信息,滚动视图(ScrollView)无疑是绝佳的“东牀之选”。
在 SwiftUI 历史的长河中,总觉得苹果对于 ScrollView 视图功能的升级是在“挤牙膏”。这不,在本届最新 WWDC24 重磅打造的 SwiftUI 6.0 中就让我们来看看 ScrollView 又能挤出怎样的新花样吧?
在本篇博文中,您将学到如下精彩的内容:
- 概览
- 1. SwiftUI 6.0 之前的滚动世界
- 2. SwiftUI 6.0(iOS 18)中全新的 ScrollPosition 类型
- 3. “新老搭配,干活不累”
- 4. 如何判断当前滚动是由用户指尖触发的?
- 5. 实时监听滚动视图的内容偏移(ContentOffset)
- 总结
在 WWDC24 里,苹果对 SwiftUI 6.0 中滚动视图的全新升级无疑解了一众秃头码农们的额燃眉之急。
那还等什么呢?让我们马上开始滚动大冒险吧!
Let‘s rolling!!!😉
1. SwiftUI 6.0 之前的滚动世界
苹果从 SwiftUI 2.0 开始陆续“发力”向 ScrollView 增加了许多新特性,其中包括秃头码农们翘首跂踵的滚动位置读取与设置、滚动模式等高级功能。
在 SwiftUI 6.0 之前,我们是通过单一状态来读取和设置滚动位置的:
struct ContentView: View {@State private var position: Int?var body: some View {ScrollView {LazyVStack {ForEach(0..<100) { index inText(verbatim: index.formatted()).id(index)}}.scrollTargetLayout()}.scrollTargetBehavior(.viewAligned).scrollPosition(id: $position)}
}
如上代码所示:滚动视图中滚动位置其实是由其子视图的 id 值来确定的,我们通过读取和更改 position 状态的值达到了把控滚动位置之目的。
2. SwiftUI 6.0(iOS 18)中全新的 ScrollPosition 类型
而从 SwiftUI 6.0 开始,苹果推出了全新的 ScrollPosition 类型专门由于描述 ScrollView 的滚动位置:
有了 ScrollPosition 坐镇,除了通过视图 id 以外我们还能够以多种方式来表示滚动位置了。比如,以滚动边缘(Edge)来描述和设置滚动视图的位置:
struct ContentView: View {@State private var position = ScrollPosition(edge: .top)var body: some View {ScrollView {Button("Scroll to bottom") {position.scrollTo(edge: .bottom)}ForEach(1..<100) { index inText(verbatim: index.formatted()).id(index)}Button("Scroll to top") {position.scrollTo(edge: .top)}}.scrollPosition($position)}
}
除了使用 position.scrollTo(edge:) 方法滚动到特定的顶部和底部边缘以外,我们还可以一如既往的恣意滚动到任意 id 对应的子视图中去:
struct ContentView: View {@State private var position = ScrollPosition(edge: .top)var body: some View {ScrollView {Button("Random Scroll") {let id = (1..<100).randomElement() ?? 0position.scrollTo(id: id, anchor: .center)}ForEach(1..<100) { index inText(verbatim: index.formatted()).id(index)}}.scrollPosition($position).animation(.default, value: position)}
}
如上代码所示,当用户按下按钮时我们通过 position.scrollTo(id:, anchor:) 方法将视图滚动到了一个随机的位置上。
在 SwiftUI 6.0 中除了按照子视图的 id 滚动以外,我们还可以按照指定的偏移来滚动视图:
struct ContentView: View {@State private var position = ScrollPosition(edge: .top)var body: some View {ScrollView {Button("Scroll to offset") {position.scrollTo(point: CGPoint(x: 0, y: 100))}ForEach(1..<100) { index inText(verbatim: index.formatted()).id(index)}}.scrollPosition($position).animation(.default, value: position)}
}
注意,我们可以分别沿 x 和 y 轴来滚动视图:
Button("Scroll to offset") {position.scrollTo(y: 100)position.scrollTo(x: 200)
}
3. “新老搭配,干活不累”
不过从目前(iOS 18)看来,使用新的 scrollPosition(_⚓️) 视图修改器方法是无法监控到实时滚动位置的。
从下面的示意图中可以验证这一点 —— 只有通过代码设置的滚动位置才能被 scrollPosition(_⚓️) 方法所捕获到:
那么,如果大家希望实时监听滚动的位置又该如何是好呢?
别急,我们可以让新旧两种滚动机制珠联璧合从而达到“双剑合璧,秃头治愈”之神奇功效:
struct ContentView: View {@State private var position = ScrollPosition(edge: .top)@State var curPosID: Int?@State var offsetY: CGFloat?var body: some View {ScrollView {ForEach(1..<100) { index inText(verbatim: index.formatted()).font(.largeTitle.weight(.heavy)).padding().id(index)}.scrollTargetLayout()}.scrollPosition(id: $curPosID).scrollPosition($position).animation(.default, value: position).safeAreaInset(edge: .bottom) {Button("Random Scroll") {let id = (1..<100).randomElement() ?? 0position.scrollTo(id: id, anchor: .top)}}.onChange(of: position) { old,new inprint("用代码滚动视图的ID: \(new.viewID)")curPosID = new.viewID as? Int}.onChange(of: curPosID) { _,new inprint("实时滚动视图的 ID: \(new)")}}
}
代码执行效果如下所示:
4. 如何判断当前滚动是由用户指尖触发的?
有时候我们需要了解:到底是用户实际滑动还是我们的代码引发了滚动。这在 SwiftUI 6.0 之前几乎是不可能的任务。
幸运的是,在 SwiftUI 6.0 中新降临 ScrollPosition 类型就包含一个 isPositionedByUser 属性,我们可以用它来明确滚动视图滚动的原因:
struct ContentView: View {@State private var position = ScrollPosition(edge: .top)var body: some View {ScrollView {ForEach(1..<100) { index inText(verbatim: index.formatted()).font(.largeTitle.weight(.heavy)).padding().id(index)}}.scrollPosition($position).animation(.default, value: position).safeAreaInset(edge: .bottom) {Button("Random Scroll") {let id = (1..<100).randomElement() ?? 0position.scrollTo(id: id, anchor: .top)}}.onChange(of: position) { old,new inprint("是否由用户拖动引起的滚动:\(new.isPositionedByUser ? "是" : "否")")}}
}
从运行结果可以看到,只有当我们轻盈的指尖引起滚动时 isPositionedByUser 的值才会为真!
5. 实时监听滚动视图的内容偏移(ContentOffset)
从上面的讨论可知新的滚动机制能够让我们如虎添翼。不过虽然我们可以从 ScrollPosition 对象中获取到很多与滚动相关的信息,可是有一个滚动中至关重要的数据我们却对它束手无策:那就是滚动中内容视图实时的偏移值(ContentOffset)。
在正常情况下,通过直接访问 ScrollPosition 中的 point 属性将会一无所获:
.onChange(of: position) { old,new inprint("当前内容滚动偏移:\(new.point)")
}
不过别担心,苹果在 SwiftUI 6.0 中又新增了一个 onScrollGeometryChange 修改器方法来专门解决此事:
该方法可以在滚动几何构造发生变化时,执行我们想要的动作。注意它的 transform 闭包会传入一个 ScrollGeometry 类型的参数,我们可以用它来获取任何与滚动几何(Geometry)相关的信息:
现在,使用 onScrollGeometryChange() 修改器方法我们可以游刃有余的在滚动中实时获取滚动的偏移啦:
struct ContentView: View {@State private var position = ScrollPosition(edge: .top)@State var curPosID: Int?@State var offsetY: CGFloat?var body: some View {ScrollView {ForEach(1..<100) { index inText(verbatim: index.formatted()).font(.largeTitle.weight(.heavy)).padding().id(index)}.scrollTargetLayout()}.scrollPosition(id: $curPosID).scrollPosition($position).animation(.default, value: position).safeAreaInset(edge: .bottom) {Button("Random Scroll") {let id = (1..<100).randomElement() ?? 0position.scrollTo(id: id, anchor: .top)}}.onChange(of: position) { old,new inprint("用代码滚动视图的ID: \(new.viewID)")curPosID = new.viewID as? Int}.onChange(of: curPosID) { _,new inprint("实时滚动视图的 ID: \(new)")}.onScrollGeometryChange(for: CGFloat.self, of: {geo ingeo.contentOffset.y}, action: { old, new inoffsetY = new}).onChange(of: offsetY) { _, new inguard let new else { return }print("当前 y 轴滚动偏移:\(new.formatted())")}}
}
最后,我们来看一下执行效果:
可以看到,有了 SwiftUI 6.0 对 iOS 18 和 iPadOS 18 中滚动视图的“重磅升级”,秃头码农们现在终于可以心无旁骛、怡然自得的和 ScrollView 心照神交啦!棒棒哒!
总结
在本篇博文中,我们介绍了 SwiftUI 6.0(iOS/iPadOS 18)中滚动视图(ScrollView)的全新升级,其中包括 ScrollPosition 以及动态获取滚动实时偏移(Content Offset)等精彩内容。
感谢观赏,再会!😎
相关文章:

SwiftUI 6.0(iOS 18)ScrollView 全新的滚动位置(ScrollPosition)揭秘
概览 在只有方寸之间大小的手持设备上要想体面的向用户展示海量信息,滚动视图(ScrollView)无疑是绝佳的“东牀之选”。 在 SwiftUI 历史的长河中,总觉得苹果对于 ScrollView 视图功能的升级是在“挤牙膏”。这不,在本…...

阿贝云免费虚拟主机和免费云服务器评测
阿贝云是一家提供免费虚拟主机和免费云服务器的服务商,为用户提供了一个便捷的搭建网站和应用的平台。他们的服务受到了很多用户的好评。用户可以轻松地在阿贝云上创建自己的网站,并享受免费的虚拟主机和云服务器。通过阿贝云的服务,用户可以…...

不懂就问,开通小程序地理位置接口有那么难吗?
小程序地理位置接口有什么功能? 若提审后被驳回,理由是“当前提审小程序代码包中地理位置相关接口( chooseAddress、getLocation )暂未开通,建议完成接口开通后或移除接口相关内容后再进行后续版本提审”,那么遇到这种情况&#x…...
Python 全栈系列256 异步任务与队列消息控制(填坑)
说明 每个创新都会伴随着一系列的改变。 在使用celery进行异步任务后,产生的一个问题恰好也是因为异步产生的。 内容 1 问题描述 我有一个队列 stream1, 对应的worker1需要周期性的获取数据,对输入的数据进行模式识别后分流。worker1我设施为10秒运行…...

从零开始的Ollama指南:部署私域大模型
大模型相关目录 大模型,包括部署微调prompt/Agent应用开发、知识库增强、数据库增强、知识图谱增强、自然语言处理、多模态等大模型应用开发内容 从0起步,扬帆起航。 大模型应用向开发路径:AI代理工作流大模型应用开发实用开源项目汇总大模…...
C++类和对象总结
目录 总结 一、引言 二、类的定义 三、对象的创建与初始化 四、访问控制 五、封装 六、继承 七、多态 八、其他特性 九、总结 C类的定义 C对象的创建和初始化 C类的访问控制 总结 一、引言 C是一种面向对象的编程语言,其核心概念是类和对象。类是对现…...

基于PHP的民宿管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的民宿管理系统 一 介绍 此民宿管理系统基于原生PHP开发,数据库mysql,前端jquery.js和echarts.js。系统角色分为用户和管理员。用户可以在线浏览和预订民宿,管理员登录后台进行相关管理等。(在系统…...
ROS中C++、Python完整的目录结构
文章目录 在ROS中,一个典型的C软件包目录结构通常包括以下几个主要目录: include:该目录包含C头文件(.hpp或者.h文件),用于声明类、函数、变量等。通常,这些头文件定义了ROS节点、消息类型、服务…...

Boosting原理代码实现
1.提升方法是将弱学习算法提升为强学习算法的统计学习方法。在分类学习中,提升方法通过反复修改训练数据的权值分布,构建一系列基本分类器(弱分类器),并将这些基本分类器线性组合,构成一个强分类…...
【Qt基础教程】事件
文章目录 前言事件简介事件示例总结 前言 在开发复杂的图形用户界面(GUI)应用程序时,理解和掌握事件处理是至关重要的。Qt,作为一个强大的跨平台应用程序开发框架,提供了一套完整的事件处理系统。本教程旨在介绍Qt事件处理的基础知识&#x…...

外星人Alienware m15R7 原厂Windows11系统
装后恢复到您开箱的体验界面,包括所有原机所有驱动AWCC、Mydell、office、mcafee等所有预装软件。 最适合您电脑的系统,经厂家手调试最佳状态,性能与功耗直接拉满,体验最原汁原味的系统。 原厂系统下载网址:http://w…...

stata17中java installation not found或java not recognozed的问题
此问题在于stata不知道去哪里找java,因此需要手动的告诉他 方法1: 1.你得保证已经安装并配置好java环境 2.在stata中输入以下内容并重启stata即可 set java_home "D:\Develope\JDk17" 其中java_home后面的""里面的内容是你的jdk安装路径 我的…...

Harbor本地仓库搭建003_Harbor常见错误解决_以及各功能使用介绍_镜像推送和拉取---分布式云原生部署架构搭建003
首先我们去登录一下harbor,但是可以看到,用户名密码没有错,但是登录不上去 是因为,我们用了负债均衡,nginx会把,负载均衡进行,随机分配,访问的 是harbora,还是harborb机器. loadbalancer中 解决方案,去loadbalance那个机器中,然后 这里就是25机器,我们登录25机器 然后去配置…...

怎样搭建serveru ftp个人服务器
首先说说什么是ftp? FTP协议是专门针对在两个系统之间传输大的文件这种应用开发出来的,它是TCP/IP协议的一部分。FTP的意思就是文件传输协议,用来管理TCP/IP网络上大型文件的快速传输。FTP早也是在Unix上开发出来的,并且很长一段…...

SEO是什么?SEO相关发展历史
一、SEO是什么意思? SEO(Search Engine Optimization),翻译成中文就是“搜索引擎优化”。简单来讲,seo是指自然搜索结果下获得的网站流量的技术,是可以不用花钱就可以让自己的网站有好的排名,也…...
android之WindowManager悬浮框
文章目录 阐述悬浮框的实现AndroidManifest配置使用方法 阐述 Window的类型大致分为三种: Application Window 应用程序窗口、Sub Window 子窗口、System Window 系统窗口 窗口类型图层值(type)Application Window1~99Sub Windo…...
注解详解系列 - @Scope:定义Bean的作用范围
注解简介 在今天的注解详解系列中,我们将探讨Scope注解。Scope是Spring框架中的一个重要注解,用于定义bean的作用范围。通过Scope注解,可以控制Spring容器中bean的生命周期和实例化方式。 注解定义 Scope注解用于定义Spring bean的作用范围…...

仿中波本振电路的LC振荡器电路实验
手里正好有一套中波收音机套件的中周。用它来测试一下LC振荡器,电路如下: 用的是两只中频放大的中周,初步测试是用的中周自带的瓷管电容,他们应该都是谐振在465k附近。后续测试再更换电容测试。 静态电流,0.5到1mA。下…...
Java 面试题:谈谈 final、finally、 finalize 有什么不同?
在 Java 编程中,final、finally 和 finalize 是三个看似相似但用途截然不同的关键字和方法。理解它们的区别对于编写高质量和健壮的代码至关重要。 final 关键字可用于声明常量、方法和类。用在变量上表示变量不可变,用在方法上表示方法不能被重写&#…...

45、基于深度学习的螃蟹性别分类(matlab)
1、基于深度学习的螃蟹性别分类原理及流程 基于深度学习的螃蟹性别分类原理是利用深度学习模型对螃蟹的图像进行训练和识别,从而实现对螃蟹性别的自动分类。整个流程可以分为数据准备、模型构建、模型训练和性别分类四个步骤。 数据准备: 首先需要收集包…...

【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...

1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...

如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈
在日常iOS开发过程中,性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期,开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发,但背后往往隐藏着系统资源调度不当…...

AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机
这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机,因为在使用过程中发现 Airsim 对外部监控相机的描述模糊,而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置,最后在源码示例中找到了,所以感…...

Unity VR/MR开发-VR开发与传统3D开发的差异
视频讲解链接:【XR马斯维】VR/MR开发与传统3D开发的差异【UnityVR/MR开发教程--入门】_哔哩哔哩_bilibili...