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)等精彩内容。

感谢观赏,再会!😎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/857411.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

spire.Pdf 将pdf转成image

一、nuget安装 <ItemGroup><PackageReference Include"Spire.PDF" Version"10.6.7" /></ItemGroup> 二、直接上代码 using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using System; using System.IO;namespace …

乱弹篇(35)掩耳盗铃与两三十年

成语“ 掩耳盗铃 ”&#xff0c;比喻自己欺骗自己&#xff0c;明明是掩盖不住的事情偏要想法子掩盖。它多用来讽刺那些做事不想让别人知道&#xff0c;却偏偏又引起他人注意的人。 现在网络上以新浪微博和邪恶的“800727”为典型的自媒体平台和其掌控者&#xff0c;就是现代版…

提升研发效率:三品PLM解决方案在汽车汽配行业的实践

随着全球汽车市场的快速发展&#xff0c;中国汽车汽配行业迎来了前所未有的发展机遇。然而&#xff0c;在这一过程中&#xff0c;企业也面临着诸多挑战&#xff0c;如研发能力的提升、技术资料管理的复杂性、以及跨部门协作的困难等。为了应对这些挑战&#xff0c;三品产品生命…

模式分解算法-满足3NF的无损且保持函数依赖的分解算法、满足BCNF的无损连接分解算法

一、引言 1、对指定的关系模式&#xff0c;若范式级别较低&#xff0c;为第一范式或第二范式&#xff0c;由于存在数据冗余或更新异常问题&#xff0c;在实际中一般是不可用的&#xff0c;关系模式的规范化就是将满足低一级的关系模式分解为若干满足高一级范式的关系模式的集合…

Python轻松设置Excel单元格数字显示格式

Excel作为强大的数据处理与分析工具&#xff0c;不仅能够存储大量数据&#xff0c;还支持复杂的数据处理与可视化功能。而如何恰当地展示Excel表格中的数据是Excel文件制作的关键之一。这便涉及到Excel单元格数字格式的设置。数字格式不仅关乎数据的美学呈现&#xff0c;如货币…

聊聊 C# dynamic 类型,并分享一个将 dynamic 类型变量转为其它类型的技巧和实例

前言 dynamic 是一种有别于传统变量类型的动态类型声明&#xff0c;刚开始接触可能在理解上会有些困难&#xff0c;可以简单地把它理解为一个盲盒&#xff0c;你可以任意猜测盒子有什么东西&#xff0c;并认为这些东西真正存在而进行处理&#xff0c;等到真正打开时&#xff0…

网上的流量卡真的可以免费领取吗?

网上的流量卡真的可以免费领取吗&#xff1f;当然可以&#xff0c;目前运营商推出的流量卡都是可以免费领取的。 有很多朋友私信给小编&#xff0c;听说流量卡是免费领取的就觉得不太靠谱&#xff0c;其实这种想法是不对的&#xff0c;首先大家要换位思考一下&#xff0c;如果我…

Ubuntu配置ssh+vnc(完整版)

Ubuntu配置sshvnc&#xff08;完整版&#xff09; 1 配置ssh 1. 安装openssh-server&#xff0c;配置开机自启 # 更新包 sudo apt-get update # 安装openssh-server sudo apt-get install -y openssh-server # 启动服务 sudo service ssh start # 配置开机自启 sudo systemc…

细说MCU定时器模块的输入捕捉功能的实现方法

目录 一、工程背景 二、建立工程 1、配置GPIO 2、选择时钟源和Debug 3、 配置定时器TIM1 4、 配置定时器TIM13 5、配置串口 6、配置中断 7、配置系统时钟 三、代码修改 1、使能TIM1输入捕捉功能和TIM3的PWM输出功能 2、自定义变量 3、重定义回调函数 4、输出到…

【国际化I18n使用方法】vue2使用i18简单实现多语种切换,刷新保持,动态数据处理

效果图 使用流程 总结就是&#xff0c;安装好插件后&#xff0c;配置几个语言的js文件&#xff0c;每个词都要在每个js内写一遍对应的语言&#xff0c;然后通过切换js文件拿到对应的语言&#xff0c;实现翻译的效果。然后当前使用什么语言保存到本地&#xff0c;这样刷新就可以…

