SwiftUI 打造 TikTok 风格的滑动短视频播放器

结合 ScrollView + LazyVStack + AVPlayer,30 行核心代码即可完成「滑到哪儿播到哪儿」的沉浸式体验。


本文参考Yutube上的一个Demo,将原视频的视频播放和暂停逻辑做了改动,利用本地视频,实现类似TikTok的视频秀效果

项目结构速览

文件 作用
TikTokView.swift 顶层容器,负责垂直分页滚动与当前播放状态
TikTokContentView.swift 单条视频卡片,内部持有独立 AVPlayer
LightweightVideoPlayer.swift 极简 UIViewRepresentable,只渲染视频层
Models.swift FeedResponse 数据模型 & 本地假数据
Assets/ mp4 示例视频与自定义字体 TikTokIcons.ttf

1. TikTokView:一屏一视频的分页列表

swift 复制代码
ScrollView(showsIndicators: false) {
    LazyVStack(spacing: 0) {
        ForEach(feedData) { feed in
            TikTokContentView(feed: feed,
                              currentPlayingID: $currentPlayingID)
                .id(feed.id)                 // ✨ 绑定唯一 id
        }
    }
    .scrollTargetLayout()
}
.scrollTargetBehavior(.paging)          // ✨ 分页滚动
.scrollPosition(id: $scrollPosition)    // ✨ 捕获当前可见 id
.onChange(of: scrollPosition) { _, newID in
    currentPlayingID = newID            // 告诉子组件该谁播放
}
  • 分页滚动scrollTargetBehavior(.paging) 让每次滑动都停在整页。
  • 当前页识别scrollPosition(id:) 自动把最居中的 cell 的 id 写入 scrollPosition 绑定。
  • 播放指令下发onChange 将最新 id 存入 currentPlayingID,所有子组件都能感知。

这样就避免了手动计算可见区域,也无需监听 GeometryReader


2. TikTokContentView:每条视频自带一个 AVPlayer

swift 复制代码
@State private var player: AVPlayer
private var isActive: Bool { currentPlayingID == feed.id }

init(feed: FeedResponse,
     currentPlayingID: Binding<String?>) {
    _currentPlayingID = currentPlayingID
    let url = Bundle.main.url(forResource: feed.videoUrl,
                              withExtension: "mp4")!
    _player = State(initialValue: AVPlayer(url: url))
}
  • 初始化阶段即 为每个视频创建独立 AVPlayer ,并缓存在 @State;这样切走再返回时能秒播,且无需全局单例。

播放 & 暂停的核心逻辑

swift 复制代码
private func syncPlayback() {
    if isActive {
        player.play()     // 当前页:播放
    } else {
        player.pause()    // 其它页:暂停
    }
}

.onAppear { syncPlayback() }         // 首次出现
.onChange(of: currentPlayingID) { _ in syncPlayback() }
.onDisappear {                       // 离屏:停+复位
    player.pause()
    player.seek(to: .zero)
}
  1. 出现时 :判断自己是不是当前页,决定是否立刻 play()
  2. 全局 id 变化 :再次比对,若失去焦点则 pause()
  3. 完全离屏:保证下次出现从 0 秒开始。

整个逻辑只依赖一个 currentPlayingID,干净、可测试,无需 KVO 监听滚动偏移。


3. LightweightVideoPlayer:极简 UIKit 封装

swift 复制代码
final class PlayerLayerView: UIView {
    override class var layerClass: AnyClass { AVPlayerLayer.self }
    var playerLayer: AVPlayerLayer { layer as! AVPlayerLayer }
}

struct LightweightVideoPlayer: UIViewRepresentable {
    let player: AVPlayer
    func makeUIView(context: Context) -> PlayerLayerView {
        let v = PlayerLayerView()
        v.playerLayer.videoGravity = .resizeAspectFill   // 铺满
        v.playerLayer.player = player
        return v
    }
    func updateUIView(_ uiView: PlayerLayerView, context: Context) {}
}
  • 只有 15 行 :去掉 AVPlayerViewController 冗余 UI。
  • containerRelativeFrame:在父级强制铺满,全屏显示。

4. 性能小结

优化点 说明
LazyVStack 只实例化可见行,减少内存
独立 AVPlayer 避免频繁换源;复用同一 AVPlayer 反而要处理时许状态机
.scrollTargetBehavior(.paging) 系统级分页,动画跟手且省心
onDisappear 重置 释放解码管线,防止后台继续占用 GPU

5. 可以做得更好的地方

  1. 预加载下一条视频 :滑动前提前 prepare,体验更丝滑。
  2. 远程流媒体 :示例用了本地 mp4,生产可接入 HLS。
  3. 封面 & 首帧:未播放时显示静态封面,减少黑屏闪烁。
  4. 全局静音切换:结合系统音量/静音键。
  5. 点赞动画 :借助 MatchedGeometryEffect 或自定义 CAEmitterLayer

下载 DEMO 源码

点此下载完整 SwiftUI 示例项目

相关推荐
2501_9159090619 小时前
iOS APP 抓包全流程解析,HTTPS 调试、网络协议分析与多工具组合方案
android·ios·小程序·https·uni-app·iphone·webview
2501_9151063219 小时前
游戏上架 App Store 的技术流程解析 从构建到审核的全流程指南
游戏·macos·ios·小程序·uni-app·cocoa·iphone
非专业程序员20 小时前
精读 GitHub - servo 浏览器(一)
前端·ios·rust
2501_916007471 天前
iOS 压力测试的工程化体系,构建高强度、多维度、跨工具协同的真实负载测试流程
android·ios·小程序·uni-app·cocoa·压力测试·iphone
2501_916008891 天前
API接口调试全攻略 Fiddler抓包工具、HTTPS配置与代理设置实战指南
前端·ios·小程序·https·fiddler·uni-app·webview
2501_915921431 天前
iOS 开发者工具推荐,构建从调试到性能优化的多维度生产力工具链(2025 深度工程向)
android·ios·性能优化·小程序·uni-app·iphone·webview
00后程序员张2 天前
全面解析网络抓包工具使用:Wireshark和TCPDUMP教程
网络·ios·小程序·uni-app·wireshark·iphone·tcpdump
游戏开发爱好者82 天前
Mac 抓包软件怎么选?从 HTTPS 调试、TCP 数据流分析到多工具协同的完整抓包方案
tcp/ip·macos·ios·小程序·https·uni-app·iphone
马拉萨的春天2 天前
iOS中广告SDK如何判断一个广告是否真实展示
macos·ios·cocoa
ajassi20002 天前
开源 Objective-C IOS 应用开发(十九)视频的播放
ios·开源·objective-c