How can I populate custom UITableViewCell with JSON data? - uitableview

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),
])
}
}

Related

Swift Tableviewcell linkage after API communication

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

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)
}

Swift. I pull out pictures in cells through api. Everything is being built, but instead of pictures it's empty. Returns nil. Explain please

I'm a beginner. I pull out pictures in cells through api. Everything is built, but instead of pictures - it's empty. Returns nil. I've been sitting here all day and can't figure it out!
API link - https://swiftbook.ru//wp-content/uploads/api/api_courses
If this answer is somewhere, I apologize, and if it's not difficult to give a link, send it please, thank you.
Thank you very much in advance for your help and clarification!!!
enter image description here
import UIKit
class CourseCell: UITableViewCell {
#IBOutlet var courseImage: UIImageView!
#IBOutlet var courseNameLabel: UILabel!
#IBOutlet var numberOfLessons: UILabel!
#IBOutlet var numberOfTests: UILabel!
func configure(with course: Course) {
courseNameLabel.text = course.name
numberOfLessons.text = "Number of lessons \(course.number_of_lessons ?? 0)"
numberOfTests.text = "Number of tests \(course.number_of_tests ?? 0)"
DispatchQueue.global().async {
guard let stringUrl = course.imageUrl,
let imageURL = URL(string: stringUrl),
let imageData = try? Data(contentsOf: imageURL)
else {
return
}
DispatchQueue.main.async {
self.courseImage.image = UIImage(data: imageData)
}
}
}
}
Model for decode by JSON
Course.swift
struct Course: Decodable {
let name: String?
let imageUrl: String?
let number_of_lessons: Int?
let number_of_tests: Int?
}
struct WebsiteDescription: Decodable {
let courses: [Course]?
let websiteDescription: String?
let websiteName: String?
}
And piece of code with JSON from CoursesViewController.swift
extension CoursesViewController {
func fetchCourses() {
guard let url = URL(string: URLExamples.exampleTwo.rawValue) else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
guard let data = data else {
return
}
do {
// получаем курсы в переменную
self.courses = try JSONDecoder().decode([Course].self, from: data)
// и мы должны перезагрузить таблицу
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let error {
print(error)
}
}.resume()
}
}
And here is i get nil probably (please see screenshot below)
enter image description here
I tried to make another version of your code and it's able to run. You can check my code and compare with your own.
CoursesViewController
class CoursesViewController: UIViewController {
private lazy var tableView: UITableView = {
let tableView = UITableView(frame: .zero, style: .plain)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.register(CourseCell.self, forCellReuseIdentifier: "CourseCell")
tableView.delegate = self
tableView.dataSource = self
return tableView
}()
private var courses: [Course] = []
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
setupLayout()
fetchCourses()
}
private func setupViews() {
view.addSubview(tableView)
}
private func setupLayout() {
tableView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
private func fetchCourses() {
guard let url = URL(string: "https://swiftbook.ru//wp-content/uploads/api/api_courses") else {
return
}
URLSession.shared.dataTask(with: url) { (data, _, _) in
guard let data = data else {
return
}
do {
// получаем курсы в переменную
self.courses = try JSONDecoder().decode([Course].self, from: data)
// и мы должны перезагрузить таблицу
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let error {
print(error)
}
}.resume()
}
}
extension CoursesViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
courses.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "CourseCell", for: indexPath) as? CourseCell else {
return UITableViewCell()
}
cell.configure(with: courses[indexPath.row])
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
120
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
120
}
}
Cell
class CourseCell: UITableViewCell {
private lazy var nameLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.textColor = .black
label.font = .systemFont(ofSize: 14, weight: .bold)
return label
}()
private lazy var courseImage = UIImageView(frame: .zero)
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
setupLayout()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(with course: Course) {
nameLabel.text = course.name
DispatchQueue.global().async {
guard let stringUrl = course.imageUrl,
let imageURL = URL(string: stringUrl),
let imageData = try? Data(contentsOf: imageURL)
else {
return
}
DispatchQueue.main.async {
// Make sure it's the same course
self.courseImage.image = UIImage(data: imageData)
}
}
}
private func setupViews() {
courseImage.contentMode = .scaleAspectFill
contentView.addSubview(nameLabel)
contentView.addSubview(courseImage)
}
private func setupLayout() {
nameLabel.snp.makeConstraints { make in
make.top.leading.trailing.equalToSuperview().inset(8)
}
courseImage.snp.makeConstraints { make in
make.centerX.equalToSuperview().inset(8)
make.top.equalTo(nameLabel.snp.bottom).offset(12)
make.height.width.equalTo(80)
}
}
}
In my opinion, you should check your UI layout to make sure that the image view can be loaded and displayed properly.
Some Improvement suggestion
Course.swift: Please use lower camel case convention for variables name because it's the common Swift convention
CourseCell.swift: Since the course don't have ID so after a while you load image from background, this cell might be used by another because of the reuse cell mechanism.
DispatchQueue.main.async {
// Make sure it's the same course
if course.id == self.course.id {
self.courseImage.image = UIImage(data: imageData)
}
}
Use caching mechanism every time you load image from the server so that next time you don't need to fetch from the server again (you can set timeout for cache)
Instead of handing loading image by yourself, you can use well-known 3rd party libs like SDWebImage or KingFisher.
The answer above is Excellent !!! It's an additional valuable experience for me!
But main reason was in blocked .ru domains in my country. WHEN I ACCIDENTALLY TURNED ON THE VPN ON THE MAC AND BUILD APP, THEN EVERYTHING LOADED!!! Because I am in Ukraine now, and we have all .ru domains blocked, and the API URL is just on .ru

