简体   繁体   中英

ReactiveSwift Refresh data

I beginner in ReactiveSwift. This is fetching code in my view model :

private let viewDidLoadProperty = MutableProperty<Void?>(nil)
    public func viewDidLoad() {

        disposables += self.weatherFetcher.fetchCurrentWeather().startWithResult { (result) in
            switch result {
            case .success(let value):
                _ = value?.list?
                    .map { [weak self] weatherData in
                        if let weather = weatherData.weather?.first {
                            self?.weatherFetcher.fetchWeatherImage(icon: weather.icon).startWithResult { (result) in
                                switch result {
                                case .success(let iconData):
                                    self?.cellViewModels.append(WeatherCellViewModel(with: weatherData, iconData: iconData))
                                case .failure(let error):
                                    print("something went wrong - \(error)")
                                }
                            }
                        } else {
                            self?.cellViewModels.append(WeatherCellViewModel(with: weatherData, iconData: nil))
                        }
                }
            case .failure(let error):
                print(error)
            }
        }

        self.viewDidLoadProperty.value = ()
    }

When viewDidLoad is called in ViewController then view model starts fetching data. How to tell VC that fetch is end and refreshData can be called? Is any possibility to catch end of viewDidLoad func, I mean after fetching.

initCode :

init(weatherFetcher: WeatherFetcher) {
    self.weatherFetcher = weatherFetcher
    didStartLoadingWeather = self.viewDidLoadProperty.signal.skipNil()
}

I would first of all advise you on using a ViewModel, that would be in charge of doing these operations in behalf of the UIViewController .

Answering your question directly. You will have to use some sort of mechanism to hold to the data. This can be either a Property or MutableProperty . My advice is for the former. You will also need a trigger, so when viewDidLoad happens, you can communicate this. Assuming you have a ViewModel:

import ReactiveSwift
import enum Result.NoError

public enum ViewState<T> {
    case loading
    case loaded([T])
    case failure(YourError)
}

class ViewModel {

    private let (signal, observer) = Signal<Void, NoError>.pipe()
    let state: Property<ViewState<WeatherCellViewModel>>

    init() {
        let fetch = signal.flatMap(.latest, transform: fetcher)
        self.state = Property.init(initial: .loading then: fetch)
    }

    func fetch() {
        observer.send(value: ())
    }
}

I will leave the completion of the fetch for you. But:

  1. This approach allows you to keep state around (via a property) and allow for an external trigger.
  2. You would now read the values from the state .
  3. the fetcher is created at initialization time and only triggered after fetch function is called.

A good practice for loading table views using ReactiveSwift (if that's your case) is that just simply bind your table view data to a MutableProperty<[your data]>

you just simply fetch your data and when the value is received, reload table view. After that, table view will be magically refreshed.


in your view model:

struct WeatherInfo {
    var temp: Int
    var icon: String
}

var data: MutableProperty<[WeatherInfo]>([])

func fetch() -> SignalProducer<Bool, SomeError> {
    return weatherFetcher.fetchCurrentWeather().on(value: { val in
         let mapped = myCustomMapFunc(val) //transforms 'val' to [WeatherInfo]
         self.data.swap(mapped)
    })
}

in your view controller:

let viewModel = MyViewModel()

func viewDidLoad() {
   viewModel.fetch().startWithCompleted {
        self.tableView.reloadData()
   }
}

public override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
     return viewModel.data.value.count
}

public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

     let cell = self.tableView.dequeueReusableCell(withIdentifier: "myCellId", for:indexPath) as! MyCustomCell
     let info = viewModel.data.value[indexPath.row]
     cell.txtTemp.text = info.temp
     cell.icon = info.icon

     return cell
}

If it's not the table view, just keep the idea and do it on whatever you want. For example, if it's a simple cell, load the cell with new data:

in your view model:

// var data: MutableProperty<WeatherInfo>([]) //you don't need it anymore

    func fetch() -> SignalProducer<WethearInfo, SomeError> {
        return weatherFetcher.fetchCurrentWeather().map({
             return myCustomMapFunc($0) //transforms the input into WeatherInfo
        })
    }

in your view controller:

    let viewModel = MyViewModel()

    func viewDidLoad() {
       viewModel.fetch().startWithValues { val in
            self.reloadMyCell(info: val)
       }
    }

    func reloadMyCell(info: WeatherInfo) {
       mycell.temp = info.temp
       mycell.icon = info.icon
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM