SwiftUI 中掌握 ScrollView 的使用:滚动可见性

前言

我们的滚动 API 中又有一个重要的新增功能:滚动可见性。现在,你可以获取可见标识符列表,或者快速检查并监控 ScrollView 内视图的可见性状态。本周,我们将学习如何使用新的 onScrollTargetVisibilityChangeonScrollVisibilityChange 视图修饰符。

视图修饰符

让我们先从 onScrollTargetVisibilityChange 视图修饰符开始。它设计得易于使用,允许你将其附加到具有滚动目标布局的任何 ScrollView 上。让我们通过一个示例来探讨这个修饰符的使用。

swift 复制代码
struct ContentView: View {
    @State private var visible: [Int] = []
    
    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(1..<100, id: \.self) { item in
                    Text(verbatim: item.formatted())
                }
            }
            .scrollTargetLayout()
        }
        .onScrollTargetVisibilityChange(idType: Int.self) { identifiers in
            visible = identifiers
        }
        .onChange(of: visible) {
            print(visible)
        }
    }
}

如上例所示,我们在懒加载栈(LazyVStack)上使用 scrollTargetLayout 视图修饰符,以便允许 ScrollView 针对栈的子视图进行目标识别,而不是针对栈本身。

要了解有关 scrollTargetLayout 视图修饰符的更多信息,请查看我的文章《掌握 SwiftUI 中的 ScrollView:滚动几何》。

应用场景

我们还将 onScrollTargetVisibilityChange 视图修饰符附加到 ScrollView 上,提供标识符类型和操作闭包。在操作闭包内,我们获取可见标识符列表,并可以对可见项执行所需的操作。

有时,视图需要在其可见性状态在 ScrollView 中发生变化时进行响应。对于这些情况,SwiftUI 框架引入了 onScrollVisibilityChange 视图修饰符,你可以将其附加到 ScrollView 内的任何视图上以处理其可见性。

swift 复制代码
struct VideoPlayerView: View {
    let url: URL
    
    @State var player: AVPlayer?
    
    var body: some View {
        VideoPlayer(player: player)
            .task {
                if player == nil {
                    player = AVPlayer(url: url)
                }
            }
            .onScrollVisibilityChange { isVisible in
                if isVisible {
                    player?.play()
                } else {
                    player?.pause()
                }
            }
    }
}

上例定义了 VideoPlayerView 视图,该视图在其可见时自动播放视频内容。正如你所见,我们将 onScrollVisibilityChange 视图修饰符附加到视图本身,并提供一个操作闭包。我们在操作闭包内获得可见性参数,并可以对其变化进行响应。

可见性

onScrollVisibilityChangeonScrollTargetVisibilityChange 修饰符都具有 threshold 参数。threshold 参数允许我们调整需要可见的视口部分的数量,以触发操作闭包。默认情况下,SwiftUI 框架使用 0.5 作为阈值,这意味着至少 50% 的视图需要可见,SwiftUI 才会运行操作。但你可以轻松调整此值。

swift 复制代码
struct VideoPlayerView: View {
    let url: URL
    
    @State var player: AVPlayer?
    
    var body: some View {
        VideoPlayer(player: player)
            .task {
                if player == nil {
                    player = AVPlayer(url: url)
                }
            }
            .onScrollVisibilityChange(threshold: 0.1) { isVisible in
                if isVisible {
                    player?.play()
                } else {
                    player?.pause()
                }
            }
    }
}

在上述示例中,我们定义了阈值,这意味着 SwiftUI 将在视图至少有 10% 可见时运行操作闭包。同样,当视图从可见状态转换为不可见状态,即显示的视口部分少于 10% 时,也会运行该闭包。

完整示例

上面对视图修饰符有了初步了解,它的设计得易于使用,允许你将其附加到具有滚动目标布局的任何 ScrollView 上。让我们通过一个示例来探讨这个修饰符的使用。

示例代码如下:

swift 复制代码
import SwiftUI
import AVKit

struct ContentView: View {
    @State private var visible: [Int] = []
    
    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(1..<100, id: \.self) { item in
                    Text(verbatim: item.formatted())
                        .frame(width: 100, height: 100)
                        .background(item % 2 == 0 ? Color.blue : Color.red)
                        .cornerRadius(10)
                        .padding(5)
                }
            }
            .scrollTargetLayout()
        }
        .onScrollTargetVisibilityChange(idType: Int.self) { identifiers in
            visible = identifiers
        }
        .onChange(of: visible) { newVisible in
            print("Visible items: \(newVisible)")
        }
        .navigationTitle("ScrollView Demo")
    }
}

