app crash when success initialize data MVVM - ios

my apps crash when I try to passing the data into another controller but it was successfully load data in my viewController. it's kinda weird though because the data successfully display in my label and my imageView in my controller, what can I suspect is the data is not yet being load. but if the data not being load why the imageView display my data, here I show you my code.
// This is my schoolListViewModel
class SchoolListViewModel {
private(set) var schoolListViewModel = [SchoolViewModel]()
private let services: ProfileServices
private let schools = UserDefaults.getSelectedSchool()
var count: Int {
return schoolListViewModel.count
}
var id: String {
selectedSchoolID()
}
var imageUrl: String {
selectedSchoolImageURL()
}
var schoolName: String {
selectedSchoolName()
}
var schoolCity: String {
selectedSchoolCity()
}
init(services: ProfileServices) {
self.services = services
}
}
extension SchoolListViewModel {
func loadData(success: #escaping (() -> Void), failure: #escaping ((String) -> Void)) {
services.fetchSchoolList { [weak self] result in
switch result {
case .success(let schools):
self?.schoolListViewModel = schools.compactMap({ SchoolViewModel(school: $0)
})
success()
case .failure(let error):
failure(error.localizedDescription)
}
}
}
}
// this is in my profileViewController
var schoolListViewModel: SchoolListViewModel!
private let profileServices = ProfileServices()
// this is my viewModel in my viewDidLoad
progressHUD.show(in: self.view)
schoolListViewModel = SchoolListViewModel(services: profileServices)
schoolListViewModel.loadData(success: {
self.progressHUD.dismiss(animated: true)
self.populateSchool()
}) { [weak self] error in
self?.showHUDWithError(error)
}
schoolSelectionBtn.addTarget(self, action: #selector(handlePickSchool), for: .touchUpInside)
// This is my objc function
private func showHUDWithError(_ error: String) {
progressHUD.textLabel.text = "Error"
progressHUD.detailTextLabel.text = error
progressHUD.dismiss(afterDelay: 4)
}
#objc private func handlePickSchool() {
let selectSchoolVC = SelectSchoolPopUpVC()
selectSchoolVC.schoolListViewModel = schoolListViewModel
present(selectSchoolVC, animated: true, completion: nil)
}
// this is in my SchoolPopUpVC controller
var schoolListViewModel: SchoolListViewModel!
// this is where the error goes in my schoolPopUpVC tableViewDataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return schoolListViewModel.count
}
this where when I try to passing the data, the error show in my tableView numberOfRowsInSection
unexpected found nil when try to unwrapping optional value

You are using an implicitly unwrapped optional for schoolListViewModel but you don't assign a value until viewDidLoad executes. As it's name implies, that method executes after the view is loaded. You refer to schoolListViewModel in numberOfRowsInSection which will be called after the view is loaded but before viewDidLoad has executed, so you get a crash.
The simple fix is to conditionally unwrap schoolListViewModel in that method:
return schoolListViewModel?.count ?? 0

Related

How to send Json Data to Table View Array? Swift

