Swift Tableviewcell linkage after API communication - ios

I am importing the school information API and connecting it to the table view cell.
API communicates. But I don't know how to float it in the cell. I am using RxSwift.
I am passing data through protocol. Floating in cells doesn't work very well.
Here is ViewModel
import UIKit
import Moya
import RxSwift
import RxCocoa
protocol SchoolInfoProtocol: AnyObject {
var schoolData: PublishSubject<[SchoolInfo]> { get set }
}
class SchoolNameViewModel: BaseViewModel {
weak var delegate: SchoolInfoProtocol?
func fetchSchoolName(schoolName: String) {
let provider = MoyaProvider<SchoolNameAPI>()
provider.request(.schools(schoolName: schoolName, apiKey: "::")) { (result) in
switch result {
case .success(let response):
let responseData = response.data
do {
let decoded = try JSONDecoder().decode(Welcome.self, from: responseData ).schoolInfo
self.delegate?.schoolData.onNext(decoded)
print(decoded)
} catch {
print(error.localizedDescription)
}
case .failure(let error):
print(error.localizedDescription)
print("")
}
}
}
}
Here tableViewCell
import UIKit
class SchoolNameTableViewCell: UITableViewCell{
static let cellId = "SchoolNameTableViewCell"
private let schoolNameLabel = UILabel().then {
$0.text = "정암초"
$0.font = .systemFont(ofSize: 14, weight: .semibold)
}
private let schoolAddressLabel = UILabel().then {
$0.text = "첨단 120-141"
$0.font = .systemFont(ofSize: 12, weight: .medium)
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.layer.cornerRadius = 10
contentView.backgroundColor = .red
self.selectionStyle = .none
addView()
setLayout()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
contentView.frame = contentView.frame.inset(by: UIEdgeInsets(top: 0, left: 0, bottom: 16, right: 0))
}
private func addView() {
contentView.addSubViews(schoolNameLabel, schoolAddressLabel)
}
private func setLayout() {
schoolNameLabel.snp.makeConstraints {
$0.centerY.equalToSuperview()
$0.leading.equalToSuperview().offset(30)
}
schoolAddressLabel.snp.makeConstraints {
$0.top.equalTo(schoolNameLabel.snp.bottom).offset(5)
$0.trailing.equalToSuperview()
$0.centerY.equalToSuperview()
}
}
func changeCellData(with model: [Row]) {
DispatchQueue.main.async {
self.schoolNameLabel.text = model[3].schulNm
self.schoolAddressLabel.text = model[3].orgRdnma
}
}
}
Here ViewController
In this file I am binding to the tableview
addview and setLayout are done
import UIKit
import Lottie
import RxSwift
import RxCocoa
class SchoolNameViewController: BaseVC<SchoolNameViewModel>, SchoolInfoProtocol {
private let disposeBag = DisposeBag()
var schoolData = PublishSubject<[SchoolInfo]>()
private let schoolNameTableView = UITableView().then {
$0.register(SchoolNameTableViewCell.self, forCellReuseIdentifier: SchoolNameTableViewCell.cellId)
$0.backgroundColor = .blue
}
private func bindTableView() {
schoolData.bind(to: schoolNameTableView.rx.items(cellIdentifier: SchoolNameTableViewCell.cellId, cellType: SchoolNameTableViewCell.self)) { (row, data, cell) in
cell.changeCellData(with: data.row ?? .init())
}.disposed(by: disposeBag)
}
private func fetchSchoolData() {
viewModel.fetchSchoolName(schoolName: "첨단")
}
override func configureVC() {
schoolNameTextField.delegate = self
fetchSchoolData()
bindTableView()
}
here APIModel
import Foundation
// MARK: - Welcome
struct Welcome: Codable {
let schoolInfo: [SchoolInfo]
}
// MARK: - SchoolInfo
struct SchoolInfo: Codable {
let head: [Head]?
let row: [Row]?
}
struct Head: Codable {
let listTotalCount: Int?
let result: Result?
enum CodingKeys: String, CodingKey {
case listTotalCount = "list_total_count"
case result = "RESULT"
}
}
// MARK: - Result
struct Result: Codable {
let code, message: String
enum CodingKeys: String, CodingKey {
case code = "CODE"
case message = "MESSAGE"
}
}
// MARK: - Row
struct Row: Codable {
let atptOfcdcScCode, sdSchulCode, schulNm: String
let engSchulNm, juOrgNm: String
let fondScNm, orgRdnzc, orgRdnma, orgRdnda: String
enum CodingKeys: String, CodingKey {
case atptOfcdcScCode = "ATPT_OFCDC_SC_CODE"
case sdSchulCode = "SD_SCHUL_CODE"
case schulNm = "SCHUL_NM"
case engSchulNm = "ENG_SCHUL_NM"
case juOrgNm = "JU_ORG_NM"
case fondScNm = "FOND_SC_NM"
case orgRdnzc = "ORG_RDNZC"
case orgRdnma = "ORG_RDNMA"
case orgRdnda = "ORG_RDNDA"
}
}
I want to connect to tableView Cell

Related

How can I display the json data in a Swift SpreadsheetView

I am using a pod called SpreadsheetView to show data, from a json, in a grid but I don't know how to show them because with this pod it is necessary to invoke an array for each column.
I would like to order this data from my json in the corresponding columns and also when touching a cell in a row I was taken to a view to show the related data.
Attached the code of what I have done.
The view that I use to display the data
import UIKit
import SpreadsheetView
class TiendasViewController: UIViewController, SpreadsheetViewDataSource, SpreadsheetViewDelegate, ConsultaModeloProtocol{
let headers = ["Sucursal", "Venta total", "Tickets", "Piezas", "Pzs/Ticket", "Ticket prom.", "Utilidad", "Última venta"]
var feedItems = [DetallesConsulta]()
func itemConsulta(LaConsulta: [DetallesConsulta]) {
feedItems = LaConsulta
self.tablaTiendas.reloadData()
}
var selectDato : DetallesConsulta = DetallesConsulta()
private let tablaTiendas = SpreadsheetView()
override func viewDidLoad() {
super.viewDidLoad()
let consultaModelo = ConsultaModelo()
consultaModelo.ElDelegado = self
consultaModelo.downloadConsulta()
tablaTiendas.dataSource = self
tablaTiendas.delegate = self
tablaTiendas.contentInset = UIEdgeInsets(top: 4, left: 0, bottom: 4, right: 0)
tablaTiendas.intercellSpacing = CGSize(width: 4, height: 1)
tablaTiendas.gridStyle = .none
tablaTiendas.gridStyle = .solid(width: 1, color: .blue)
tablaTiendas.register(SucursalesCell.self, forCellWithReuseIdentifier: String(describing: SucursalesCell.self))
tablaTiendas.register(DateCell.self, forCellWithReuseIdentifier: String(describing: DateCell.self))
view.addSubview(tablaTiendas)
print("Imprimiendo los feeditems: ", feedItems)
// Do any additional setup after loading the view.
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tablaTiendas.frame = CGRect(x: 0, y:216, width: view.frame.size.width, height: view.frame.size.height-100)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
tablaTiendas.flashScrollIndicators()
}
func spreadsheetView(_ spreadsheetView: SpreadsheetView, cellForItemAt indexPath: IndexPath) -> Cell? {
if case (0...(headers.count), 0) = (indexPath.column, indexPath.row) {
let cell = spreadsheetView.dequeueReusableCell(withReuseIdentifier: String(describing: DateCell.self), for: indexPath) as! DateCell
cell.label.text = headers[indexPath.column - 0]
return cell
} else if case(0, 1...(sucursales.count + 1)) = (indexPath.column, indexPath.row){
let cell = spreadsheetView.dequeueReusableCell(withReuseIdentifier: String(describing: SucursalesCell.self), for: indexPath) as! SucursalesCell
cell.label.text = sucursales[indexPath.row - 1]
return cell
}
/*
let cell = tablaTiendas.dequeueReusableCell(withReuseIdentifier: MyLabelCell.identifier, for: indexPath) as! MyLabelCell
if indexPath.row == 0 {
cell.setup(with: headers[indexPath.column])
cell.backgroundColor = .systemBlue
}
return cell
*/
return nil
}
func numberOfColumns(in spreadsheetView: SpreadsheetView) -> Int {
return headers.count
}
func numberOfRows(in spreadsheetView: SpreadsheetView) -> Int {
return 1 + sucursales.count
}
func spreadsheetView(_ spreadsheetView: SpreadsheetView, widthForColumn column: Int) -> CGFloat {
return 200
}
func spreadsheetView(_ spreadsheetView: SpreadsheetView, heightForRow row: Int) -> CGFloat {
if case 0 = row{
return 24
}else{
return 55
}
}
func frozenColumns(in spreadsheetView: SpreadsheetView) -> Int {
return 1
}
}
class MyLabelCell: Cell {
private let label = UILabel()
public func setup(with text: String){
label.text = text
label.textAlignment = .center
contentView.addSubview(label)
}
override func layoutSubviews() {
super.layoutSubviews()
label.frame = contentView.bounds
}
}
class DateCell: Cell {
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
label.frame = bounds
label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
label.font = UIFont.boldSystemFont(ofSize: 15)
label.textAlignment = .center
contentView.addSubview(label)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
class SucursalesCell: Cell {
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
label.frame = bounds
label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
label.font = UIFont.monospacedDigitSystemFont(ofSize: 12, weight: UIFont.Weight.medium)
label.textAlignment = .center
contentView.addSubview(label)
}
override var frame: CGRect {
didSet {
label.frame = bounds.insetBy(dx: 6, dy: 0)
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
The model to download the json
import UIKit
import Foundation
protocol ConsultaModeloProtocol: AnyObject {
func itemConsulta (LaConsulta: [DetallesConsulta])
}
var fechaPresente: String = ""
var fechaPasada: String = ""
let elToken : String = UserDefaults.standard.string(forKey: "token")!
let helper = Helper()
class ConsultaModelo: NSObject {
weak var ElDelegado : ConsultaModeloProtocol!
let URLPath = helper.host+"tiendas"
func downloadConsulta(){
var request = URLRequest(url: URL(string: URLPath)!)
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("Bearer \(elToken)", forHTTPHeaderField: "Authorization")
request.httpMethod = "POST"
let SessionDefault = Foundation.URLSession(configuration: URLSessionConfiguration.ephemeral)
URLCache.shared.removeAllCachedResponses()
let task = SessionDefault.dataTask(with: request){
(data, response, error)in
if error != nil {
print("Error al descargar la consulta")
}else{
print("Datos descargados")
self.parseJSON(data!)
}
}
task.resume()
}
func parseJSON(_ data: Data){
var jsonResult = NSArray()
do{
jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as! NSArray
}catch let error as NSError{
print(error)
}
var jsonElement = NSDictionary()
var detalles = [DetallesConsulta]()
for i in 0 ..< jsonResult.count{
jsonElement = jsonResult[i] as! NSDictionary
let detalle = DetallesConsulta()
let Fecha = jsonElement["Fecha"]
let Sucursal = jsonElement["Sucursal"]
let Suc = jsonElement["Suc"]
let VentaTotal = jsonElement["Venta_Total"]
let NoFolios = jsonElement["N_Folios"]
let Piezas = jsonElement["Piezas"]
let PzaxTicket = jsonElement["PzaxTicket"]
let TicketPromedio = jsonElement["TicketPromedio"]
detalle.Fecha = Fecha as? String
detalle.Sucursal = Sucursal as? String
detalle.Suc = Suc as? String
detalle.VentaTotal = VentaTotal as? String
detalle.NoFolios = NoFolios as? Int
detalle.Piezas = Piezas as? String
detalle.PzaxTicket = PzaxTicket as? String
detalle.TicketPromedio = TicketPromedio as? String
detalles.append(detalle)
}
DispatchQueue.main.async(execute: { ()-> Void in
self.ElDelegado.itemConsulta(LaConsulta: detalles)
})
}
}
The details
import UIKit
class DetallesConsulta: NSObject {
var Fecha: String?
var Sucursal: String?
var Suc: String?
var VentaTotal: String?
var NoFolios: Int?
var Piezas: String?
var PzaxTicket: String?
var TicketPromedio: String?
override init(){
}
init(Fecha: String, Sucursal: String, Suc: String, VentaTotal: String, NoFolios: Int, Piezas: String, PzaxTicket: String, TicketPromedio: String){
self.Fecha = Fecha
self.Sucursal = Sucursal
self.Suc = Suc
self.VentaTotal = VentaTotal
self.NoFolios = Int(NoFolios)
self.Piezas = Piezas
self.PzaxTicket = PzaxTicket
self.TicketPromedio = TicketPromedio
}
override var description: String{
return "Fecha: \(Fecha), Sucursal: \(Sucursal), Suc: \(Suc), VentaTotal: \(VentaTotal), NoFolios: \(NoFolios), Piezas: \(Piezas), PzaxTicket: \(PzaxTicket), TicketPromedio: \(TicketPromedio)"
}
}
JSON Response
[
{
"Fecha": "2022-11-17",
"Sucursal": "SCALPERS PUEBLA",
"Suc": "004",
"Venta_Total": "xxxxxx.xxxxxxxxx",
"N_Folios": 12,
"Piezas": "xx.000",
"PzaxTicket": "x.x",
"TicketPromedio": "xxxx.x"
},
{
"Fecha": "2022-11-17",
"Sucursal": "SCALPERS SATELITE",
"Suc": "005",
"Venta_Total": "xxxxx.xxx",
"N_Folios": xx,
"Piezas": "xx.000",
"PzaxTicket": "x.x",
"TicketPromedio": "xxxxx.xxxx"
},
{
"Fecha": "2022-11-17",
"Sucursal": "SCALPERS OUTLET QUERETARO",
"Suc": "006",
"Venta_Total": "xxx.xxxxxxxxxx",
"N_Folios": 4,
"Piezas": "6.000",
"PzaxTicket": "1.5",
"TicketPromedio": "1419.5"
},
{
"Fecha": "2022-11-17",
"Sucursal": "SCALPERS ONLINE",
"Suc": "xxxx",
"Venta_Total": "xxxxx.xxxxxxx",
"N_Folios": 15,
"Piezas": "45.000",
"PzaxTicket": "3.0",
"TicketPromedio": "1930.5"
}
]
I hope you can help me.
Thank you.
I downloaded the data from the json and would like to have it displayed in a grid table with this pod.
Your code is too complicated and too objective-c-ish. Never use NSArray/NSDictionary in Swift (except in a few rare CoreFoundation APIs)
To decode the data use Decodable. This is just a small part of the JSON as your class and the hard-coded data are quite different
let jsonString = """
[{"Sucursal":"Hamleys", "Venta_Total":"$1", "Piezas":"10"},
{"Sucursal":"Bobby Brown", "Venta_Total":"$1", "Piezas":"20"}]
"""
Create a struct conforming to Decodable and add CodingKeys to get rid of the capitalized keys. Maybe it's even possible with the .convertFromSnakeCase strategy
struct DetallesConsulta: Decodable {
var sucursal: String
var ventaTotal: String
var piezas: String
private enum CodingKeys: String, CodingKey {
case sucursal = "Sucursal", ventaTotal = "Venta_Total", piezas = "Piezas"
}
}
Then decode the data and to get arrays of the properties map the data.
do {
let result = try JSONDecoder().decode([DetallesConsulta].self, from: Data(jsonString.utf8))
let sucursales = result.map(\.sucursal)
let vtaTotal = result.map(\.ventaTotal)
let piezas = result.map(\.piezas)
print(sucursales, vtaTotal, piezas)
} catch {
print(error)
}

How to pass data from ViewModel to UICollectionViewCell?

I have ViewModel for getting data from API. I want to pass this data to my UICollectionViewCell and show it on my ViewController but I don't know how.
I'm trying to delete extra information and leave useful information in code below:
My ViewModel:
class DayWeatherViewModel {
let url = "https://api.openweathermap.org/data/2.5/weather?q=London&appid=APIKEY"
func viewDidLoad() {
getData(from: url)
}
func getData(from url: String) {
guard let url = URL(string: url) else {
print("Failed to parse URL")
return
}
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
print("something went wrong")
return
}
var result: CitiesWeather?
do {
result = try JSONDecoder().decode(CitiesWeather.self, from: data)
self.weatherDidChange?(result!)
}
catch {
print("failed to convert \(error)")
}
guard let json = result else {
return
}
print(json.coord?.latitude)
print(json.coord?.longitude)
print(json.weather)
print(json.wind?.speed)
}
task.resume()
}
var weatherDidChange: ((CitiesWeather) -> Void)?
}
My UICollectionViewCell:
class DayWeatherCell: UICollectionViewCell, UIScrollViewDelegate {
struct Model {
let mainTemperatureLabel: Double
}
var mainTemperatureLabel: UILabel = {
let label = UILabel()
label.font = UIFont(name: "Rubik-Medium", size: 36)
label.text = "10"
label.textColor = .white
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
func configure(with model: Model) {
mainTemperatureLabel.text = String(model.mainTemperatureLabel)
}
My ViewController:
class MainScrenenViewController: UIViewController {
let viewModel: DayWeatherViewModel
private var main: Double? {
didSet {
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
}
var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.register(DayWeatherCell.self, forCellWithReuseIdentifier: "sliderCell")
collectionView.layer.cornerRadius = 5
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = UIColor(red: 0.125, green: 0.306, blue: 0.78, alpha: 1)
return collectionView
}()
init(viewModel: DayWeatherViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
collectionView.dataSource = self
collectionView.delegate = self
setupConstraints()
viewModel.weatherDidChange = { result in
self.main = result.main?.temp
}
viewModel.viewDidLoad()
}
extension MainScrenenViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "sliderCell", for: indexPath) as! DayWeatherCell
if let myd: String? = String(main ?? 1.1) {
cell.mainTemperatureLabel.text = myd
}
return cell
}
}
extension MainScrenenViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
}
}
My Struct for parse JSON:
struct CitiesWeather: Decodable {
let coord : Coordinate?
let cod, visibility, id : Int?
let name : String?
let base : String?
let weather: [Weather]?
let sys: Sys?
let main: Main?
let wind: Wind?
let clouds: Clouds?
let dt: Date?
var timezone: Int?
}
struct Coordinate: Decodable {
var longitude: Double?
var latitude: Double?
}
struct Weather: Decodable {
let id: Int?
let main: MainEnum?
let description: String?
let icon: String?
}
struct Sys : Decodable {
let type, id : Int?
let sunrise, sunset : Date?
let message : Double?
let country : String?
}
struct Main : Decodable {
let temp, tempMin, tempMax : Double?
let pressure, humidity : Int?
}
struct Wind : Decodable {
let speed : Double?
let deg : Int?
}
struct Clouds: Decodable {
let all: Int?
}
enum MainEnum: String, Decodable {
case clear = "Clear"
case clouds = "Clouds"
case rain = "Rain"
}