struct VideoPlayerView: View {
    let url: URL
    
    @State var player: AVPlayer?
    
    var body: some View {
        VideoPlayer(player: player)
            .frame(height: 200)
            .task {
                if player == nil {
                    player = AVPlayer(url: url)
                }
            }
            .onScrollVisibilityChange { isVisible in
                if isVisible {
                    player?.play()
                } else {
                    player?.pause()
                }
            }
    }
}

@main
struct ScrollViewVisibilityApp: App {
    var body: some Scene {
        WindowGroup {
            NavigationView {
                VStack {
                    ContentView()
                    Spacer()
                    VideoPlayerView(url: URL(string: "https://www.example.com/video.mp4")!)
                        .padding()
                }
            }
        }
    }
}

这个示例 Demo 展示了如何使用 onScrollTargetVisibilityChangeonScrollVisibilityChange 视图修饰符来跟踪 ScrollView 中的视图可见性。整个示例分为两个部分:一个是显示带有多个文本视图的 ScrollView,另一个是显示一个视频播放器视图。

ContentView

  1. ScrollView 和 LazyVStack :使用 ScrollView 包裹一个 LazyVStack,在其中放置 1 到 99 的数字。每个数字都显示在一个 Text 视图中,并有不同的背景颜色。
  2. scrollTargetLayout :在 LazyVStack 上应用 scrollTargetLayout 视图修饰符,以允许 ScrollView 针对栈的子视图进行目标识别。
  3. onScrollTargetVisibilityChange :在 ScrollView 上应用 onScrollTargetVisibilityChange 视图修饰符,并提���标识符类型和操作闭包。在操作闭包内,获取可见标识符列表并赋值给 visible 状态变量。
  4. onChange :监听 visible 状态变量的变化,并打印当前可见的项。

VideoPlayerView

  1. VideoPlayer :定义一个视频播放器视图,使用 AVPlayer 播放视频。
  2. task :在 task 修饰符中初始化播放器。
  3. onScrollVisibilityChange :在视频播放器视图上应用 onScrollVisibilityChange 视图修饰符,并提供一个操作闭包。在操作闭包内,根据可见性状态来播放或暂停视频。

ScrollViewVisibilityApp

  1. 主应用入口 :定义主应用入口 ScrollViewVisibilityApp,将 ContentViewVideoPlayerView 组合到一个垂直堆栈中,并通过 NavigationView 进行导航。

运行这个 Demo,你会看到一个带有多个文本视图的 ScrollView,当你滚动时,控制台会打印当前可见的项。此外,在页面底部有一个视频播放器,当视频播放器出现在视口内时,它会自动播放,当其离开视口时,会自动暂停。

总结

今天,我们学习了如何跟踪 ScrollView 内特定视图的可见性,并监控可见标识符列表。示例展示了如何使用 SwiftUI 的滚动可见性修饰符来增强用户体验和交互性。希望能对你有所帮助。

相关推荐
四眼蒙面侠4 小时前
深入 SwiftWork(第 0 篇):用 SwiftUI 构建一个 Agent 可视化工作台
swift·openagentsdk
YJlio5 小时前
10.2.8 以其他账户运行服务(Running services in alternate accounts):为什么“把服务切到某个用户账号下运行”,本质上是在改变服务的整个安全上下文?
python·安全·ios·机器人·django·iphone·7-zip
pop_xiaoli8 小时前
【iOS】KVC与KVO
笔记·macos·ios·objective-c·cocoa
90后的晨仔9 小时前
《swiftUI进阶 第10章:现代状态管理(iOS 17+)》
ios
sakiko_19 小时前
UIKit学习笔记4-使用UITableView制作滚动视图
笔记·学习·ios·swift·uikit
小锋学长生活大爆炸1 天前
【开源软件】这次iPhone也是用上Claw了 | PhoneClaw
ios·开源软件·iphone·claw
SameX1 天前
独立开发一个把走过的路变成 km² 的 App,聊聊 25m 网格和后台 GPS 的坑
ios
XD7429716361 天前
科技早报晚报|2026年4月30日:Agent 安全壳、浏览器 iOS 测试台与可穿戴数据 API,今天更值得看的 3 个技术机会
科技·ios·开源项目·科技新闻·开发者工具
北京自在科技1 天前
Find Hub App 小更新
android·ios·安卓·findmy·airtag
2501_915921431 天前
HTTPS前端劫持 新一代流量劫持解决方案
前端·网络协议·ios·小程序·https·uni-app·iphone