-
Notifications
You must be signed in to change notification settings - Fork 9
/
ContentView.swift
161 lines (130 loc) · 6.35 KB
/
ContentView.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//
// ContentView.swift
// TikTok-Example
//
// Created by shayanbo on 2023/6/29.
//
import SwiftUI
import VideoPlayerContainer
import AVKit
struct ContentView: View {
/// create ViewModel and let it have the same lifecycle with its enclosing underlying view
/// ViewModel holds Context instance for each video data, making them independent each other
@StateObject var viewModel = ViewModel()
func playerView(_ video: VideoData) -> some View {
/// fetch the corresponding context from the viewModel by the video data
/// launch PlaybackService.
/// services in the launch parameter will be created at the beginning, we can do some task in the constructor of service
PlayerWidget(viewModel.findOrCreateContext(video), launch: [PlaybackService.self])
.onAppear {
/// hold the reference to the Context, we need to use it frequently below
let context = viewModel.findOrCreateContext(video)
/// take Portrait as the initial status, so we can see the portrait-related widgets inside the Control overlay
context.status.toPortrait()
/// setup DataService with data, DataService is the data provider, other service can fetch original data from it
context[DataService.self].load(video)
/// make the Control overlay alway presented. not respond to the tap action
context.control.configure(displayStyle: .always)
/// remove default shadow for Portrait's top & bottom
context.control.configure([.portrait(.top), .portrait(.bottom)], shadow: nil)
/// setup insets for the whole Control overlay, since we would like to leave some space at the edges to make it looks better
context.control.configure(.portrait, insets: .init(top: 0, leading: 10, bottom: 0, trailing: 10))
context.control.configure(.portrait(.bottom1)) {[
SeekBarWidget()
]}
/// customize right side layout for portrait status
context.control.configure(.portrait(.right)) { views in
VStack(spacing: 20) {
ForEach(views) { $0 }
}
}
/// widgets for center in portrait status
/// the spacer here is used to separate left and right, since there's a Text at the left side without specifying its width.
/// the left and right side would next to each other
context.control.configure(.portrait(.center)) {[
SpacerWidget(width: 50)
]}
/// widgets for right side in portrait status
context.control.configure(.portrait(.right)) {[
SpacerWidget(),
ProfileWidget(),
LikeWidget(),
MessageWidget(),
FavorWidget(),
ShareWidget(),
AlbumWidget(),
SpacerWidget(height: 0)
]}
/// customize the center layout for portrait
context.control.configure(.portrait(.left)) { views in
VStack(alignment: .leading, spacing: 10) {
ForEach(views) { $0 }
}
}
/// widgets for left side in portrait status
context.control.configure(.portrait(.left)) {[
SpacerWidget(),
TitleWidget(),
DescriptionWidget(),
SpacerWidget(height: 0)
]}
/// setup show/dismiss transition for Portrait's top & bottom
context.control.configure([.portrait(.left), .portrait(.right)], transition: .opacity)
/// make the Control overlay only able to hide or show by calling API
context.control.configure(displayStyle: .custom(animation: .default))
/// make the Control overlay presented
context.control.present()
/// adjust the video render view's content mode to aspectFit
context.render.layer.videoGravity = .resizeAspect
/// we encourage developers to use the simultaneous-related API to add gesture over the whole VideoPlayerContainer
/// here, we add drag gesture to swipe up and down the feeds view
context.gesture.simultaneousDragGesture = viewModel.dragGesture
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
var feeds: some View {
GeometryReader { proxy in
LazyVStack(spacing:0) {
ForEach(viewModel.videos, id: \.self) { video in
playerView(video)
.frame(width: proxy.size.width, height: proxy.size.height)
.offset(CGSize(width: 0, height: viewModel.base + viewModel.offset))
}
}
.onAppear {
viewModel.playCurrentVideo()
}
.onDisappear {
viewModel.pauseCurrentVideo()
}
}
.background(.black)
.clipped()
.ignoresSafeArea(edges: .top)
}
var body: some View {
TabView {
feeds
.tabItem {
Label("Home", systemImage: "house.circle.fill")
}
Text("Second Page")
.tabItem {
Label("Friends", systemImage: "link.circle.fill")
}
Text("Third Page")
.tabItem {
Label("Message", systemImage: "message.circle.fill")
}
Text("Forth Page")
.tabItem {
Label("Profile", systemImage: "person.crop.circle.fill")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}