How can I populate custom UITableViewCell with JSON data?

I've been practicing networking with swift 5, but I seem to always get stuck on parsing json to the UITableViewCells. I've looked at many tutorials, but can't seem to figure out the problem.
What I'm looking to do is retrieve the name of the country and place it in the tableview cells.
I use this url to request json: https://restcountries.eu/rest/v2/
The view models:
struct Countries: Codable {
let name: String?
let capital: String?
let region: String?
let population: String?
let currencies: [Currency]?
let languages: [Language]?
let flag: URL?
}
struct Currency: Codable {
let code: String?
let name: String?
let symbol: String?
}
struct Language: Codable {
let name: String?
}
My API request:
class Service {
static let shared = Service()
func fetchCountriesData(completed: #escaping (Result<Countries, ErrorMessages>) -> Void) {
let urlString = "https://restcountries.eu/rest/v2/"
guard let url = URL(string: urlString) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
// error
if let _ = error {
completed(.failure(.invalidData))
return
}
// response
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
completed(.failure(.invalidResponse))
return
}
// data
guard let data = data else {
completed(.failure(.invalidData))
return
}
do {
let decoder = JSONDecoder()
//decoder.keyDecodingStrategy = .convertFromSnakeCase
let results = try decoder.decode(Countries.self, from: data)
completed(.success(results))
} catch {
completed(.failure(.invalidData))
}
}
task.resume()
}
}
My MainViewController:
class MainViewController: UITableViewController {
var cellId = "cellId"
var countryData = [Countries]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
style()
tableView.delegate = self
tableView.dataSource = self
}
func style() {
navigationItem.title = "Search country information"
tableView.register(CountryViewCell.self, forCellReuseIdentifier: cellId)
//tableView.separatorStyle = .none
}
}
extension MainViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as? CountryViewCell else {
return UITableViewCell()
}
let results = countryData[indexPath.row].name
cell.titleLabel.text = results
return cell
}
}
And the custom TableViewCell:
class CountryViewCell: UITableViewCell {
private var countryResults: Countries? {
didSet {
self.titleLabel.text = countryResults?.name
}
}
var titleLabel: UILabel = {
let label = UILabel()
label.text = "countries"
label.font = .boldSystemFont(ofSize: 16)
label.numberOfLines = 0
label.textColor = .black
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
configure()
layout()
//displayResults()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension CountryViewCell {
func configure() {
selectionStyle = .gray
titleLabel.translatesAutoresizingMaskIntoConstraints = false
}
func layout() {
addSubview(titleLabel)
NSLayoutConstraint.activate([
titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
titleLabel.heightAnchor.constraint(equalToConstant: 20),
titleLabel.centerYAnchor.constraint(equalTo: centerYAnchor),
])
}
}

How to get the value inside the onNext to access it outside in my code

i am using mvvm pattern in my app here my example of code :
let userVM = UserViewModel()
userVM.getUser()
.subscribeOn(SerialDispatchQueueScheduler.init(qos: .background))
.observeOn(MainScheduler.instance)
.subscribe(onNext: { user in
self.user = user
}, onError: { error in
}, onCompleted: {
}, onDisposed: {
}).disposed(by: self.disposeBag)
i want to access the user emitted onNext outside the onSubscribe function any help with that ?
You shouldn't use any API calls on View.
Make output subscriber on your ViewModel and drive it to your view.
Example ViewModel:
import Foundation
import RxSwift
import RxCocoa
import RxDataSources
extension ModelListViewModel {
enum Sections: SectionModelType {
typealias Item = ModelDTO
case model(models: [Item])
var items: [Item] {
switch self {
case .model(let models):
return models
}
}
init(original: Sections, items: [Item]) {
self = original
}
}
}
class ModelListViewModel {
//inputs
let didLoad = PublishSubject<Void>()
let modelSelected = PublishSubject<ModelDTO>()
//outputs
let sections: Driver<[Sections]>
private let disposeBag = DisposeBag()
private static var data: [ModelDTO] = {
var array = [ModelDTO]()
guard let urlPath = Bundle.main.url(forResource: "seat_medium_quality_m_center", withExtension: "usdz") else {
return []
}
let firstModel = ModelDTO(url: urlPath, modelName: "Seat 1")
let secondModel = ModelDTO(url: urlPath, modelName: "Seat 2")
let thirdModel = ModelDTO(url: urlPath, modelName: "Seat 3")
return [firstModel, secondModel, thirdModel]
}()
init(context: ARRouter.ModelListContext) {
//TODO: Make sections due to responce from BackEnd
sections = didLoad
.mapTo([Sections.model(models: ModelListViewModel.data)])
.asDriver(onErrorDriveWith: .empty())
modelSelected.map { $0.url }
.bind(to: context.modelSelectedIn)
.disposed(by: disposeBag)
}
}
Example of ViewController:
import UIKit
import RxSwift
import RxCocoa
import RxDataSources
class ModelListViewController: BaseViewController {
private struct Cells {
static let modelListCell = ReusableCell<ModelListCell>(nibName: "ModelListCell")
}
#IBOutlet private weak var tableView: UITableView! {
didSet {
tableView.register(Cells.modelListCell)
viewModel.sections
.drive(tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
tableView.rx
.modelSelected(ModelDTO.self)
.bind(to: viewModel.modelSelected)
.disposed(by: disposeBag)
}
}
private let disposeBag = DisposeBag()
private let viewModel: ModelListViewModel
private let dataSource: RxTableViewSectionedReloadDataSource<ModelListViewModel.Sections>
init(viewModel: ModelListViewModel) {
self.viewModel = viewModel
self.dataSource = .init(configureCell: { (_, tableView, indexPath, item) -> UITableViewCell in
let cell = tableView.dequeue(Cells.modelListCell, for: indexPath)
cell.setup(with: item)
return cell
})
super.init(nibName: "ModelListViewController", bundle: nil)
rx.viewDidLoad
.bind(to: viewModel.didLoad)
.disposed(by: disposeBag)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
}
}

How to disable automatic scrolling to top

How can I disable auto scroll to the top of table view when I append new data to data source of it.
The problem is visible in the following gif.
Edit: Added ViewController, ViewModel and MessageEntity.
Used frameworks are: RxSwift, RxDataSources for reactive datasource of table view.
ViewController:
class RabbitMqVC: BaseViewController {
struct Cells {
static let message = ReusableCell<MessageCell>(nibName: "MessageCell")
static let messageTheir = ReusableCell<MessageCellTheir>(nibName: "MessageCellTheir")
}
#IBOutlet
weak var tableView: UITableView!{
didSet{
rabbitMqViewModel.sections
.drive(tableView.rx.items(dataSource: dataSource))
.addDisposableTo(disposeBag)
}
}
private let dataSource = RxTableViewSectionedAnimatedDataSource<RabbitMqViewModel.MessageSections>()
private let rabbitMqViewModel : rabbitMqViewModel
init(rabbitMqViewModel: rabbitMqViewModel) {
self.rabbitMqViewModel = rabbitMqViewModel
super.init(nibName: "RabbitMqVC", bundle: nil)
dataSource.configureCell = { _, tableView, indexPath, item in
let randomNumber = 1.random(to: 2)
let cell = randomNumber == 1 ? tableView.dequeue(Cells.message, for: indexPath) : tableView.dequeue(Cells.messageTheir, for: indexPath)
cell.message = item
return cell
}
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(Cells.message)
tableView.register(Cells.messageTheir)
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 80
}
}
ViewModel:
class RabbitMqViewModel: ViewModel {
enum MessageSections: AnimatableSectionModelType {
typealias Item = MessageEntity
typealias Identity = Int
case messages(messages: [MessageEntity])
var items: [Item] {
switch self {
case .messages(messages:let messages):
return messages
}
}
var identity: Int {
return 1
}
init(original: MessageSections, items: [Item]) {
switch original {
case .messages:
self = .messages(messages: items)
}
}
}
// input
let didLoad = PublishSubject<Void>()
//output
let sections: Driver<[MessageSections]>
init(service: RabbitMqService,){
let messages: Observable<[MessageEntity]> = didLoad
.flatMapLatest { _ -> Observable<[MessageEntity]> in
return service.listenMessages()
}
.share()
self.sections = messages
.map { (messages) -> [RabbitMqViewModel.MessageSections] in
var sections: [MessageSections] = []
sections.append(.messages(messages: messages))
return sections
}
.asDriver(onErrorJustReturn: [])
}
}
MessageEntity:
struct MessageEntity {
let id: String
let conversationId: String
let messageText: String
let sent: Date
let isSentByClient: Bool
let senderName: String
let commodityClientId : Int?
}
extension MessageEntity: IdentifiableType, Equatable {
typealias Identity = Int
public var identity: Identity {
return id.hashValue
}
public static func ==(lhs: MessageEntity, rhs: MessageEntity) -> Bool {
return lhs.id == rhs.id
}
}
estimatedRowHeight = 1
Fixed it.

Resources