本文作者:99ANYc3cd6

ios抖音demo

99ANYc3cd6 01-07 21
ios抖音demo摘要: 这个 Demo 不会是 1:1 复刻抖音的所有复杂功能,而是会包含其最核心的 “全屏沉浸式短视频播放” 和 “上下滑动切换” 体验,并辅以一些常见的交互元素,如点赞、评论、分享、用...

这个 Demo 不会是 1:1 复刻抖音的所有复杂功能,而是会包含其最核心的 “全屏沉浸式短视频播放”“上下滑动切换” 体验,并辅以一些常见的交互元素,如点赞、评论、分享、用户信息等。

ios抖音demo
(图片来源网络,侵删)

我们将使用苹果官方推荐的 AVKit 框架来播放视频,并利用 SwiftUI 来构建界面,因为它能让我们用更简洁的代码实现复杂的布局和动画。


Demo 核心功能预览

  1. 全屏视频播放: 视频自动播放,并占据整个屏幕。
  2. 上下滑动切换: 向上滑动,无缝切换到下一个视频;向下滑动,返回上一个视频。
  3. 交互式控件: 底部有播放/暂停按钮,右侧有点赞、评论、分享按钮。
  4. 用户信息: 顶部显示视频作者头像和昵称。
  5. 音乐信息: 显示当前视频使用的背景音乐。
  6. 动画反馈: 点赞按钮有点赞动画和计数变化。

技术选型与架构

  • 语言: Swift
  • UI 框架: SwiftUI (现代、声明式,易于实现动画和复杂布局)
  • 视频播放框架: AVKit (AVPlayerViewController)
  • 数据源: 本地视频文件 + 模拟的 JSON 数据
  • 布局: SwiftUI 的 GeometryReaderoverlay 来实现全屏和控件叠加。

第一步:项目准备

  1. 创建新项目: 在 Xcode 中创建一个新的 "iOS App" 项目,语言选择 Swift,界面选择 SwiftUI。
  2. 准备素材:
    • 视频文件: 准备 3-5 个不同主题的短视频(.mp4 格式),拖拽到 Xcode 项目中,确保它们在 "Copy Bundle Resources" 中。
    • 图片资源: 准备一些用于点赞、评论、分享、用户头像的图标,以及一个“喜欢”的填充图标,同样拖拽到项目中。
    • 数据模拟: 创建一个 Video 模型来存储每个视频的元数据。

第二步:数据模型

创建一个 Swift 文件 Video.swift,定义我们的视频数据结构。

// Video.swift
import Foundation
import AVKit
struct Video: Identifiable, Hashable {
    let id = UUID()
    let url: URL // 视频文件路径
    let user: User
    let music: Music
    var isLiked: Bool = false
}
struct User: Hashable {
    let id = UUID()
    let name: String
    let avatar: String // 图片名
}
struct Music: Hashable {
    let id = UUID()
    let title: String
    let author: String
}
// 模拟数据
let mockVideos: [Video] = [
    Video(
        url: Bundle.main.url(forResource: "video1", withExtension: "mp4")!,
        user: User(name: "旅行家小李", avatar: "avatar1"),
        music: Music(title: "夏日微风", author: "未知艺术家")
    ),
    Video(
        url: Bundle.main.url(forResource: "video2", withExtension: "mp4")!,
        user: User(name: "美食家小王", avatar: "avatar2"),
        music: Music(title: "美味人生", author: "大厨")
    ),
    Video(
        url: Bundle.main.url(forResource: "video3", withExtension: "mp4")!,
        user: User(name: "健身达人Alex", avatar: "avatar3"),
        music: Music(title: "燃爆全场", author: "健身音乐")
    )
]

第三步:主视图 - 全屏视频播放器

这是整个 Demo 的核心,我们将创建一个 VideoPlayerView,它负责管理单个视频的播放逻辑和 UI。

