Day 17: swiftUI与Coroutine强强联手,迸出新滋味.

Keyword: swiftUI,Coroutine Scope


改写ObservableObject

既然我们将拉取网路资料的部分下放到了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
    }
}

更新SwiftUI

接下来也来改写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)

>>:  Python 练习

第26天:实作档案上传功能(3)

昨天我们档案上传功能有个问题是不能上传太大的档案,根据我的研究发现,写入档案的部分所需要的时间是不一...

Gulp npm install 中的 --save 与 --save-dev 差异 DAY94

npm install --save (产品用) npm install --save-dev (开...

Day 18 | Frame Animation

逐格动画Frame Animation 最早期的动画制作方式,使用不同的图片连续拨放 先将图片放入专...

前端工程师也能开发全端网页:挑战 30 天用 React 加上 Firebase 打造社群网站|Day18 即时更新留言

连续 30 天不中断每天上传一支教学影片,教你如何用 React 加上 Firebase 打造社群...

Prometheus 与 Spring boot

说到 Prometheus 不得提到监控,Prometheus能帮助我们指标数据采集、指标数据储存、...