【进阶篇-Day4:使用JAVA编写石头迷阵游戏】

目录 1、绘制界面2、打乱石头方块3、移动业务4、游戏判定胜利5、统计步数6、重新游戏7、完整代码&#xff1a; 1、绘制界面 上述思路是&#xff1a;使用一个二维数组存放图片的编号&#xff0c;然后在后持遍历即可获取对应的图片。 代码如下&#xff1a; package com.itheima.s…

取代煤气灶,新能源电燃灶真有那么牛吗

在当今社会&#xff0c;能源问题日益凸显&#xff0c;能源危机的警钟不断敲响。与此同时&#xff0c;人们对于生活品质和安全环保的要求也越来越高。在这样的背景下&#xff0c;一种号称能取代燃气灶和电磁炉的新能源电燃灶——华火电燃灶进入了人们的视野。 华火电燃灶的出现似…

SpringBoot整合justauth实现多种方式的第三方登陆

目录 0.准备工作 1.引入依赖 2.yml文件 3. Controller代码 4.效果 参考 0.准备工作 你需要获取三方登陆的client-id和client-secret 以github为例 申请地址&#xff1a;Sign in to GitHub GitHub 1.引入依赖 <?xml version"1.0" encoding"UTF-8&quo…

【SCAU数据挖掘】数据挖掘期末总复习题库简答题及解析——下

1.从某超市顾客中随机抽取5名&#xff0c;他们的购物篮数据的二元0/1表示如下&#xff1a; 顾客号 面包 牛奶 尿布 啤酒 鸡蛋 可乐 1 1 1 0 0 0 0 2 1 0 1 1 1 0 3 0 1 1 1 0 1 4 1 1 1 1 0 0 5 1 1 1 0 0 1 某学生依据这些数据做…

Linux常用命令(16)—awk命令(有相关截图)

写在前面&#xff1a; 最近在学习Linux命令&#xff0c;记录一下学习Linux常用命令的过程&#xff0c;方便以后复习。仅供参考&#xff0c;若有不当的地方&#xff0c;恳请指正。如果对你有帮助&#xff0c;欢迎点赞&#xff0c;关注&#xff0c;收藏&#xff0c;评论&#xf…

Java 10新特性介绍

Java 10是Java平台的一个重要更新&#xff0c;它引入了多项新特性和改进。以下是一些主要的新特性&#xff1a; 局部变量类型推断&#xff08;var关键字&#xff09; Java 10允许使用var关键字来声明局部变量&#xff0c;而无需显式指定变量的类型。编译器会根据变量赋值的上下…

Android获取控件宽高的几种方式

第一种方式&#xff1a;在需要时获取&#xff0c;如控件点击时再获取 button.setOnClickListener(new View.OnClickListener() { Override public void onClick(View v) { getTextWidthAndHeight(); } }); 第二种方式&#xff1a;重写onWindowFocusChanged()方法 Overr…

【机器学习】机器学习赋能交通出行:智能化实践与创新应用探索

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀目录 &#x1f4d2;1. 引言&#x1f4d9;2. 交通流量预测与优化&#x1f31e;数据准备&#x1f319;模型训练与预测⭐评估模型与优化 &#x…

jsonpath_解析例子代码

# _*_ coding : utf-8 _*_ # Time : 2023-11-05 13:23 # Author : haowen # File : jsonpath_解析_淘票票 # Project : py练习 import urllib.request url https://dianying.taobao.com/cityAction.json?activityId&_ksTS1699161894273_112&jsoncallbackjsonp113&…

网页抓取单词关联

在当今数字化的时代&#xff0c;数据的获取与处理成为了众多企业和开发者关注的焦点。API 服务的出现&#xff0c;为我们打开了高效、便捷获取和利用数据的新大门。接下来&#xff0c;为您介绍几款独具特色的 API 服务&#xff0c;它们将为您的业务和开发工作带来前所未有的便利…