列表页
如上图,列表页很简单,主要就是展示[VedioManager.File]数组,让我们看看怎么用Swift UI方式构建。
首先,创建代表列表页的VedioList:
import SwiftUI
struct VedioList {}
为什么是个空结构体?
因为,这里我们会用到Introducing Container views in SwiftUI这篇文章里面提到的思想,引入容器视图和渲染视图,这样做的好处建议大家详细看文章,最后总结大概如下:
这位作者也是介绍Swift相关教程,风格简洁一些,同样很屌很炸天。
容器视图应该只做与数据流相关的事情:
- 存储视图的状态
- 处理生命周期(onAppear / onDisappear)
- 使用ObservableObject获取数据
- 为“渲染”视图提供操作处理程序
渲染视图应该只执行与渲染相关的事情:
- 使用SwiftUI提供的原始组件构建用户界面。
- 使用其他渲染视图构建用户界面。
- 使用数据作为输入来呈现用户界面,不存储任何状态。
Content View
先来看看渲染视图,主要工作如下:
- 根据传入的File数组创建cell
- 处理左滑删除的响应
按照上面的思想,应该是这样:
extension VedioList {
struct Content: View {
var files: [VedioManager.File]
var delete: (_ offsets: IndexSet) -> Void
var body: some View {
List {
ForEach(files, id: \.self) { file in
self.cell(for: file)
}
.onDelete(perform: delete)
}
}
private func cell(for file: VedioManager.File) -> AnyView {
file.isFolder ?
NavigationLink(file.name, destination: Container(path: file.path)).eraseToAnyView() :
NavigationLink(file.name, destination:VedioPlayer.Container(file: file)).eraseToAnyView()
}
}
}
可以看到,用Swift UI,代码量真的很少,很少,也比较简单。
需要注意的是,当我们点击一个cell的时候,会根据是否是目录,导航到不同界面,这里用到了AnyView。
不过根据文章How to return different view types,还可以使用Group。使用AnyView会降低性能,建议不要经常使用。
Container View
Container视图的主要工作就是
- onAppear时加载并更新指定目录的数据
- 处理删除逻辑
代码也很简单,而且当标记为@State的属性files发生变化时,Content View会自动更新,非常方便!
extension VedioList {
struct Container: View {
let path: String
@State
private var files: [VedioManager.File] = []
var body: some View {
Content(files: files, delete: delete)
.navigationBarTitle(path.lastPathComponent)
.onAppear(perform: loadData)
}
private func loadData() {
print(path)
let files = VedioManager.load(at: path)
DispatchQueue.main.async {
self.files = files
}
}
private func delete(at offsets: IndexSet) {
for idx in offsets {
let file = files[idx]
if VedioManager.delete(file) {
files.remove(at: idx)
}
}
}
}
}
这里有个坑,注意loadData方法里面的self.files = files这句代码,如果不放到DispatchQueue.main.async里,
当点击目录push到新的VedioLis.Containert时,会报table view Invalid update crash。。