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 in
                    Text(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 in
                Text(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() ?? 0
                position.scrollTo(id: id, anchor: .center)
            }
            
            ForEach(1..<100) { index in
                Text(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 in
                Text(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 in
                Text(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() ?? 0
                position.scrollTo(id: id, anchor: .top)
            }
        }
        .onChange(of: position) { old,new in
            print("用代码滚动视图的ID: \(new.viewID)")
            curPosID = new.viewID as? Int
        }
        .onChange(of: curPosID) { _,new in
            print("实时滚动视图的 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 in
                Text(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() ?? 0
                position.scrollTo(id: id, anchor: .top)
            }
        }
        .onChange(of: position) { old,new in
            print("是否由用户拖动引起的滚动:\(new.isPositionedByUser ? "是" : "否")")
        }
    }
}

从运行结果可以看到,只有当我们轻盈的指尖引起滚动时 isPositionedByUser 的值才会为真!
在这里插入图片描述

5. 实时监听滚动视图的内容偏移(ContentOffset)

从上面的讨论可知新的滚动机制能够让我们如虎添翼。不过虽然我们可以从 ScrollPosition 对象中获取到很多与滚动相关的信息,可是有一个滚动中至关重要的数据我们却对它束手无策:那就是滚动中内容视图实时的偏移值(ContentOffset)。

在正常情况下,通过直接访问 ScrollPosition 中的 point 属性将会一无所获:

.onChange(of: position) { old,new in
    print("当前内容滚动偏移:\(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 in
                Text(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() ?? 0
                position.scrollTo(id: id, anchor: .top)
            }
        }
        .onChange(of: position) { old,new in
            print("用代码滚动视图的ID: \(new.viewID)")
            curPosID = new.viewID as? Int
        }
        .onChange(of: curPosID) { _,new in
            print("实时滚动视图的 ID: \(new)")
        }
        .onScrollGeometryChange(for: CGFloat.self, of: {
            geo in
            geo.contentOffset.y
        }, action: { old, new in
            offsetY = new
        })
        .onChange(of: offsetY) { _, new in
            guard 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)等精彩内容。

感谢观赏,再会!😎

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

“一站式企业服务平台”的功能架构

为提升区域营商环境&#xff0c;为促进区域经济发展&#xff0c;实现资源高效配置&#xff0c;全国各区域政府及产业园区都越来越重视如何创新企业服务机制、提升企业服务水平&#xff0c;来保障区域内的企业稳定及帮扶企业高质量的发展。随着近年来大数据、人工智能等新一代信…

多线程环境下 System.out.println 导致死锁问题分析

背景 一个文件采集系统&#xff0c;使用了多线程递归采集指定目录下的文件&#xff0c;并为每个目录创建一个线程去采集。 这个应用每隔几天就出现罢工情况&#xff0c;查看进程还在&#xff0c;堆内存空间还很充足&#xff0c;就是导出堆栈时&#xff0c;发现几乎所有的采集…

Docker:安装RediSearch全文搜索

1、简述 在本文中&#xff0c;我们将介绍如何使用Docker快速、简便地安装RediSearch&#xff0c;Redis的全文搜索模块。RediSearch提供了高效的全文搜索功能&#xff0c;通过Docker安装&#xff0c;可以轻松地在任何环境中部署和管理RediSearch。 官网地址&#xff1a;https:/…

欧洲杯数据控@20240621

点击标题下「蓝色微信名」可快速关注 西班牙成为第二支晋级淘汰赛的球队。 今日积分榜&#xff0c; 今日射手榜&#xff0c; 今日助攻榜&#xff0c; 本届欧洲杯相关文章&#xff0c; 《欧洲杯赛况20240621》 《欧洲杯数据控20240620》 《欧洲杯赛况20240620》 《欧洲杯数据控2…

中霖教育怎么样?中级会计考试大纲哪里能看?

中霖教育怎么样?中级会计考试大纲哪里能看? 在准备中级会计职业资格考试时&#xff0c;考试大纲是至关重要的参考资料。考试大纲能够为考生提供考试覆盖范围和核心考点&#xff0c;从而有助于进行针对性的复习和学习&#xff0c;提高学习的效率。 中级会计考试大纲哪里能看…

大模型项目落地时,该如何估算模型所需GPU算力资源

近期公司有大模型项目落地。在前期沟通时,对于算力估算和采购方案许多小伙伴不太了解,在此对相关的算力估算和选择进行一些总结。 不喜欢过程的可以直接 跳到HF上提供的模型计算器 要估算大模型的所需的显卡算力,首先要了解大模型的参数基础知识。 大模型的规模、参数的理解…

Nature Climate Change | 气候变暖会造成未来全球干旱区面积扩张?

在气候变暖的情况下&#xff0c;旱地通常被预测将在全球范围内扩大&#xff0c;旱地包括以水资源有限、植被稀疏为特征的土地区域。然而&#xff0c;这种预测依赖于旱地的大气代用物&#xff0c;即干旱指数。最近的研究表明&#xff0c;干旱指数对陆地水循环的各种组成部分的预…

Java多线程基础知识-3

ReentrantLock&#xff1a; condition.await()方法之前必须调用lock.lock()代码获取同步监视器。类比&#xff1a; Object类中的wait()相当于Condition类中的await()Object类中wait(long timeout)相当于Condition类中await(long time, TimeUnit unit)方法Object类中notify()相…

看到vue3源码中的__tests__文件中有很多xxx.spec.ts文件,这些文件是干什么的

问: computed.spec.ts这是什么文件 回答: computed.spec.ts 是一个文件命名的示例&#xff0c;通常用于编写和运行针对计算机软件中"computed"模块的测试。在这种情况下&#xff0c;.ts 扩展名表明这是一个 TypeScript 文件&#xff0c;通常用于编写 Angular 或者…

共享购模式的全新解析与收益分析

在当下这个数字化时代&#xff0c;共享购模式以其独特的创新消费理念&#xff0c;为商家和消费者带来了前所未有的增值机会。这一模式不仅整合了商家的资源&#xff0c;还通过一系列机制确保了消费者与商家的共赢。 一、商家参与机制 商家想要加入这一平台&#xff0c;需要支付…

HTML静态网页成品作业(HTML+CSS+JS)——动漫斗罗大陆介绍网页(3个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;使用Javacsript代码实现图片轮播和tab切换&#xff0c;共有3个页面。 …

分流井设备的监测控制和智慧运维

分流井是一种用于将雨水和污水进行分离的设施&#xff0c;通常设置在雨水管和污水管的汇合处。它可以根据不同的情况&#xff0c;将雨水和污水分别排放到不同的管道中&#xff0c;从而实现雨污分流的目的。 以下是一些常见的分流井类型和工作原理&#xff1a; 1、智能分流井&a…

Echarts饼图-实现今日进度-动态图

效果预览 本次实现的是一个饼图&#xff0c;蓝色科技背景色&#xff0c;星球转动效果 进度显示。 构建一个动态饼图&#xff0c;采用ECharts&#xff0c;背景为蓝色科技风&#xff0c;有星球转动效果。通过echarts.init初始化&#xff0c;设置图表尺寸和背景色&#xff0c;配…

高性能并行计算华为云实验二:WordCount算法实验

目录 一、实验目的 二、实验说明 三、实验过程 3.1 创建wordcount源码 3.1.1 实验说明 3.1.2 文件创建 3.2 Makefile文件创建与编译 3.3 主机配置文件建立与运行监测 3.3.1 主机配置文件建立 3.3.2 运行监测 三、实验结果与分析 4.1 实验结果 4.2 结果分析 4.2.1 …

昇思25天学习打卡营第3天|数据集 Dataset

1. 学习内容复盘 集数据 什么是数据集 数据是深度学习的基础&#xff0c;高质量的数据输入将在整个深度神经网络中起到积极作用。MindSpore提供基于Pipeline的数据引擎&#xff0c;通过数据集&#xff08;Dataset&#xff09;和数据变换&#xff08;Transforms&#xff09;实…

el-input-number 限制输入正整数

vue 页面 限制输入最小值为0 :min"0" <el-input-number v-model"scope.row.num" change"handleNumChange(scope)" keydown.enter.prevent style"width: 200px; " :min"0" />methods 里面限制输入的数字不为小数 使…

虚拟机没关机,电脑直接关机导致虚拟机无法使用

虚拟机没关机&#xff0c;电脑直接关机导致虚拟机无法使用 虚拟机未正常关机 无法打开虚拟机&#xff0c;移除 删除虚拟机目录下的该文件夹CentOSXX.vmx.lck&#xff08;或者重新命名&#xff09; 虚拟机正常打开

引擎霸屏推广9招带你驰骋市场战场-华媒舍

在现代市场竞争激烈的环境下&#xff0c;企业如何快速上车&#xff0c;脱颖而出&#xff0c;引擎霸屏推广成为了一种有效的市场推广手段。本文将为您介绍9招带您驰骋市场战场&#xff0c;让您的品牌广告在市场中快速传播。 一、选对渠道 选择合适的渠道是成功的关键。通过市场…

【C++LeetCode】【热题100】字母异位词分组【中等】-不同效率的题解【3】

题目&#xff1a; 暴力方法&#xff1a; class Solution { public:vector<vector<string>> groupAnagrams(vector<string>& strs) {std::unordered_set<std::string> uniqueWord;//单词字符唯一化集合vector<vector<std::string>>…

【CPP】插入排序、希尔排序

目录 1.插入排序1.1直接插入排序简介代码分析 1.2直接插入对比冒泡排序简介代码对比分析(直接插入排序与冒泡的复杂度效率区别) 1.3希尔排序简介代码分析 1.插入排序 基本思想&#xff1a;把一个待排数字按照关键码值插入到一个有序序列中&#xff0c;得到一个新的有序序列。 …