当前位置: 首页 > news >正文

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)揭秘

概览 在只有方寸之间大小的手持设备上要想体面的向用户展示海量信息&#xff0c;滚动视图&#xff08;ScrollView&#xff09;无疑是绝佳的“东牀之选”。 在 SwiftUI 历史的长河中&#xff0c;总觉得苹果对于 ScrollView 视图功能的升级是在“挤牙膏”。这不&#xff0c;在本…...

阿贝云免费虚拟主机和免费云服务器评测

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

不懂就问,开通小程序地理位置接口有那么难吗?

小程序地理位置接口有什么功能&#xff1f; 若提审后被驳回&#xff0c;理由是“当前提审小程序代码包中地理位置相关接口( chooseAddress、getLocation )暂未开通&#xff0c;建议完成接口开通后或移除接口相关内容后再进行后续版本提审”&#xff0c;那么遇到这种情况&#x…...

Python 全栈系列256 异步任务与队列消息控制(填坑)

说明 每个创新都会伴随着一系列的改变。 在使用celery进行异步任务后&#xff0c;产生的一个问题恰好也是因为异步产生的。 内容 1 问题描述 我有一个队列 stream1, 对应的worker1需要周期性的获取数据&#xff0c;对输入的数据进行模式识别后分流。worker1我设施为10秒运行…...

从零开始的Ollama指南:部署私域大模型

大模型相关目录 大模型&#xff0c;包括部署微调prompt/Agent应用开发、知识库增强、数据库增强、知识图谱增强、自然语言处理、多模态等大模型应用开发内容 从0起步&#xff0c;扬帆起航。 大模型应用向开发路径&#xff1a;AI代理工作流大模型应用开发实用开源项目汇总大模…...

C++类和对象总结

目录 总结 一、引言 二、类的定义 三、对象的创建与初始化 四、访问控制 五、封装 六、继承 七、多态 八、其他特性 九、总结 C类的定义 C对象的创建和初始化 C类的访问控制 总结 一、引言 C是一种面向对象的编程语言&#xff0c;其核心概念是类和对象。类是对现…...

基于PHP的民宿管理系统

有需要请加文章底部Q哦 可远程调试 基于PHP的民宿管理系统 一 介绍 此民宿管理系统基于原生PHP开发&#xff0c;数据库mysql&#xff0c;前端jquery.js和echarts.js。系统角色分为用户和管理员。用户可以在线浏览和预订民宿&#xff0c;管理员登录后台进行相关管理等。(在系统…...

ROS中C++、Python完整的目录结构

文章目录 在ROS中&#xff0c;一个典型的C软件包目录结构通常包括以下几个主要目录&#xff1a; include&#xff1a;该目录包含C头文件&#xff08;.hpp或者.h文件&#xff09;&#xff0c;用于声明类、函数、变量等。通常&#xff0c;这些头文件定义了ROS节点、消息类型、服务…...

Boosting原理代码实现

1&#xff0e;提升方法是将弱学习算法提升为强学习算法的统计学习方法。在分类学习中&#xff0c;提升方法通过反复修改训练数据的权值分布&#xff0c;构建一系列基本分类器&#xff08;弱分类器&#xff09;&#xff0c;并将这些基本分类器线性组合&#xff0c;构成一个强分类…...

【Qt基础教程】事件

文章目录 前言事件简介事件示例总结 前言 在开发复杂的图形用户界面(GUI)应用程序时&#xff0c;理解和掌握事件处理是至关重要的。Qt&#xff0c;作为一个强大的跨平台应用程序开发框架&#xff0c;提供了一套完整的事件处理系统。本教程旨在介绍Qt事件处理的基础知识&#x…...

外星人Alienware m15R7 原厂Windows11系统

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

stata17中java installation not found或java not recognozed的问题

此问题在于stata不知道去哪里找java,因此需要手动的告诉他 方法1&#xff1a; 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&#xff1f; FTP协议是专门针对在两个系统之间传输大的文件这种应用开发出来的&#xff0c;它是TCP/IP协议的一部分。FTP的意思就是文件传输协议&#xff0c;用来管理TCP/IP网络上大型文件的快速传输。FTP早也是在Unix上开发出来的&#xff0c;并且很长一段…...