// VideoPlayerView.swift
import SwiftUI
import AVKit
struct VideoPlayerView: View {
    let video: Video
    @Binding var isPlaying: Bool
    @Binding var isLiked: Bool
    @State private var player: AVPlayer?
    @State private var playerItem: AVPlayerItem?
    var body: some View {
        GeometryReader { geometry in
            ZStack {
                // 1. 视频播放器背景
                if let player = player {
                    VideoPlayer(player: player)
                        .onAppear(perform: setupPlayer)
                        .onDisappear(perform: cleanupPlayer)
                        .gesture(
                            // 2. 单击控制播放/暂停
                            TapGesture()
                                .onEnded {
                                    isPlaying.toggle()
                                    isPlaying ? player.play() : player.pause()
                                }
                        )
                        .overlay(
                            // 3. 右侧交互按钮
                            VStack {
                                Spacer()
                                HStack {
                                    Spacer()
                                    // 点赞按钮
                                    LikeButtonView(isLiked: $isLiked)
                                        .padding(.trailing, 30)
                                    // 评论按钮
                                    ActionButtonView(imageName: "comment", text: "2.3w")
                                        .padding(.trailing, 20)
                                    // 分享按钮
                                    ActionButtonView(imageName: "share", text: "分享")
                                        .padding(.trailing, 20)
                                }
                                .padding(.bottom, 100)
                            }
                        )
                } else {
                    // 加载状态
                    Color.black
                        .overlay(ProgressView())
                }
                // 4. 顶部用户信息
                VStack {
                    HStack {
                        Image(video.user.avatar)
                            .resizable()
                            .scaledToFill()
                            .frame(width: 40, height: 40)
                            .clipShape(Circle())
                            .overlay(Circle().stroke(Color.white, lineWidth: 1))
                        Text(video.user.name)
                            .foregroundColor(.white)
                            .font(.headline)
                        Spacer()
                        // 关注按钮
                        Button(action: {
                            // 关注逻辑
                        }) {
                            Text("关注")
                                .font(.subheadline)
                                .fontWeight(.semibold)
                                .padding(.horizontal, 16)
                                .padding(.vertical, 6)
                                .background(Color.red)
                                .foregroundColor(.white)
                                .cornerRadius(20)
                        }
                    }
                    .padding(.horizontal, 16)
                    .padding(.top, 54) // 状态栏高度
                    Spacer()
                }
            }
        }
    }
    private func setupPlayer() {
        playerItem = AVPlayerItem(url: video.url)
        player = AVPlayer(playerItem: playerItem)
        // 自动播放
        player?.play()
        isPlaying = true
        // 循环播放
        NotificationCenter.default.addObserver(
            forName: .AVPlayerItemDidPlayToEndTime,
            object: playerItem,
            queue: .main
        ) { _ in
            player?.seek(to: .zero)
            player?.play()
        }
    }
    private func cleanupPlayer() {
        player?.pause()
        player = nil
        playerItem = nil
        NotificationCenter.default.removeObserver(self)
    }
}
// 点赞按钮组件
struct LikeButtonView: View {
    @Binding var isLiked: Bool
    @State private var animationAmount: Double = 1
    var body: some View {
        Button(action: {
            withAnimation(.spring(response: 0.3, dampingFraction: 0.6)) {
                animationAmount = 1.5
            }
            withAnimation(.spring(response: 0.3, dampingFraction: 0.6).delay(0.1)) {
                animationAmount = 1.0
            }
            isLiked.toggle()
        }) {
            VStack {
                Image(systemName: isLiked ? "heart.fill" : "heart")
                    .font(.system(size: 40))
                    .foregroundColor(isLiked ? .red : .white)
                    .scaleEffect(animationAmount)
                Text(isLiked ? "已喜欢" : "喜欢")
                    .foregroundColor(.white)
                    .font(.caption)
            }
        }
    }
}
// 通用交互按钮组件
struct ActionButtonView: View {
    let imageName: String
    let text: String
    var body: some View {
        VStack {
            Image(systemName: imageName)
                .font(.system(size: 30))
                .foregroundColor(.white)
            Text(text)
                .foregroundColor(.white)
                .font(.caption)
        }
    }
}

