Essentially I have am using JSON data to create an array and form a tableview.
I would like the table cells to be grouped by one of the fields from the JSON array.
This is what the JSON data looks like:
[{"customer":"Customer1","number":"122039120},{"customer":"Customer2","number":"213121423"}]
Each number needs to be grouped by each customer.
How can this be done?
This is how I've implemented the JSON data using the table:
CustomerViewController.swift
import UIKit
class CustomerViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, FeedCustomerProtocol {
var feedItems: NSArray = NSArray()
var selectedStock : StockCustomer = StockCustomer()
let tableView = UITableView()
#IBOutlet weak var customerItemsTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
//set delegates and initialize FeedModel
self.tableView.allowsMultipleSelection = true
self.tableView.allowsMultipleSelectionDuringEditing = true
self.customerItemsTableView.delegate = self
self.customerItemsTableView.dataSource = self
let feedCustomer = FeedCustomer()
feedCustomer.delegate = self
feedCustomer.downloadItems()
}
}
func itemsDownloaded(items: NSArray) {
feedItems = items
self.customerItemsTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of feed items
print("item feed loaded")
return feedItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Retrieve cell
let cell = tableView.dequeueReusableCell(withIdentifier: "customerGoods", for: indexPath) as? CheckableTableViewCell
let cellIdentifier: String = "customerGoods"
let myCell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)!
// Get the stock to be shown
let item: StockCustomer = feedItems[indexPath.row] as! StockCustomer
// Configure our cell title made up of name and price
let titleStr = [item.number].compactMap { $0 }.joined(separator: " - ")
return myCell
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.accessoryType = .none
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
let cellIdentifier: String = "customerGoods"
let myCell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)!
myCell.textLabel?.textAlignment = .left
}
}
FeedCustomer.swift:
import Foundation
protocol FeedCustomerProtocol: class {
func itemsDownloaded(items: NSArray)
}
class FeedCustomer: NSObject, URLSessionDataDelegate {
weak var delegate: FeedCustomerProtocol!
let urlPath = "https://www.example.com/example/test.php"
func downloadItems() {
let url: URL = URL(string: urlPath)!
let defaultSession = Foundation.URLSession(configuration: URLSessionConfiguration.default)
let task = defaultSession.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Error")
}else {
print("stocks downloaded")
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()
let stocks = NSMutableArray()
for i in 0 ..< jsonResult.count
{
jsonElement = jsonResult[i] as! NSDictionary
let stock = StockCustomer()
//the following insures none of the JsonElement values are nil through optional binding
if let number = jsonElement[“number”] as? String,
let customer = jsonElement["customer"] as? String,
{
stock.customer = customer
stock.number = number
}
stocks.add(stock)
}
DispatchQueue.main.async(execute: { () -> Void in
self.delegate.itemsDownloaded(items: stocks)
})
}
}
StockCustomer.swift:
import UIKit
class StockCustomer: NSObject {
//properties of a stock
var customer: String?
var number: String?
//empty constructor
override init()
{
}
//construct with #name and #price parameters
init(customer: String) {
self.customer = customer
}
override var description: String {
return "Number: \(String(describing: number)), customer: \(String(describing: customer))"
}
}
You can achieve this by making an array of array. So something like this
[[{"customer": "customer1", "number": "123"}, {"customer": "customer1", "number": "456"}], [{"customer": "customer2", "number": "678"}, {"customer": "customer2", "number": "890"}]]
This is not the only data structure you can use to group. Another possibility is:
{"customer1": [{"customer": "customer1", "number": "123"}, {"customer": "customer1", "number": "456"}], "customer2": [{"customer": "customer2", "number": "678"}, {"customer": "customer2", "number": "890"}]}
Then you can use UITableView sections to group by customers. Section count would be the number of inside arrays and each section would contain as many rows as there are numbers in that inside array.
You can group a sequence based on a particular key using one of the Dictionary initializer,
init(grouping:by:)
The above method init will group the given sequence based on the key you'll provide in its closure.
Also, for parsing such kind of JSON, you can easily use Codable instead of manually doing all the work.
So, for that first make StockCustomer conform to Codable protocol.
class StockCustomer: Codable {
var customer: String?
var number: String?
}
Next you can parse the array like:
func parseJSON(data: Data) {
do {
let items = try JSONDecoder().decode([StockCustomer].self, from: data)
//Grouping the data based on customer
let groupedDict = Dictionary(grouping: items) { $0.customer } //groupedDict is of type - [String? : [StockCustomer]]
self.feedItems = Array(groupedDict.values)
} catch {
print(error.localizedDescription)
}
}
Read about init(grouping:by:) in detail here: https://developer.apple.com/documentation/swift/dictionary/3127163-init
Make the feedItems object in CustomerViewController of type [[StockCustomer]]
Now, you can implement UITableViewDataSource methods as:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.feedItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customerGoods", for: indexPath) as! CheckableTableViewCell
let items = self.feedItems[indexPath.row]
cell.textLabel?.text = items.compactMap({$0.number}).joined(separator: " - ")
//Configure the cell as per your requirement
return cell
}
Try implementing the approach with all the bits and pieces and let me know in case you face any issues.
Related
I start learning swift with Paul Hudson's "100 Days of Swift" and I need your advices.
I'm trying making app with country's info (capital, language, currencies etc.) and stuck with trying represent result of my JSON parsing in tableView.
This is my struct for parsing country's info from https://restcountries.com/v3.1/all
struct Country: Codable {
let name: Name
let cca2: String
let capital: [String]?
let population: Int
let currencies: [String: Currency]?
}
struct Name: Codable {
let common: String
let official: String
}
struct Currency: Codable {
let name: String?
let symbol: String?
}
I have problems with currencies. I don't understand how represent them properly in tableView. This is code of my ViewController:
import UIKit
class ViewController: UITableViewController {
var countries = [Country] ()
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "https://restcountries.com/v3.1/all"
if let url = URL(string: urlString) {
if let data = try? Data(contentsOf: url) {
parse(json: data)
return
}
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
countries.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Country", for: indexPath)
let country = countries[indexPath.row]
cell.textLabel?.text = country.name.common
cell.imageView?.image = UIImage(named: country.cca2.lowercased())
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let vc = storyboard?.instantiateViewController(withIdentifier: "Detail") as? DetailViewController {
vc.country = countries[indexPath.row]
present(vc, animated: true)
}
}
func parse(json: Data) {
let decoder = JSONDecoder()
do {
let jsonCountries = try decoder.decode([Country].self, from: json)
countries = jsonCountries
}
catch let error {
print(error)
}
}
}
And this is code of my DetailViewController:
import UIKit
class DetailViewController: UITableViewController {
var country: Country!
let flag = "Flag"
let general = "General"
let currency = "Currency"
var currencyName = ""
var currencySymbol = ""
lazy var sectionTitles = [flag, general, currency]
lazy var currencies = country.currencies?.values
override func viewDidLoad() {
super.viewDidLoad()
title = country.name.common
getCurrencyName()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return sectionTitles.count
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sectionTitles[section]
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch sectionTitles[section] {
case flag:
return 1
case general:
return 4
case currency:
// How make to return proper number's of rows??
return 2
default:
return 0
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch sectionTitles[indexPath.section] {
case flag:
let cell = tableView.dequeueReusableCell(withIdentifier: "Flag", for: indexPath)
if let cell = cell as? FlagCell {
cell.flagImageView.image = UIImage(named: country.cca2.lowercased())
}
return cell
case general:
let cell = tableView.dequeueReusableCell(withIdentifier: "Text", for: indexPath)
cell.textLabel?.numberOfLines = 0
switch indexPath.row {
case 0:
cell.textLabel?.text = "Common country name: \(country.name.common)"
case 1:
cell.textLabel?.text = "Official country name: \(country.name.official)"
case 2:
cell.textLabel?.text = "Capital: \(country.capital?[0] ?? "Unknown")"
case 3:
cell.textLabel?.text = "Population: \(country.population) people"
default:
return cell
}
return cell
case currency:
let cell = tableView.dequeueReusableCell(withIdentifier: "Text", for: indexPath)
cell.textLabel?.numberOfLines = 0
switch indexPath.row {
case 0:
// How to represent each currency of country?
cell.textLabel?.text = "Currency name: \(currencyName)"
case 1:
cell.textLabel?.text = "Currency symbol: \(currencySymbol)"
default:
return cell
}
return cell
default:
break
}
return UITableViewCell ()
}
func getCurrencyName () {
for currency in currencies! {
currencyName = currency.name ?? ""
currencySymbol = currency.symbol ?? ""
}
}
}
For now I understand how to represent one currency of each country, but how I can represent all currencies of each country in different rows?
Sorry for my English it's not my native language :)
I would suggest getting a sorted list of the currencies. E.g., for a given Country:
let currencies = country.currencies?.sorted { $0.0 < $1.0 }
To get the count:
let count = currencies?.count ?? 0
To get the details for a particular row, it would be:
if let (code, currency) = currencies?[indexPath.row] {
let currencyCode = code
let currencyName = currency.name
let currencySymbol = currency.symbol
}
You can access the number of currencies for each country with this, and use it in the numberOfRowsInSection method to return enough number of rows for currencies:
country.currencies.count
The rest is filling the cells in the cellForRowAt method by using the indexPath's section and row values. You should iterate over the currencies dictionary of the selected country, and display each key and value pair in the dictionary in a row.
You can do the iteration like so:
for (key, value) in dict {
// Use key and value here
}
Hello good afternoon to everyone.
I hope you are doing very well despite the situation we are going through.
I tell you my problem.
I am trying to save in an array, a series of data that is selected from a tableView through a JSON.
That is, the tableView shows some data available to select, the ones that are selected I want to save them in an array but when I select a data in the tableView my app crashes and shows me a breakpoint "Thread 1: signal SIGABRT"
And in the console I get this:
Could not cast value of type 'MallConcierge.DetallesIntereses' (0x10ca9be10) to 'NSString' (0x7fff86d8bbb0).
I hope you can help me, I attach the classes from where I download the data, the details and the class where I connect the tableView.
InteresesModelo.swift (in this class is where I download the data in JSON)
import UIKit
protocol InteresesModeloProtocol: class{
func interesesDownload (interest: NSArray)
}
class InteresesModelo: NSObject {
weak var delegate: InteresesModeloProtocol!
let urlPath = "http://localhost:8888/mallconcierge/API-movil/interests.php"
func interestDownload(){
let url: URL = URL(string: urlPath)!
let defaultSession = Foundation.URLSession(configuration: URLSessionConfiguration.ephemeral)
URLCache.shared.removeAllCachedResponses()
let task = defaultSession.dataTask(with: url){
(data, response, error) in
if error != nil{
print("Error al descargar datos")
}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()
let detalles = NSMutableArray()
for i in 0 ..< jsonResult.count{
jsonElement = jsonResult[i] as! NSDictionary
let detalle = DetallesIntereses()
let idInteres = jsonElement["idInteres"]
let nombreInteres = jsonElement["interesNombre"]
detalle.idInteres = idInteres as? String
detalle.nombreInteres = nombreInteres as? String
detalles.add(detalle)
}
DispatchQueue.main.async(execute: { ()-> Void in
self.delegate.interesesDownload(interest: detalles)
})
}
}
DetallesIntereses.swift
import UIKit
class DetallesIntereses: NSObject {
var idInteres: String?
var nombreInteres: String?
override init() {
}
init(idInteres: String, nombreInteres:String) {
self.idInteres = idInteres
self.nombreInteres = nombreInteres
}
override var description: String{
return "idInteres: \(idInteres), nombreInteres: \(nombreInteres)"
}
}
InteresesViewController.swift
import UIKit
class InteresesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, InteresesModeloProtocol {
var selectIntereses = [String]()
var feedInterests: NSArray = NSArray()
// var selectInterests: DetallesIntereses = DetallesIntereses()
var items=[String]()
#IBOutlet var listaInteresesTableView: UITableView!
func interesesDownload(interest: NSArray) {
feedInterests = interest
self.listaInteresesTableView.reloadData()
}
override func viewDidLoad() {
self.listaInteresesTableView.isEditing = true
self.listaInteresesTableView.allowsMultipleSelectionDuringEditing = true
self.listaInteresesTableView.delegate = self
self.listaInteresesTableView.dataSource = self
let interesesModelo = InteresesModelo()
interesesModelo.delegate = self
interesesModelo.interestDownload()
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return feedInterests.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "celInterests", for: indexPath) as! InteresesTableViewCell
let interest: DetallesIntereses = feedInterests[indexPath.row] as! DetallesIntereses
cell.lblNombreIntereses!.text = interest.nombreInteres
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.selectDeselectCell(tableView: listaInteresesTableView, indexPath: indexPath)
print("Seleccionado")
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
self.selectDeselectCell(tableView: listaInteresesTableView, indexPath: indexPath)
print("Deseleccionado")
}
func selectDeselectCell(tableView: UITableView, indexPath: IndexPath){
self.selectIntereses.removeAll()
if let arr = listaInteresesTableView.indexPathsForSelectedRows{
for index in arr{
selectIntereses.append(feedInterests[indexPath.row] as! String)
}
}
print(selectIntereses)
}
#IBAction func seleccionarIntereses(_ sender: Any){
print(selectIntereses)
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
The JSON
[
{
"idInteres": "1",
"interesNombre": "Moda Mujer"
},
{
"idInteres": "3",
"interesNombre": "Moda Hombre"
},
{
"idInteres": "4",
"interesNombre": "Belleza"
},
{
"idInteres": "5",
"interesNombre": "Relojes y Joyería"
},
{
"idInteres": "6",
"interesNombre": "Hogar/Interiorismo"
},
{
"idInteres": "7",
"interesNombre": "Gastronomía"
},
{
"idInteres": "8",
"interesNombre": "Entretenimiento"
},
{
"idInteres": "9",
"interesNombre": "Wellness"
}
]
I hope you can help me, please.
I thank you all
There's lots of little stuff going on here. I've tried to highlight as I make edits with //<-- Here.
By far the biggest issue is using the untyped Objective-C NSArray instead of using a typed Swift array. By switching to that, you can get useful type errors.
I had to make a couple of assumptions about what you wanted to do, but this should get you started.
I've left the original JSON decoding for the most part, but I'd strongly suggest you look into using Codable instead of JSONSerialization unless you have a compelling reason not to.
protocol InteresesModeloProtocol : AnyObject {
func interesesDownload (interest: [DetallesIntereses]) //<-- Here
}
class InteresesModelo: NSObject {
weak var delegate: InteresesModeloProtocol!
let urlPath = "http://localhost:8888/mallconcierge/API-movil/interests.php"
func interestDownload(){
let url: URL = URL(string: urlPath)!
let defaultSession = Foundation.URLSession(configuration: URLSessionConfiguration.ephemeral)
URLCache.shared.removeAllCachedResponses()
let task = defaultSession.dataTask(with: url){
(data, response, error) in
if error != nil{
print("Error al descargar datos")
}else{
print("Datos descargados")
self.parseJSON(data!)
}
}
task.resume()
}
func parseJSON(_ data:Data){
var jsonResult = [[String:String]]()
do{
jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as! [[String : String]]
}catch let error as NSError{
print(error)
}
let detalles = jsonResult.map { jsonElement -> DetallesIntereses in
let detalle = DetallesIntereses(idInteres: jsonElement["idInteres"], nombreInteres: jsonElement["interesNombre"])
return detalle
}
DispatchQueue.main.async {
self.delegate.interesesDownload(interest: detalles)
}
}
}
struct DetallesIntereses { //<-- Here -- no reason for this to be an `NSObject`
var idInteres: String?
var nombreInteres: String?
}
class InteresesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, InteresesModeloProtocol {
var selectIntereses = [DetallesIntereses]() //<-- Here
var feedInterests: [DetallesIntereses] = [] //<-- Here
var items = [String]()
#IBOutlet var listaInteresesTableView: UITableView!
func interesesDownload(interest: [DetallesIntereses]) { //<-- Here
feedInterests = interest //<-- Here
self.listaInteresesTableView.reloadData()
}
override func viewDidLoad() {
self.listaInteresesTableView.isEditing = true
self.listaInteresesTableView.allowsMultipleSelectionDuringEditing = true
self.listaInteresesTableView.delegate = self
self.listaInteresesTableView.dataSource = self
let interesesModelo = InteresesModelo()
interesesModelo.delegate = self
interesesModelo.interestDownload()
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return feedInterests.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "celInterests", for: indexPath) as! InteresesTableViewCell
let interest: DetallesIntereses = feedInterests[indexPath.row] //<-- Here
cell.lblNombreIntereses!.text = interest.nombreInteres
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.selectDeselectCell(tableView: listaInteresesTableView, indexPath: indexPath)
print("Seleccionado")
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
self.selectDeselectCell(tableView: listaInteresesTableView, indexPath: indexPath)
print("Deseleccionado")
}
func selectDeselectCell(tableView: UITableView, indexPath: IndexPath){
self.selectIntereses.removeAll()
selectIntereses.append(feedInterests[indexPath.row]) //<-- Here
print(selectIntereses)
}
#IBAction func seleccionarIntereses(_ sender: Any){
print(selectIntereses)
}
}
I have a tableview loading data from firebase database. When i open my app the data does not populate. it crashes and throws the error index out of range. I am fairly new to xcode and would appreciate the help. i have been given recommendations on how to fix my code with regards to having multiple arrays but everyone that advices forget i'm new and does realize that i am pulling imageurls into my tableview too and dont know how to adopt their recommendations into my code. if i could get help getting passed this error that would be awesome.
import UIKit
import FirebaseAuth
import FirebaseDatabase
class EventsViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var eventsRef: DatabaseReference?
var eventsDatabaseHandle:DatabaseHandle?
var eventsTitles = [String]()
var eventTimestamps = [String]()
var eventsLocations = [String]()
var eventsImages = [UIImage]()
#IBOutlet weak var addEventsButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
adminAuth()
eventsRef = Database.database().reference()
tableView.reloadData()
tableView.transform = CGAffineTransform(rotationAngle: -CGFloat.pi)
tableView.delegate = self
tableView.dataSource = self
eventsDatabaseHandle = eventsRef?.child("Church Events").observe(.childAdded, with: { (snaphot) in
let eventPost = snaphot.value as! [String: Any]
self.eventTimestamps.append(eventPost["eventdate"] as! String)
self.eventsTitles.append(eventPost["eventtitle"] as! String)
self.eventsLocations.append(eventPost["eventlocation"] as! String)
let task = URLSession.shared.dataTask(with: URL(string: eventPost["ImageUrl"] as! String)!) {(data, response, error) in
if let image: UIImage = UIImage(data: data!) {
self.eventsImages.append(image)
}
}
task.resume()
self.tableView.reloadData()
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return eventsTitles.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "events") as! EventsTableViewCell
// let image = eventsImages[indexPath.row]
cell.flyerImages.image? = eventsImages[indexPath.row] **<- index out of range**
cell.eventTitle.text! = eventsTitles[indexPath.row]
cell.eventDate.text! = eventTimestamps[indexPath.row]
cell.eventLocation.text! = eventsLocations[indexPath.row]
cell.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
return cell
}
func adminAuth() {
if (Auth.auth().currentUser!.displayName != "Neil Leon") {
self.addEventsButton.tintColor = UIColor.clear
self.addEventsButton.isEnabled = false
}
else{
self.addEventsButton.isEnabled = true
}
}
}``````
At the moment reloadData is called the image array is empty and causes the crash
Use a custom struct and reload the table view after the image data has been received
struct Event {
let title, timestamp, location : String
var image : UIImage?
}
var events = [Event]()
...
eventsDatabaseHandle = eventsRef?.child("Church Events").observe(.childAdded, with: { (snaphot) in
let eventPost = snaphot.value as! [String: Any]
let event = Event(title: eventPost["eventtitle"] as! String,
timestamp: eventPost["eventdate"] as! String,
location: eventPost["eventlocation"] as! String,
image: nil)
let task = URLSession.shared.dataTask(with: URL(string: eventPost["ImageUrl"] as! String)!) { data, _, error in
if let error = error {
print(error)
} else {
event.image = UIImage(data: data!)
DispatchQueue.main.async {
self.events.append(event)
self.tableView.reloadData()
}
}
}
task.resume()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return events.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "events", for: indexPath) as! EventsTableViewCell
let event = events[indexPath.row]
cell.flyerImages.image = event.image
cell.eventTitle.text = event.title
cell.eventDate.text = event.timestamp
cell.eventLocation.text = event.location
cell.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
return cell
}
I have a tableview loading data from firebase database. When I open my app the data does not populate. when I create a new post I can see the tableview cells modifying like changed were made but the post doesn't populate. I can't figure it out.
import UIKit
import FirebaseAuth
import FirebaseDatabase
class EventsViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var eventsRef: DatabaseReference?
var eventsDatabaseHandle:DatabaseHandle?
var eventsTitles = [String]()
var eventTimestamps = [String]()
var eventsLocations = [String]()
var eventsImages = [UIImage]()
#IBOutlet weak var addEventsButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
adminAuth()
eventsRef = Database.database().reference()
tableView.reloadData()
tableView.transform = CGAffineTransform(rotationAngle: -CGFloat.pi)
tableView.delegate = self
tableView.dataSource = self
eventsDatabaseHandle = eventsRef?.child("Church Events").observe(.childAdded, with: { (snaphot) in
let eventPost = snaphot.value as! [String: Any]
self.eventTimestamps.append(eventPost["eventdate"] as! String)
self.eventsTitles.append(eventPost["eventtitle"] as! String)
self.eventsLocations.append(eventPost["eventlocation"] as! String)
let task = URLSession.shared.dataTask(with: URL(string: eventPost["ImageUrl"] as! String)!) {(data, response, error) in
if let image: UIImage = UIImage(data: data!) {
self.eventsImages.append(image)
}
}
self.tableView.reloadData()
task.resume()
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return eventsImages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "events") as! EventsTableViewCell
let image = eventsImages[indexPath.row]
cell.flyerImages.image! = image
cell.eventTitle.text! = eventsTitles[indexPath.row]
cell.eventDate.text! = eventTimestamps[indexPath.row]
cell.eventLocation.text! = eventsLocations[indexPath.row]
cell.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
tableView.reloadData()
return cell
}
func adminAuth() {
if (Auth.auth().currentUser!.displayName != "Neil Leon") {
self.addEventsButton.tintColor = UIColor.clear
self.addEventsButton.isEnabled = false
}
else{
self.addEventsButton.isEnabled = true
}
}
}
image of empty tableview
]
So the code below is not tested as I don't have firebase setup currently.
However, observing childAdded... the documentation says it will pass all of the current records in the database at first and will then just post new additions. So all you need to do is loop through them, setup your tableView data source and reload the table.
Rather than use multiple arrays for values I've created an array of ChurchEvent objects instead.
struct ChurchEvents {
let title: String
let location: String?
let date: Date?
let imageUrlString: String?
init(dict: [String: Any]) {
self.title = dict["title"] as String
self.location = dict["location"] as? String
// etc
}
}
var events = [ChurchEvents]()
eventsDatabaseHandle = eventsRef?.child("Church Events").observe(.childAdded, with: { snapshot in
let list = snapshot.value as? [[String : AnyObject]]
let newEvents = list.map { ChurchEvent(dict: $0) }
events.append(newEvents)
tableView.reloadData()
}
Other improvements you could make:
class EventsTableViewCell: UICollectionViewCell {
func configure(with event: ChurchEvent {
eventDate.text = event.date
eventTitle.text = event.title
eventLocation.text = event.location
// etc
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "events") as! EventsTableViewCell
let event = events[indexPath.row]
cell.configure(with: event)
return cell
}
I am new to programming in swift. I am trying to make an app which downloads student data from firebase database. I am unable to get the app running. This is my JSON file:
{"classA":[
{
"name": "Student1",
"USN": "1DS16CS095"
},
{
"name": "student2",
"USN": "1DS16CS065"
},
{
"name":"student3",
"USN":"1DS16CS047"
}
]
}
This is my code to download the above JSON file and put it in tableView. Modelstudent is my class where I have my variables name and USN stored. and marksstudentlistTableViewCell is the class I am using to manipulate the labels of my prototype cell.
import UIKit
import Firebase
import FirebaseDatabase
struct StudentData {
let StudentName : String
let StudentUSN : String
}
class marksstudentlist: UIViewController, UITableViewDelegate, UITableViewDataSource{
var FetchedStudentIDs = [StudentData]()
#IBOutlet weak var tableView: UITableView!
var ref: DatabaseReference!
var _selectedsub: Int!
var selectedsub: Int {
get {
return _selectedsub
}set {
_selectedsub = newValue
}
}
override func viewDidLoad() {
tableView.delegate = self
tableView.dataSource = self
if(classselected==0){
ref = Database.database().reference().child("classA")
ref.observe(DataEventType.value, with: { (snapshot) in
if snapshot.childrenCount>0{
for students in snapshot.children.allObjects as! [DataSnapshot]{
let studentObject = students.value as? [String: String]
let studentname = studentObject?["name"]
let studentUSN = studentObject?["USN"]
let student = Modelstudent(name: studentname , USN: studentUSN)
self.FetchedStudentIDs.insert(StudentData(StudentName :(studentname as? String!)! , StudentUSN : (studentUSN as? String!)! ) , at: 0)
}
self.tableView.reloadData()
}
})
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return FetchedStudentIDs.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "marksstudentlistcell", for: indexPath) as! marksstudentlistTableViewCell
cell.namelbl.text = FetchedStudentIDs[indexPath.row].StudentName// Student name comes from Firebase
cell.USNlbl.text = FetchedStudentIDs[indexPath.row].StudentUSN
return cell
}
#IBAction func backbtn(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
}
Using Swift Struct is the best way to get your firebase data inside the tableView,
Let's Get started
on your TableView swift file, above the class and stuff paste this
Struct StudentData {
let StudentObject : String // If it's
let StudentName : String // It returns a string right?
let StudentUSN : String
// do the others.
}
okay so then create a var just down below the class call it
var FetchedStudentIDs = [StudentData]()
then you got the reading method from firebase
ref = Database.database().reference().child("classA")
ref.observe(DataEventType.value, with: { (snapshot) in
if snapshot.childrenCount>0{
self.studentslist.removeAll()
for students in snapshot.children.allObjects as! [DataSnapshot]{
let studentObject = students.value as? [String: AnyObject]
let studentname = studentObject?["name"]
let studentUSN = studentObject?["USN"]
let student = Modelstudent(name: studentname as! String?, USN: studentUSN as! String?) //storing in Modelstudent class
self.
FetchedStudentIDs.insert(StudentData(studentname:StudentName as! String , studentUSN : StudentUSN as! String ) , at: 0) // Student name comes from the struct Above, do the others as this
}
self.tableView.reloadData() // make sure you call this
}
})
return your tableView
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return FetchedStudentIDs.count
}
your CellForRowAtIndexPath
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableViewCell
cell.exampleLabel.text = FetchedStudentIDs[Indexpath.row].StudentName// Student name comes from Firebase
return cell
}
Hope this Helps