I've been researching and wrecking my brain attempting to get my JSON data to load into my tableview. I've tried placing the data in a Variable & I'm able to see the data in the console when I print it, however unable to push it to my table view.
Am I doing something wrong on the data page or am I not properly accessing the data within the loop?
I've tried putting the loop in the viewdidload but haven't been successful either.
// ViewController
import Foundation
import UIKit
import SDWebImage
class EntertainmentViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var A = EntertainmentApi()
var data = [EntertainmentPageData]()
var AA = EntertainmentApi().userFeedPosts
#IBOutlet weak var entPostTableView: UITableView!
override func viewDidLoad() {
func showTable() {
}
entPostTableView.register(EntertainmentViewrTableViewCell.nib(), forCellReuseIdentifier: EntertainmentViewrTableViewCell.identifier)
entPostTableView.delegate = self
entPostTableView.dataSource = self
super.viewDidLoad()
DispatchQueue.main.async {
self.entPostTableView.reloadData()
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let customCell1 = tableView.dequeueReusableCell(withIdentifier: EntertainmentViewrTableViewCell.identifier, for: indexPath) as! EntertainmentViewrTableViewCell
customCell1.profileDisplayName.text = AA[indexPath.row].postDisplayName
self.AA.forEach({ (EntertainmentPageData) in
customCell1.configue(with: EntertainmentPageData.postDisplayName, PostImage: EntertainmentPageData.imageURLString, PostDescription: EntertainmentPageData.postDescription)
})
return customCell1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func item(for index: Int) -> EntertainmentPageData {
return data[index]
}
func numberOfItems() -> Int {
return data.count
}
}
//Data
import SwiftUI
import SDWebImage
public protocol EntertainmentPagePostItem {
/// The image for the card.
var imageURLString: String { get }
/// Rating from 0 to 5. If set to nil, rating view will not be displayed for the card.
var postDescription: String? { get }
/// Will be displayed in the title view below the card.
var postDisplayName: String { get }
}
public protocol EntertainmentPagePostDataSource: class {
/// CardSliderItem for the card at given index, counting from the top.
func item(for index: Int) -> EntertainmentPagePostItem
/// Total number of cards.
func numberOfItems() -> Int
}
struct HomePagePost: Codable {
var displayName: String
var cityStatus: String
var displayDescription: String
var displayImageURL: String
var lookingFor: String
var profileImager1: String?
var profileImager2: String?
var profileImager3: String?
var profileImager4: String?
}
struct EntertainmentPageData: Codable {
let postDisplayName: String
let imageURLString: String
let postDescription: String?
}
public class entPostFly: Codable {
let postDisplayName, imageURLString, postDescription: String
}
struct eItem: EntertainmentPagePostItem {
var postDisplayName: String
var imageURLString: String
var postDescription: String?
}
public class EntertainmentApi {
var userFeedPosts = [EntertainmentPageData]()
init() {
load()
}
func load() {
guard let apiURL = URL(string: "https://api.quickques.com/....") else {
return
}
let task: () = URLSession.shared.dataTask(with: apiURL) { Data, apiResponse, error in
guard let Data = Data else { return }
do {
let entPostData = try JSONDecoder().decode([EntertainmentPageData].self, from: Data)
self.userFeedPosts = entPostData
}
catch {
let error = error
print(error.localizedDescription)
}
}.resume()
}
func getFeedPosts(completion: #escaping ([EntertainmentPageData]) -> () ) {
guard let apiURL = URL(string: "https://api.quickques.com/....") else {
return
}
let task: () = URLSession.shared.dataTask(with: apiURL) { Data, apiResponse, error in
guard let Data = Data else { return }
do {
let entPostData = try JSONDecoder().decode([EntertainmentPageData].self, from: Data)
completion(entPostData)
}
catch {
let error = error
print(error.localizedDescription)
}
}.resume()
}
}
class Api {
func getHomePagePosts(completion: #escaping ([HomePagePost]) -> Void ) {
guard let apiURL = URL(string: "https://api.quickques.com/.....") else {
return
}
let task: () = URLSession.shared.dataTask(with: apiURL) { Data, apiResponse, error in
guard let Data = Data else { return }
do {
let homePostData = try JSONDecoder().decode([HomePagePost].self, from: Data)
completion(homePostData)
}
catch {
let error = error
print(error.localizedDescription)
}
}.resume()
}
func getImageData(from url: URL, completion: #escaping (Data?, URLResponse?, Error?) -> ()) {
URLSession.shared.dataTask(with: url, completionHandler: completion).resume()
}
}
func getTopMostViewController() -> UIViewController? {
var topMostViewController = UIApplication.shared.keyWindow?.rootViewController
while let presentedViewController = topMostViewController?.presentedViewController {
topMostViewController = presentedViewController
}
return topMostViewController
}
First you have an empty function showTable inside your viewDidLoad - This does nothing. Presumably it is something hanging around from your various attempts. Delete that.
As you have probably worked out, your network fetch operation is going to occur asynchronously and you need to reload the table view once the data has been fetched.
You have some code in viewDidLoad that kind of tries to do this, but it isn't related to the fetch operation. It is just dispatched asynchronously on the next run loop cycle; This is probably still before the data has been fetched.
However, even if the data has been fetched, it won't show up because you are assigning userFeedPosts from a second instance of your API object to AA at initialisation time. This array is empty and will remain empty since Swift arrays are value types, not reference types. When userFeedPosts is updated, AA will hold the original empty array.
To load the data you need to
Start a load operation when the view loads
Pass a completion handler to that load operation to be invoked when the load is complete
Reload your table view with the new data
class EntertainmentViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var data = [EntertainmentPageData]()
#IBOutlet weak var entPostTableView: UITableView!
override func viewDidLoad() {
entPostTableView.register(EntertainmentViewrTableViewCell.nib(), forCellReuseIdentifier: EntertainmentViewrTableViewCell.identifier)
entPostTableView.delegate = self
entPostTableView.dataSource = self
super.viewDidLoad()
EntertainmentAPI.getFeedPosts { result in
DispatchQueue.main.async { // Ensure UI updates on main queue
switch result {
case .error(let error):
print("There was an error: \(error)")
case .success(let data):
self.data = data
self.entPostTableView.reloadData
}
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let customCell1 = tableView.dequeueReusableCell(withIdentifier: EntertainmentViewrTableViewCell.identifier, for: indexPath) as! EntertainmentViewrTableViewCell
let post = data[indexPath.row)
customCell1.profileDisplayName.text = data[indexPath.row].postDisplayName
customCell1.configure(with: post.postDisplayName, PostImage: post.imageURLString, PostDescription: post.postDescription)
return customCell1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
}
public class EntertainmentAPI {
static func getFeedPosts(completion: #escaping ((Result<[EntertainmentPageData],Error>) -> Void) ) {
guard let apiURL = URL(string: "https://api.quickques.com/....") else {
return
}
let task = URLSession.shared.dataTask(with: apiURL) { data, apiResponse, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else {
/// TODO - Invoke the completion handler with a .failure case
return
}
do {
let entPostData = try JSONDecoder().decode([EntertainmentPageData].self, from: Data)
completion(.success(entPostData))
}
catch {
completion(.failure(error))
}
}.resume()
}
}

tableview not updating current state

Click on cell is opening new ViewController with proper value from each cell
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let singleMovie = movieList[indexPath.row]
getCredits(movieId: singleMovie.id)
var directorName = ""
creditResponse?.crew.forEach({ singleCredit in
if singleCredit.knownForDepartment == .directing{
directorName = singleCredit.name
}
})
let detailVc = DetailViewController(title: singleMovie.title, imageUrl: singleMovie.posterPath,description: singleMovie.overview, groups: checkGroups(groups: singleMovie.genreIds), director: directorName)
navigationController?.pushViewController(detailVc, animated: true)
}
With function below, I am adding value to creditResponse which is CreditsResponse type.
func getCredits(movieId: Int) {
networkManager.getDirector(from: "https://api.themoviedb.org/3/movie/", movieId: movieId) { (creditResponse) in
guard let credit = creditResponse else {
return
}
self.creditResponse = credit
}
}
With function below I am fetching data from URL.
func getDirector(from url: String, movieId: Int, _ completed: #escaping (CreditsResponse?) -> Void){
guard let safeUrl = URL(string: url + "\(String(movieId))/credits" + apiid) else {return}
URLSession.shared.dataTask(with: safeUrl){ data, urlResponse, error in
guard let safeData = data, error == nil, urlResponse != nil else {
completed(nil)
return
}
if let decodedObject: CreditsResponse = SerializationManager().parse(jsonData: safeData){
completed(decodedObject)
}else{
completed(nil)
}
}.resume()
}
Problem is that when I select first cell creditReponse is nil, and after selecting second cell there are value from first cell that I select (it presents always previous value)
Also, when new ViewController is pushed, its content is shown in transition before root controller is moved like in picture below:
Image
Change 1
Add a completion parameter to following.
func getCredits(movieId: Int, completion: #escaping (() -> Void)) {
networkManager.getDirector(from: "https://api.themoviedb.org/3/movie/", movieId: movieId) { (creditResponse) in
guard let credit = creditResponse else {
return
}
self.creditResponse = credit
DispatchQueue.main.async {
completion()
}
}
}
Change 2
Wait for the API call to complete before moving to next screen.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let singleMovie = movieList[indexPath.row]
getCredits(movieId: singleMovie.id, completion: { [weak self] in
guard let self = self else { return }
var directorName = ""
self.creditResponse?.crew.forEach({ singleCredit in
if singleCredit.knownForDepartment == .directing {
directorName = singleCredit.name
}
})
let detailVc = DetailViewController(title: singleMovie.title, imageUrl: singleMovie.posterPath, description: singleMovie.overview, groups: checkGroups(groups: singleMovie.genreIds), director: directorName)
self.navigationController?.pushViewController(detailVc, animated: true)
})
}
The problem is creditResponse gets set, but it gets set too late you are making an async call and then leaving. Local var directorName then gets copied as "" and goes out of scope. When the network call comes back you need to update the detailVC. Consider passing just singleMovie and handling your async calls in detailVC. Keep in mind you can only update the view from the main thread when the async block returns.

Swift - How do I decode json from a REST API

I am trying to make a GET from a REST API in swift. When I use the print statement (print(clubs)) I see the expected response in the proper format. But in the VC is gives me an empty array.
Here is the code to talk to the API
extension ClubAPI {
public enum ClubError: Error {
case unknown(message: String)
}
func getClubs(completion: #escaping ((Result<[Club], ClubError>) -> Void)) {
let baseURL = self.configuration.baseURL
let endPoint = baseURL.appendingPathComponent("/club")
print(endPoint)
API.shared.httpClient.get(endPoint) { (result) in
switch result {
case .success(let response):
let clubs = (try? JSONDecoder().decode([Club].self, from: response.data)) ?? []
print(clubs)
completion(.success(clubs))
case .failure(let error):
completion(.failure(.unknown(message: error.localizedDescription)))
}
}
}
}
and here is the code in the VC
private class ClubViewModel {
#Published private(set) var clubs = [Club]()
#Published private(set) var error: String?
func refresh() {
ClubAPI.shared.getClubs { (result) in
switch result {
case .success(let club):
print("We have \(club.count)")
self.clubs = club
print("we have \(club.count)")
case .failure(let error):
self.error = error.localizedDescription
}
}
}
}
and here is the view controller code (Before the extension)
class ClubViewController: UIViewController {
private var clubs = [Club]()
private var subscriptions = Set<AnyCancellable>()
private lazy var dataSource = makeDataSource()
enum Section {
case main
}
private var errorMessage: String? {
didSet {
}
}
private let viewModel = ClubViewModel()
#IBOutlet private weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.subscriptions = [
self.viewModel.$clubs.assign(to: \.clubs, on: self),
self.viewModel.$error.assign(to: \.errorMessage, on: self)
]
applySnapshot(animatingDifferences: false)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.viewModel.refresh()
}
}
extension ClubViewController {
typealias DataSource = UITableViewDiffableDataSource<Section, Club>
typealias Snapshot = NSDiffableDataSourceSnapshot<Section, Club>
func applySnapshot(animatingDifferences: Bool = true) {
// Create a snapshot object.
var snapshot = Snapshot()
// Add the section
snapshot.appendSections([.main])
// Add the player array
snapshot.appendItems(clubs)
print(clubs.count)
// Tell the dataSource about the latest snapshot so it can update and animate.
dataSource.apply(snapshot, animatingDifferences: animatingDifferences)
}
func makeDataSource() -> DataSource {
let dataSource = DataSource(tableView: tableView) { (tableView, indexPath, club) -> UITableViewCell? in
let cell = tableView.dequeueReusableCell(withIdentifier: "ClubCell", for: indexPath)
let club = self.clubs[indexPath.row]
print("The name is \(club.name)")
cell.textLabel?.text = club.name
return cell
}
return dataSource
}
}
You need to apply a new snapshot to your table view once you have fetched the clubs. Your current subscriber simply assigns a value to clubs and nothing more.
You can use a sink subscriber to assign the new clubs value and then call applySnapshot. You need to ensure that this happens on the main queue, so you can use receive(on:).
self.subscriptions = [
self.viewModel.$clubs.receive(on: RunLoop.main).sink { clubs in
self.clubs = clubs
self.applySnapshot()
},
self.viewModel.$error.assign(to: \.errorMessage, on: self)
]