第四步:主容器 - 滑动切换

我们需要一个容器来管理 VideoPlayerView,并实现滑动切换的逻辑,我们将使用 TabView 并自定义其 .page 样式。

ios抖音demo
(图片来源网络,侵删)
// ContentView.swift
import SwiftUI
struct ContentView: View {
    // 使用模拟数据
    @State private var videos = mockVideos
    @State private var currentIndex: Int = 0
    var body: some View {
        // TabView 的 page 样式是实现抖音式滑动切换的关键
        TabView(selection: $currentIndex) {
            ForEach(videos.indices, id: \.self) { index in
                VideoPlayerView(
                    video: videos[index],
                    isPlaying: Binding(
                        get: { currentIndex == index && videos[index].isLiked == false }, // 只有当前视频且未点赞时才播放
                        set: { _ in }
                    ),
                    isLiked: $videos[index].isLiked
                )
                .tag(index)
                // 向上滑动手势,用于切换到下一个视频
                .gesture(
                    DragGesture()
                        .onEnded { value in
                            if value.translation.y < -50 { // 向上滑动超过50个点
                                withAnimation {
                                    if currentIndex < videos.count - 1 {
                                        currentIndex += 1
                                    }
                                }
                            }
                        }
                )
            }
        }
        .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) // 隐藏默认的页码指示器
        .background(Color.black) // 设置背景色
    }
}

第五步:运行与测试

  1. 确保素材正确: 在 Xcode 中检查你的视频文件和图片文件是否已正确添加到项目中。
  2. 设置模拟器: 在模拟器上运行 App,最好使用一个屏幕尺寸较大的模拟器(如 iPhone 14 Pro Max)来获得更好的体验。
  3. 测试功能:
    • 自动播放: App 启动后,第一个视频应该自动开始播放。
    • 暂停/播放: 点击屏幕任意位置,视频应该暂停,再次点击,视频应该继续播放。
    • 滑动切换: 在屏幕上从下往上快速滑动,应该能切换到下一个视频,从上往下滑动,则返回上一个视频。
    • 点赞: 点击右侧的爱心图标,它应该变成红色填充,并有放大动画,再次点击,变回空心。
    • UI 元素: 检查顶部的用户头像、昵称和关注按钮是否显示正常。

进阶与扩展思路

这个 Demo 已经有了抖音的核心骨架,你可以基于此进行扩展:

  1. 网络请求: 使用 URLSession 从服务器获取视频列表和元数据,而不是使用本地模拟数据。
  2. 缓存机制: 使用 AVAssetCache 或第三方库(如 Kingfisher)来缓存视频,避免重复下载,提升用户体验。
  3. 无限滚动: 当滑动到最后一个视频时,自动加载更多视频。
  4. 更复杂的 UI:
    • 评论区: 点击评论按钮,可以弹出一个半透明的评论列表视图。
    • 搜索页面: 添加一个搜索页面,可以搜索用户或音乐。
    • 个人主页: 点击用户头像,可以跳转到该用户的个人主页。
  5. 视频拍摄: 使用 AVFoundationAVCaptureSession 来实现拍摄视频的功能。
  6. 音效和滤镜: 在拍摄或播放时添加背景音效和滤镜效果。
  7. 数据持久化: 使用 CoreDataUserDefaults 来保存用户的点赞记录、关注列表等。

这个 Demo 为你提供了一个坚实的起点,它清晰地展示了如何用 SwiftUI 和 AVKit 构建一个类似抖音的沉浸式视频体验。

文章版权及转载声明

作者:99ANYc3cd6本文地址:https://chumoping.net/post/7989.html发布于 01-07
文章转载或复制请以超链接形式并注明出处初梦运营网

阅读
分享