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"
}
Related
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)
}
I created four label inside the table view cell to fetch the data from Api . The link is https://coinmap.org/api/v1/venues/ . I have two swift file one is to define the tabelview property and other one is for Label with cell . When I run the app it displaying the tableview cell but not displaying the data with label . Here is the model .
// MARK: - Welcome
struct Coin: Codable {
let venues: [Venue]
}
// MARK: - Venue
struct Venue: Codable {
let id: Int
let lat, lon: Double
let category, name: String
let createdOn: Int
let geolocationDegrees: String
enum CodingKeys: String, CodingKey {
case id, lat, lon, category, name
case createdOn = "created_on"
case geolocationDegrees = "geolocation_degrees"
}
}
Here is the Network Manager .
class NetworkManager {
func getCoins(from url: String, completion: #escaping (Result<Coin, NetworkError>) -> Void ) {
guard let url = URL(string: url) else {
completion(.failure(.badURL))
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(.failure(.other(error)))
return
}
if let data = data {
//decode
do {
let response = try JSONDecoder().decode(Coin.self, from: data)
completion(.success(response))
} catch let error {
completion(.failure(.other(error)))
}
}
}
.resume()
}
}
Here is the presenter .
class VenuePresenter : VanueProtocol{
// creating instance of the class
private let view : VanueViewProtocol
private let networkManager: NetworkManager
private var vanues = [Venue]()
var rows: Int{
return vanues.count
}
// initilanize the class
init(view:VanueViewProtocol , networkmanager:NetworkManager = NetworkManager()){
self.view = view
self.networkManager = networkmanager
}
func getVanue(){
let url = "https://coinmap.org/api/v1/venues/"
networkManager.getCoins(from: url) { result in
switch result {
case.success(let respone):
self.vanues = respone.venues
DispatchQueue.main.async {
self.view.resfreshTableView()
}
case .failure(let error):
DispatchQueue.main.async {
self.view.displayError(error.errorDescription ?? "")
print(error)
}
}
}
}
func getId(by row: Int) -> Int {
return vanues[row].id
}
func getLat(by row: Int) -> Double {
return vanues[row].lat
}
func getCreated(by row: Int) -> Int {
return vanues[row].createdOn
}
func getLon(by row: Int) -> Double? {
return vanues[row].lon
}
}
Here is the view controller .
class ViewController: UIViewController{
private var presenter : VenuePresenter!
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
setUpUI()
presenter = VenuePresenter(view: self)
presenter.getVanue()
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "DisplayView1")
// Do any additional setup after loading the view.
}
private func setUpUI() {
tableView.dataSource = self
tableView.delegate = self
}
}
extension ViewController : VanueViewProtocol{
func resfreshTableView() {
tableView.reloadData()
}
func displayError(_ message: String) {
let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
let doneButton = UIAlertAction(title: "Done", style: .default, handler: nil)
alert.addAction(doneButton)
present(alert, animated: true, completion: nil)
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
presenter.rows
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: DisplayView.identifier, for: indexPath) as? DisplayView
else { return UITableViewCell() }
let row = indexPath.row
let id = presenter.getId(by: row)
let lat = presenter.getLat(by: row)
guard let lon = presenter.getLon(by: row) else { return UITableViewCell() }
let createdOn = presenter.getCreated(by: row)
cell.configureCell(id: id, lat: lat, lon: lon, createdOn: createdOn)
return cell
}
}
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
Here is the display view swift .
class DisplayView: UITableViewCell{
static let identifier = "DisplayView1"
#IBOutlet weak var label1: UILabel!
#IBOutlet weak var label2: UILabel!
#IBOutlet weak var label3: UILabel!
#IBOutlet weak var label4: UILabel!
func configureCell(id: Int ,lat : Double , lon : Double , createdOn: Int){
label1.text = String(id)
label2.text = String(lat)
label3.text = String(lon)
label4.text = String(createdOn)
}
}
Here is the screenshot is empty not displaying the data .
You are registering a generic cell:
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "DisplayView1")
You need to register your custom cell:
self.tableView.register(DisplayView.self, forCellReuseIdentifier: DisplayView.identifier)
The question might not sound correctly worded but I don't know how to else put it, Apologies.
I am using Google Maps API in my project, to fetch the results and put them in a CollectionView using the MSPeekCollectionViewDelegateImplementation POD
Please any help is so much appreciated!
Always on the first run, as seen in the picture below, it never shows up.
On the second run however it works perfectly, as seen below:
API manager class:
import Foundation
import CoreData
class GoogleMapsAPIManager {
var bloodBanksArray = [BloodBanksModel]()
func fetchCity(latitude: CLLocationDegrees, longitude: CLLocationDegrees, raduis: Double){
let urlString = "\(K.googleMapsAPI)&location=\(latitude),\(longitude)&radius=\(raduis)&key=\(K.apiKey)"
print(urlString)
performRequest(with: urlString)
}
//MARK: - Decoding JSON
func performRequest(with urlString: String){
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
if let safeData = data {
self.parseJSON(bloodBankData: safeData)
}
}
task.resume()
}
}
func parseJSON(bloodBankData: Data) {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(BloodBanksData.self, from: bloodBankData)
for i in 0...decodedData.results.count - 1 {
bloodBanksArray.append(BloodBanksModel(name: decodedData.results[i].name, photo: decodedData.results[i].photos?[0].photoReference ?? "ATtYBwJjqXlw3DMdL74SRtgG_GRA3LkAET6hDJXHWtkQMaOTo1B3Gx9jrDTXFLFabGStSxX8BiYdLAnknF7A9ynw33KKyUh5Oc55A9vXzo_6nd4mnk5Sx-iOqMNaw21dk0C424PWaio9GiogQaKecYxOT1q-bmj30syypZmyxkjF7r3-gFyC", open_now: decodedData.results[i].openingHours?.openNow ?? false, longitude: decodedData.results[i].geometry.location.lng, latitude: decodedData.results[i].geometry.location.lat, vincinity: decodedData.results[i].vicinity, rating: decodedData.results[i].rating, placeId: decodedData.results[i].placeId, lat: decodedData.results[i].geometry.location.lat, lng: decodedData.results[i].geometry.location.lng))
}
print("bossins \(bloodBanksArray)")
} catch {
print(error)
}
}
}
Blood banks Model:
import Foundation
struct BloodBanksModel {
let name: String
let photo: String
let open_now: Bool
let longitude: Double
let latitude: Double
let vincinity: String
let rating: Double
let placeId: String
let lat: Double
let lng: Double
}
Blood Bank Data:
import Foundation
// MARK: - BloodBanksData
struct BloodBanksData: Codable {
let results: [Result]
enum CodingKeys: String, CodingKey {
case results
}
}
// MARK: - Result
struct Result: Codable {
let geometry: Geometry
let name: String
let openingHours: OpeningHours?
let photos: [Photo]?
let rating: Double
let vicinity: String
let placeId: String
enum CodingKeys: String, CodingKey {
case geometry, name
case openingHours = "opening_hours"
case photos
case rating
case vicinity
case placeId = "place_id"
}
}
// MARK: - Geometry
struct Geometry: Codable {
let location: Location
}
// MARK: - Location
struct Location: Codable {
let lat, lng: Double
}
// MARK: - OpeningHours
struct OpeningHours: Codable {
let openNow: Bool
enum CodingKeys: String, CodingKey {
case openNow = "open_now"
}
}
// MARK: - Photo
struct Photo: Codable {
let photoReference: String
enum CodingKeys: String, CodingKey {
case photoReference = "photo_reference"
}
}
The VC:
let locationManager = CLLocationManager()
var googleMapsAPIManager = GoogleMapsAPIManager()
override func viewDidLoad() {
super.viewDidLoad()
//locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
var currentLocation: CLLocation!
if
CLLocationManager.authorizationStatus() == .authorizedWhenInUse ||
CLLocationManager.authorizationStatus() == .authorizedAlways
{
currentLocation = locationManager.location
googleMapsAPIManager.fetchCity(latitude: currentLocation.coordinate.latitude, longitude: currentLocation.coordinate.longitude, raduis: 2000.0)
}
configureCollectionView()
}
func configureCollectionView() {
collectionView.dataSource = self
collectionView.delegate = self
let cellNib = UINib(nibName: K.BloodBanks.bloodBanksCellNibName, bundle: nil)
collectionView.register(cellNib,
forCellWithReuseIdentifier: K.BloodBanks.bloodBankCellIdentifier)
behavior = MSCollectionViewPeekingBehavior(cellSpacing: 20)
behavior = MSCollectionViewPeekingBehavior(cellPeekWidth: 40)
behavior = MSCollectionViewPeekingBehavior(minimumItemsToScroll: 1)
behavior = MSCollectionViewPeekingBehavior(maximumItemsToScroll: 3)
behavior = MSCollectionViewPeekingBehavior(numberOfItemsToShow: 1)
collectionView.configureForPeekingBehavior(behavior: behavior)
}
}
// MARK: - UICollectionViewDataSource Methods
extension LandingViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return googleMapsAPIManager.bloodBanksArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: K.BloodBanks.bloodBankCellIdentifier, for: indexPath) as! BloodBanksCell
DispatchQueue.main.async {
cell.bloodBankName.text = self.googleMapsAPIManager.bloodBanksArray[indexPath.row].name
cell.bloodBankImageView.sd_setImage(with: URL(string: "\(K.googleMapsImageAPI)&photoreference=\(self.googleMapsAPIManager.bloodBanksArray[indexPath.row].photo)&key=\(K.apiKey)"), placeholderImage: #imageLiteral(resourceName: "bloodbank4"))
}
return cell
}
}
// MARK: - UICollectionViewDelegate Methods
extension LandingViewController: UICollectionViewDelegate {
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
behavior.scrollViewWillEndDragging(scrollView, withVelocity: velocity, targetContentOffset: targetContentOffset)
}
}
My goal is to display data coming from a web service called News API into a collection view controller. Below is the data model and network manager.
struct News: Codable {
var status: String?
var totalResults: Int?
var articles: [Article]?
var article: Article?
enum CodingKeys: String, CodingKey {
case status = "status"
case totalResults = "totalResults"
case articles = "articles"
}
}
// MARK: - Article
struct Article: Codable {
var source: Source?
var author: String?
var title: String?
var articleDescription: String?
var url: String?
var urlToImage: String?
var publishedAt: String?
var content: String?
enum CodingKeys: String, CodingKey {
case source = "source"
case author = "author"
case title = "title"
case articleDescription = "description"
case url = "url"
case urlToImage = "urlToImage"
case publishedAt = "publishedAt"
case content = "content"
}
}
// MARK: - Source
struct Source: Codable {
var id: ID?
var name: Name?
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
}
}
enum ID: String, Codable {
case engadget = "engadget"
case techcrunch = "techcrunch"
case theVerge = "the-verge"
}
enum Name: String, Codable {
case engadget = "Engadget"
case lifehackerCOM = "Lifehacker.com"
case techCrunch = "TechCrunch"
case theVerge = "The Verge"
}
class NetworkManger{
static let shared = NetworkManger()
private let baseURL: String
private var apiKeyPathCompononent :String
private init(){
self.baseURL = "https://newsapi.org/v2/everything?q=NFT&sortBy=popularity&"
self.apiKeyPathCompononent = "apiKey=d32071cd286c4f6b9c689527fc195b03"
}
private var jsonDecoder:JSONDecoder = {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return decoder
}()
func getArticles() {
AF.request(self.baseURL + self.apiKeyPathCompononent, method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil, interceptor: nil).response { (responseData) in
guard let data = responseData.data else {return}
do {
let news = try self.jsonDecoder.decode(News.self, from: data)
let nc = NotificationCenter.default
nc.post(name: Notification.Name("didFinishParsing"), object: nil)
} catch {
print(error)
}
}
}
}
It can display data from the web service to the console. The problem is that it can not show data to the collection view in NewsVC. I've done all necessary steps such as implementing the collection view data source, using an observer to alert the NewsVC that the JSON is parsed, setting the collection view layout, and registering the cell, but nothing seems to work. The code below is the NewVc and News grid; News Vc is meant to show the contents of the.
class NewsVC: UIViewController {
var news = [Article]()
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
configureCollection()
NetworkManger.shared.getArticles()
}
func configureCollection(){
collectionView.delegate = self
collectionView.dataSource = self
let collectionviewFlowLayout = UICollectionViewFlowLayout()
collectionviewFlowLayout.scrollDirection = .vertical
collectionviewFlowLayout.itemSize = CGSize(width: 188.0, height: 264.0)
collectionviewFlowLayout.minimumInteritemSpacing = 10.0
collectionView.collectionViewLayout = collectionviewFlowLayout
NotificationCenter.default.addObserver(self, selector: #selector(refreshcollectionView), name: Notification.Name("didFinishParsing"), object: nil)
}
#objc func refreshcollectionView(_ notification:Notification) {
guard let news = notification.object as? Article else { return}
print("News == \(news)")
self.news = [news]
print("Coount == \(self.news.count)")
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
extension NewsVC:UICollectionViewDataSource , UICollectionViewDelegate{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
news.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath) as! NewsGridCell
let stories = news[indexPath.item]
cell.setCells(stories)
cell.backgroundColor = UIColor.red
return cell
}
class NewsGridCell: UICollectionViewCell {
#IBOutlet weak var newsImage: UIImageView!
#IBOutlet weak var newsDescription: UILabel!
#IBOutlet weak var author: UILabel!
func setCells(_ news:Article){
upDateUI(newDescription:news.articleDescription, author: news.author)
}
private func upDateUI(newDescription:String? , author:String?){
self.newsDescription.text = newDescription
self.author.text = author
}
}
Try instead of
nc.post(name: Notification.Name("didFinishParsing"), object: nil)
send
nc.post(name: Notification.Name("didFinishParsing"), object: news)
Then instead of
guard let news = notification.object as? Article else { return}
write
guard let news = notification.object as? News else { return}
print("News == \(news)")
self.news = news.articles
After reading the snippets, the code looks like it should works but it doesn't. In that case I would add debugPrint(#function) to check if following methods are executed:
NewsVC -> collectionView(_:numberOfItemsInSection:)
NewsVC -> collectionView(_:cellForItemAt:)
NewsGridCell -> setCells(_:)
If those debugPrints print to the console then I would go to the "View Hierarchy inspector" to examine presence and a view's sizes.
I'm new to coding and could really use your help!
I am trying to show a 'bestseller' image on a product based on a boolean.
I am using Firestore for the database.
I have managed to get the value of the 'bestseller' field on all the documents, but I don't know what to do next.
This is my code so far. This shows the bestsellerImg on all of the products - instead of only the ones where the value = "True"
Here are two pictures to show what i mean :)
the swift file/class "ProductsVC" is controlling the ViewController with the collectionView in it.
Code from "ProductsVC"
import UIKit
import Firebase
class ProductsVC: UIViewController, ProductCellDelegate {
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var categoryName: UILabel!
var products = [Product]()
var category: Category!
var db : Firestore!
var listener : ListenerRegistration!
override func viewDidLoad() {
super.viewDidLoad()
db = Firestore.firestore()
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(UINib(nibName: Identifiers.ProductCell, bundle: nil), forCellWithReuseIdentifier: Identifiers.ProductCell)
setQuery()
categoryName.text = category.name
}
func setQuery() {
var ref: Query!
ref = db.products(category: category.id)
listener = ref.addSnapshotListener({ (snap, error) in
if let error = error {
debugPrint(error.localizedDescription)
}
snap?.documentChanges.forEach({ (change) in
let data = change.document.data()
let product = Product.init(data: data)
switch change.type {
case .added:
self.onDocumentAdded(change: change, product: product)
case .modified:
self.onDocumentModified(change: change, product: product)
case .removed:
self.onDoucmentRemoved(change: change)
}
})
})
}
func productAddToCart(product: Product) {
if UserService.isGuest {
self.simpleAlert(title: "Hej!", msg: "Man kan kun tilføje ting til sin kurv hvis man er oprettet som Fender-bruger. ")
return
}
PaymentCart.addItemToCart(item: product)
self.addedtocart(title: "Tilføjet til kurv!", msg: "")
}
}
extension ProductsVC: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func onDocumentAdded(change: DocumentChange, product: Product) {
let newIndex = Int(change.newIndex)
products.insert(product, at: newIndex)
collectionView.insertItems(at: [IndexPath(item: newIndex, section: 0)])
}
func onDocumentModified(change: DocumentChange, product: Product) {
if change.oldIndex == change.newIndex {
let index = Int(change.newIndex)
products[index] = product
collectionView.reloadItems(at: [IndexPath(item: index, section: 0)])
} else {
let oldIndex = Int(change.oldIndex)
let newIndex = Int(change.newIndex)
products.remove(at: oldIndex)
products.insert(product, at: newIndex)
collectionView.moveItem(at: IndexPath(item: oldIndex, section: 0), to: IndexPath(item: newIndex, section: 0))
}
}
func onDoucmentRemoved(change: DocumentChange) {
let oldIndex = Int(change.oldIndex)
products.remove(at: oldIndex)
collectionView.deleteItems(at: [IndexPath(item: oldIndex, section: 0)])
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
products.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Identifiers.ProductCell, for: indexPath) as? ProductCell {
cell.configureCell(product: products[indexPath.item], delegate: self)
return cell
}
return UICollectionViewCell()
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let vc = DetailProductVC()
let selectedProduct = products[indexPath.item]
vc.product = selectedProduct
vc.modalTransitionStyle = .crossDissolve
vc.modalPresentationStyle = .overCurrentContext
present(vc, animated: true, completion: nil)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = view.frame.width
let cellWidth = (width - 30) / 3
let cellHeight = cellWidth * 2.1
return CGSize(width: cellWidth, height: cellHeight)
}
}
My struct
import Foundation
import FirebaseFirestore
struct Product {
var name: String
var id: String
var category: String
var price: Double
var productDescription: String
var imageUrl: String
var timeStamp: Timestamp
var inStore: Int
var bestseller: Bool
var quantity: Int
init(
name: String,
id: String,
category: String,
price: Double,
productDescription: String,
imageUrl: String,
timeStamp: Timestamp = Timestamp(),
inStore: Int,
bestseller: Bool,
quantity: Int) {
self.name = name
self.id = id
self.category = category
self.price = price
self.productDescription = productDescription
self.imageUrl = imageUrl
self.timeStamp = timeStamp
self.inStore = inStore
self.bestseller = bestseller
self.quantity = quantity
}
init(data: [String: Any]) {
name = data["name"] as? String ?? ""
id = data["id"] as? String ?? ""
category = data["category"] as? String ?? ""
price = data["price"] as? Double ?? 0.0
productDescription = data["productDescription"] as? String ?? ""
imageUrl = data["imageUrl"] as? String ?? ""
timeStamp = data["timeStamp"] as? Timestamp ?? Timestamp()
inStore = data["inStore"] as? Int ?? 0
bestseller = data["bestseller"] as? Bool ?? true
quantity = data["quantity"] as? Int ?? 0
}
static func modelToData(product: Product) -> [String: Any] {
let data : [String: Any] = [
"name" : product.name,
"id" : product.id,
"category" : product.category,
"price" : product.price,
"productDescription" : product.productDescription,
"imageUrl" : product.imageUrl,
"timeStamp" : product.timeStamp,
"inStore" : product.inStore,
"bestseller" : product.bestseller,
"quantity" : product.quantity
]
return data
}
}
extension Product : Equatable {
static func ==(lhs: Product, rhs: Product) -> Bool {
return lhs.id == rhs.id
}
}
Code from "ProductCell"
import UIKit
import Kingfisher
import Firebase
protocol ProductCellDelegate : class {
func productAddToCart(product: Product)
}
class ProductCell: UICollectionViewCell{
#IBOutlet weak var imgView: UIImageView!
#IBOutlet weak var titleLbl: UILabel!
#IBOutlet weak var priceLbl: UILabel!
#IBOutlet weak var bestsellerImg: UIImageView!
var db: Firestore?
weak var delegate : ProductCellDelegate?
private var product: Product!
override func awakeFromNib() {
super.awakeFromNib()
imgView.layer.cornerRadius = 5
getInStore()
}
func getInStore() {
Firestore.firestore().collection("products").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents{
var isBestseller = document.get("bestseller")
}
}
}
}
func configureCell(product: Product, delegate: ProductCellDelegate) {
self.product = product
self.delegate = delegate
titleLbl.text = product.name
if let url = URL(string: product.imageUrl) {
let placeholder = UIImage(named: "Fender")
imgView.kf.indicatorType = .activity
let options : KingfisherOptionsInfo =
[KingfisherOptionsInfoItem.transition(.fade(0.1))]
imgView.kf.setImage(with: url, placeholder: placeholder, options: options)
}
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencyCode = "DKK"
if let price = formatter.string(from: product.price as NSNumber) {
priceLbl.text = price
}
}
#IBAction func addToCart(_ sender: Any) {
delegate?.productAddToCart(product: product)
}
}
Looking at the code, there may be a pretty simple solution that would simplify what you're trying to do.
Let me walk through it and then make a suggestion:
The tableView datasource is populated in the setQuery function with
func setQuery() {
...
snap?.documentChanges.forEach({ (change) in
let data = change.document.data()
let product = Product.init(data: data)
Each product knows if it's a best seller because it has a best seller property which is either true or false. So when the products are loaded from firebase, that property is set with the Product.init.
As your tableView delegate is creating each cell
cell.configureCell(product: products[indexPath.item]
why don't you just have a piece of code in ProductCell that says if the product is a bestSeller then use the bestSeller image, else use the regular image?
func configureCell(product: Product, delegate: ProductCellDelegate) {
self.product = product
self.delegate = delegate
if self.product.bestSeller == true {
//set the image the bestseller image
} else {
//set the image to the regular image
}