Keyword: swiftUI,Coroutine Scope
既然我们将拉取网路资料的部分下放到了shared中的新ViewModel,那们ObservableObject的工作就简单许多,只要提供显示使用的资讯即可.
首先.我们把网路目前的状态利用三个Published通知UI,分别代表DataState中的各数据.读取中,读取到的资料,错误讯息
class CafeItemObservableModel: ObservableObject {
private var viewModel: iOSCafeViewModel? = nil
@Published var loading = false
@Published var cafeList: Array<CafeResponseItem>? = nil
@Published var error: String? = nil
}
然後我们昨天建立iOSCafeViewModel,需要提供一个Interface,让资料可以沟通,我们在viewModel的建构子中提供给他.顺便当有资料来的时候,印出一些讯息.
func activate(){
viewModel = iOSBasicViewModel { [weak self] dataState in
self?.loading = dataState.loading//读取
self?.cafeList = dataState.data//读取到的资料
self?.error = dataState.exception//发生的错误讯息
if let cafeList = dataState.data{
print("size: \(cafeList.count)")//印出收到的值的个数
}
if let errorMessage = dataState.exception{
print("exception: \(errorMessage)")//印出错误内容
}
}
}
最後,在离开页面时,提供一个方法让iOS端呼叫,避免Coroutine Leak的问题发生
func deactivate() {//在页面离开的时候呼叫,避免Leak
viewModel?.onDestroy()
viewModel = nil
}
全部就会像这样
class CafeItemObservableModel: ObservableObject {
private var viewModel: iOSBasicViewModel? = nil
@Published var loading = false
@Published var cafeList: Array<CafeResponseItem>? = nil
@Published var error: String? = nil
func activate(){
viewModel = iOSBasicViewModel { [weak self] dataState in
self?.loading = dataState.loading
self?.cafeList = dataState.data
self?.error = dataState.exception
if let cafeList = dataState.data{
print("size: \(cafeList.count)")
}
if let errorMessage = dataState.exception{
print("exception: \(errorMessage)")
}
}
}
func deactivate() {
viewModel?.onDestroy()
viewModel = nil
}
}
接下来也来改写ContentView,除了使用新的来源外,还能让SwiftUI在读取和呈现时有些特别的效果.
先重写一下每一行的样式,改为使用shared内的ViewModel的数据.
struct CafeRowView : View{
var cafeResponseItem: CafeResponseItem
var body: some View {
HStack {
VStack(alignment: .leading) {
Text(cafeResponseItem.name).font(.headline)
Text(cafeResponseItem.address).font(.subheadline)
}
}
}
}
然後是使用这个样式的ListContent,在发生错误与读取时,这个ListContent会更换显示内容,而正常状态下,就是普通的List
struct CafeListContent : View{
var loading: Bool
var cafeList: Array<CafeResponseItem>?
var error: String?
var body: some View {
ZStack {
VStack {
if let cafeList = cafeList {//正常收到,使用建立的RowView
List(cafeList, id: \.self){cafe in
CafeRowView(cafeResponseItem:cafe)
}
}
if let error = error {//发生错误,显示错误讯息
Text(error)
.foregroundColor(.red)
}
}
if (loading) {//读取中,显示Loading的文字
Text("Loading...")
}
}
}
}
最後最外层的View,没有实际的画面,但是负责让数据跟observableModel绑定,以及在View出现时进行读取,View消失时取消Coroutine避免Leak.
struct CafeListScreen :View {
@ObservedObject var observableModel = CafeItemObservableModel()
var body: some View{
CafeListContent(//让CafeListContent的值与observableModel内的数据绑定
loading: observableModel.loading,
cafeList: observableModel.cafeList,
error: observableModel.error
)
.onAppear(perform: {//画面出现时,开始读取
observableModel.activate()
})
.onDisappear(perform: {//画面消失时,停止读取
observableModel.deactivate()
})
}
}
最後让Content View改成这个新的CafeListScreen就可以啦!如果档名不同记得去iOSApp档案修改,就在ContentView的同路径下.
可以注意到虽然不像CoroutineScope或是Android的LifecycleObserver强制把起点终点都订好,但是ContentView还是提供了一个接近的onAppear与onDisappear来管理View的生命周期.但是有个缺点,没有强制性,所以新人仍然有可能会漏掉onDisappear,造成Leak
今天就到这里,明天我们会回到Kotlin本身.我们会使用Koin进行注入管理.
<<: GitHub Advanced Security - 秘密扫描 (Secret Scanning)
昨天我们档案上传功能有个问题是不能上传太大的档案,根据我的研究发现,写入档案的部分所需要的时间是不一...
npm install --save (产品用) npm install --save-dev (开...
逐格动画Frame Animation 最早期的动画制作方式,使用不同的图片连续拨放 先将图片放入专...
连续 30 天不中断每天上传一支教学影片,教你如何用 React 加上 Firebase 打造社群...
说到 Prometheus 不得提到监控,Prometheus能帮助我们指标数据采集、指标数据储存、...