Can't show part of data in a cell

I try to show data from API Response. I have json and DataService.
When ViewController is load, my presenter give away data from my Service.
I don't know why, but I make two request to my service. First time I get only one empty cell and the second time I get other data.
Image from view hierarchy:
My DataService:
class DataService {
func getRouts(completion: #escaping (APIResponse) -> Void?) {
let urlString = "https://travel.wildberries.ru/statistics/v1/cheap"
guard let url = URL(string: urlString) else { return }
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else { return }
do {
let jsonResults = try JSONDecoder().decode(APIResponse.self, from: data)
completion(jsonResults)
}
catch {
print{error}
}
}
task.resume()
}
}
My presenter
protocol PresenterProtocol: AnyObject {
func viewDidLoad()
}
final class ListTicketsModulPresenter: PresenterProtocol {
let dataService = DataService()
weak var listTicketsViewController: ListTicketsViewController?
func viewDidLoad() {
dataService.getRouts { [weak self] results in
DispatchQueue.main.async {
print(results)
self?.listTicketsViewController?.configure(with: results)
}
}
}
}
My ViewController:
final class ListTicketsViewController: UIViewController, ListTicketsViewControllerProtocol {
var presenter: PresenterProtocol
let tableView: UITableView = {
let tableView = UITableView()
tableView.backgroundColor = UIColor.white
tableView.separatorColor = .white
tableView.allowsSelection = false
tableView.translatesAutoresizingMaskIntoConstraints = false
return tableView
}()
var numberOfRows: Int?
var startCityNameArray: [String]?
var endCityNameArray: [String]?
var startDateArray: [String]?
var endDateArray: [String]?
var price: [Int]?
...
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(tableView)
setupTableView()
presenter.viewDidLoad()
}
func setupTableView() {
tableView.delegate = self
tableView.dataSource = self
tableView.register(ListTicketsModulCell.self, forCellReuseIdentifier: "cellId")
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
}
//Functions
func configure(with model: APIResponse) {
self.startCityNameArray = model.data.map { $0.startCity }
self.endCityNameArray = model.data.map { $0.endCity }
self.numberOfRows = model.data.count
self.startDateArray = model.data.map { $0.startDate }
self.endDateArray = model.data.map { $0.endDate }
self.price = model.data.map { $0.price }
self.tableView.reloadData()
}
...
}

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"
}

Resources