SEO是什么?SEO相关发展历史

一、SEO是什么意思&#xff1f; SEO&#xff08;Search Engine Optimization&#xff09;&#xff0c;翻译成中文就是“搜索引擎优化”。简单来讲&#xff0c;seo是指自然搜索结果下获得的网站流量的技术&#xff0c;是可以不用花钱就可以让自己的网站有好的排名&#xff0c;也…...

android之WindowManager悬浮框

文章目录 阐述悬浮框的实现AndroidManifest配置使用方法 阐述 Window的类型大致分为三种&#xff1a; Application Window 应用程序窗口、Sub Window 子窗口、System Window 系统窗口 窗口类型图层值&#xff08;type&#xff09;Application Window1&#xff5e;99Sub Windo…...

注解详解系列 - @Scope:定义Bean的作用范围

注解简介 在今天的注解详解系列中&#xff0c;我们将探讨Scope注解。Scope是Spring框架中的一个重要注解&#xff0c;用于定义bean的作用范围。通过Scope注解&#xff0c;可以控制Spring容器中bean的生命周期和实例化方式。 注解定义 Scope注解用于定义Spring bean的作用范围…...

仿中波本振电路的LC振荡器电路实验

手里正好有一套中波收音机套件的中周。用它来测试一下LC振荡器&#xff0c;电路如下&#xff1a; 用的是两只中频放大的中周&#xff0c;初步测试是用的中周自带的瓷管电容&#xff0c;他们应该都是谐振在465k附近。后续测试再更换电容测试。 静态电流&#xff0c;0.5到1mA。下…...

Java 面试题:谈谈 final、finally、 finalize 有什么不同?

在 Java 编程中&#xff0c;final、finally 和 finalize 是三个看似相似但用途截然不同的关键字和方法。理解它们的区别对于编写高质量和健壮的代码至关重要。 final 关键字可用于声明常量、方法和类。用在变量上表示变量不可变&#xff0c;用在方法上表示方法不能被重写&#…...

45、基于深度学习的螃蟹性别分类(matlab)

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

浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)

✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义&#xff08;Task Definition&…...

《Playwright:微软的自动化测试工具详解》

Playwright 简介:声明内容来自网络&#xff0c;将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具&#xff0c;支持 Chrome、Firefox、Safari 等主流浏览器&#xff0c;提供多语言 API&#xff08;Python、JavaScript、Java、.NET&#xff09;。它的特点包括&a…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)

服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序

一、开发准备 ​​环境搭建​​&#xff1a; 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 ​​项目创建​​&#xff1a; File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

Python实现prophet 理论及参数优化

文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候&#xff0c;写过一篇简单实现&#xff0c;后期随着对该模型的深入研究&#xff0c;本次记录涉及到prophet 的公式以及参数调优&#xff0c;从公式可以更直观…...

对WWDC 2025 Keynote 内容的预测

借助我们以往对苹果公司发展路径的深入研究经验&#xff0c;以及大语言模型的分析能力&#xff0c;我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际&#xff0c;我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测&#xff0c;聊作存档。等到明…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)

引言&#xff1a;为什么 Eureka 依然是存量系统的核心&#xff1f; 尽管 Nacos 等新注册中心崛起&#xff0c;但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制&#xff0c;是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

OPENCV形态学基础之二腐蚀

一.腐蚀的原理 (图1) 数学表达式&#xff1a;dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一&#xff0c;腐蚀跟膨胀属于反向操作&#xff0c;膨胀是把图像图像变大&#xff0c;而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”

2025年#高考 将在近日拉开帷幕&#xff0c;#AI 监考一度冲上热搜。当AI深度融入高考&#xff0c;#时间同步 不再是辅助功能&#xff0c;而是决定AI监考系统成败的“生命线”。 AI亮相2025高考&#xff0c;40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕&#xff0c;江西、…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码

目录 一、&#x1f468;‍&#x1f393;网站题目 二、✍️网站描述 三、&#x1f4da;网站介绍 四、&#x1f310;网站效果 五、&#x1fa93; 代码实现 &#x1f9f1;HTML 六、&#x1f947; 如何让学习不再盲目 七、&#x1f381;更多干货 一、&#x1f468;‍&#x1f…...