UICollectionView reloads data without calling reloadData()

I have structured my app using the MVVM pattern, the datasource of the collectionView gets data from the viewModel.
In viewModel I have a closure which gets called after updating the data, it passes IndexSet of sections and [IndexPath] of items which collectionView should insert. However I get the crash everytime after calling the insert method with error:
'Invalid update: invalid number of sections. The number of sections contained in the collection view after the update (11) must be equal to the number of sections contained in the collection view before the update (11), plus or minus the number of sections inserted or deleted (11 inserted, 0 deleted).'
I understand what this error means, however I noticed that this is the order in which the methods are called
1. viewDidLoad()
2. numberOfSections(in collectionView: UICollectionView) // returns 0
3. update?(insertedSectionsSet, insertedRows) // the closure gets called in viewModel, insertedSectionsSet has 11 elements, that's correct
4. numberOfSections(in collectionView: UICollectionView) // returns 11, that's correct but it should be called after next point
5. viewModel.update = { [weak self] sections, items in
DispatchQueue.main.async {
self?.collectionView.performBatchUpdates({
self?.collectionView.insertSections(sections) // crash
self?.collectionView.insertItems(at: items)
}, completion: nil)
}
}
As you can see in the code below, I added prints before calling each methods and you can clearly see that numberOfSections gets called after calling the closure and before performing it. It makes absolutely no sense to me why is it happening. I believe that the cause of crash lies in calling the numberOfSections before inserting them, because then it expects 22 sections after inserting.
update
numberofsections
closure
numberofsections
Code:
class PlaylistsDataSource: NSObject, UICollectionViewDataSource {
...
func numberOfSections(in collectionView: UICollectionView) -> Int {
print("numberofsections")
return viewModel.numberOfSections
}
...
}
class PlaylistsMasterViewController: UIViewController {
...
viewDidLoad() {
viewModel.update = { [weak self] sections, items in
DispatchQueue.main.async {
print("closure")
self?.collectionView.performBatchUpdates({
self?.collectionView.insertSections(sections)
self?.collectionView.insertItems(at: items)
}, completion: nil)
}
}
}
...
}
class PlaylistsMasterViewModel {
private var sectionIndexes = [String]()
private var playlistsDictionary = [String: [AWPlaylist]]()
var update: ((IndexSet, [IndexPath]) -> Void)?
var numberOfSections: Int {
return sectionIndexes.count
}
EDIT: Added more code
extension PlaylistsMasterViewModel {
func fetchPlaylists() {
repo.getAllPlaylists(from: [.iTunes]) { [weak self] result in
switch result {
case .success(let playlists):
self?.sortIncoming(playlists: playlists)
case .failure(let error):
print(error.localizedDescription)
}
}
}
private func sortIncoming(playlists: [AWPlaylist]) {
var insertedPlaylists = [(key: String, list: AWPlaylist)]()
var insertedIndexes = [String]()
func insertNewSection(playlist: AWPlaylist, key: String) {
insertedIndexes.append(key)
playlistsDictionary.updateValue([playlist], forKey: key)
}
func insertNewRow(playlist: AWPlaylist, key: String) {
guard var value = playlistsDictionary[key] else {
print("Oh shit")
return
}
value.append(playlist)
value.sort(by: SortingPredicates.Playlist.nameAscending)
playlistsDictionary.updateValue(value, forKey: key)
insertedPlaylists.append((key, playlist))
}
for list in playlists {
let name = list.localizedName.uppercased().trimmingCharacters(in: CharacterSet.whitespaces)
guard let firstCharacter = name.first else { return }
let firstLetter = String(firstCharacter)
let key: String
if CharacterSet.english.contains(firstLetter.unicodeScalars.first!) {
key = firstLetter
} else if CharacterSet.numbers.contains(firstLetter.unicodeScalars.first!) {
key = "#"
} else {
key = "?"
}
if playlistsDictionary[key] == nil {
insertNewSection(playlist: list, key: key)
} else {
insertNewRow(playlist: list, key: key)
}
}
sectionIndexes.append(contentsOf: insertedIndexes)
sectionIndexes.sort(by: { $0 < $1 })
let insertedSections = insertedIndexes.compactMap { index -> Int? in
guard let sectionIndex = self.sectionIndexes.firstIndex(of: index) else {
return nil
}
return sectionIndex
}
let insertedSectionsSet = IndexSet(insertedSections)
let insertedRows = insertedPlaylists.compactMap { tuple -> IndexPath? in
if let section = self.sectionIndexes.firstIndex(of: tuple.key) {
if let row = self.playlistsDictionary[tuple.key]?.firstIndex(where: { $0.equals(tuple.list) }) {
return IndexPath(row: row, section: section)
}
}
return nil
}
print("update")
update?(insertedSectionsSet, insertedRows)
}

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:
This approach allows you to keep state around (via a property) and allow for an external trigger.
You would now read the values from the state.
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
}

Resources