How to Make Firebase Database Data the Data Source for Uicollection View

how can I make firebase database data the data source for UICollection View?

I hope this will get you started. It would be better to have the whole schema of the database, but I have made this based on what I could see from your screenshot. It also seems that having a separate BusinessCategory tree is not needed since you have the category type for each business in the Business tree, although that is completely up to you.

If you want to provide a more complete screenshot of your database (just something that shows the keys and data types), I would be happy to modify this code.

Since I don't know how you update your collection view, I have made it so it returns a Dictionary where the key is the category and the value is an array of bussinesses of that category. This should be an easy format if you are using sections in your collection view.

With regards to the typealias FirebaseRootDictionary, it might need to be modified as I was guessing on what your database schema was.

If you have any questions or problems with this code just put a comment beneath, and I'll try to fix it.

So to get your data:

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.

Business.getBusinesses { (businesses) in

print(businesses)
}
}

Then inside that closure have it update the collection view.

import Foundation
import Firebase

final class Business : NSObject {

typealias FirebaseRootDictionary = Dictionary<String,Dictionary<String,Dictionary<String,String>>>

var name: String

var category: String

var email: String

var imageUrl: String

override var description: String {

return "Business(name: \"\(name)\", category: \"\(category)\", email: \"\(email)\", imageUrl: \"\(imageUrl)\")"
}

init(name:String, category:String, email:String, imageUrl:String) {

self.name = name

self.category = category

self.email = email

self.imageUrl = imageUrl
}

class func getBusinesses(completionHandler:@escaping (_ businesses: BusinessesDictionary)->()) { // -> [Business]

let ref = FIRDatabase.database().reference().child("BusinessCategories")

var businesses = BusinessesDictionary()

ref.observeSingleEvent(of: .value, with: { (snapshot) in

guard let value = snapshot.value as? FirebaseRootDictionary else { return }

let categories = value.keys.sorted()

var arr = [Business]() // Array of businesses for category

for cat in categories {

guard let data = value[cat] else { continue }

let businessKeys = data.keys.sorted()

for key in businessKeys {

guard let businessData = data[key] else { continue }

guard let name = businessData["BusinessName"], let category = businessData["Category"], let email = businessData["email"], let imageUrl = businessData["imageUrl"] else { continue }

let business = Business(name: name, category: category, email: email, imageUrl: imageUrl)

arr.append(business)
}

businesses[cat] = arr

arr.removeAll()
}

completionHandler(businesses)
})
}
}

Edit:

So for the view, you have a tableview with one cell per section/category. The cell has a collection view, which has a collection view cell with a image view and label. So here I have a table view controller that will handle all that.

import UIKit

typealias BusinessesDictionary = Dictionary<String,[Business]> // I have moved this typealias to here instead of inside the Business Model.

class TableViewController: UITableViewController {

var tableData = BusinessesDictionary()

override func viewDidLoad() {
super.viewDidLoad()

self.tableView.register(CategoryCell.self, forCellReuseIdentifier: "cell")

self.tableView.allowsSelection = false

Business.get { (businesses) in

self.tableData = businesses

self.tableView.reloadData()
}
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}

// MARK: - Table view data source

override func numberOfSections(in tableView: UITableView) -> Int {

return self.tableData.keys.count
}

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {

let category = self.tableData.keys.sorted()[section]

return category
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

return 1
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? CategoryCell else { return UITableViewCell() }

// Configure the cell...

let category = self.tableData.keys.sorted()[indexPath.section]

guard let businesses = self.tableData[category] else { return UITableViewCell() }

cell.businesses = businesses

return cell
}

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {

return 120
}
}

The table view cell file.

class CategoryCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource {

var collectionView: UICollectionView!

var businesses = [Business]()

override func layoutSubviews() {

let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()

layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) // You may wan to change this as this is the spacing between cells

layout.itemSize = CGSize(width: 100, height: 120) // You may wan to change this as this is the cell size

layout.scrollDirection = .horizontal

collectionView = UICollectionView(frame: self.bounds, collectionViewLayout: layout)

collectionView.topAnchor.constraint(equalTo: self.topAnchor)

collectionView.leftAnchor.constraint(equalTo: self.leftAnchor)

collectionView.rightAnchor.constraint(equalTo: self.rightAnchor)

collectionView.bottomAnchor.constraint(equalTo: self.bottomAnchor)

collectionView.dataSource = self

collectionView.delegate = self

collectionView.register(BusinessCell.self, forCellWithReuseIdentifier: "businessCell")

collectionView.backgroundColor = .white

self.addSubview(collectionView)
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

return businesses.count
}

func numberOfSections(in collectionView: UICollectionView) -> Int {

return 1
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "businessCell", for: indexPath) as? BusinessCell else { return UICollectionViewCell() }

// Configure the cell

let business = self.businesses[indexPath.row]

cell.nameLabel.text = business.name

cell.imageView.image = UIImage(named: business.imageUrl)

return cell
}
}

This is the collection view cell.

class BusinessCell: UICollectionViewCell {

var imageView: UIImageView!

var nameLabel: UILabel!

override init(frame: CGRect) {
super.init(frame: frame)

imageView = UIImageView(frame: CGRect(x: 20, y: 20, width: 60, height: 60))

imageView.contentMode = .scaleAspectFit

nameLabel = UILabel(frame: CGRect(x: 0, y: 90, width: 100, height: 30))

nameLabel.font = UIFont.systemFont(ofSize: 11)

nameLabel.textAlignment = .center

self.addSubview(imageView)

self.addSubview(nameLabel)
}

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}

Screenshot

Here is a screenshot of the test database I made.

Database Screenshot

How should I retrieve data to the UICollectionViewCell from Firebase?

Reading through the comments it appears you are asking how to read data from Firebase. Since there's no Firebase code in the question, I'm having to guess what you ultimately want to accomplish so I would first direct you to the Firebase Guide Reading and Writing Data as that covers the topic. Then, see Working With Lists

To provide a leg up, here's one possible solution.

Since you have a tableView, that would usually be backed by a tableView datasource. So the concept is to read the data from Firebase and then populate that datasource, then refresh the tableView which will show the objects to the user in the UI.

Let's start with a class to hold the data from Firebase Realtime Database

class ProgramClass {
var description = ""
var image = ""
var title = ""

init(withSnapshot: DataSnapshot) {
self.description = withSnapshot.childSnapshot(forPath: "description").value as? String ?? "No Description"
self.image = withSnapshot.childSnapshot(forPath: "image").value as? String ?? "No Image"
self.title = withSnapshot.childSnapshot(forPath: "title").value as? String ?? "No Title"
}
}

and then a class var to hold those objects

var programsArrayDataSource = [ProgramClass]()

keep in mind that class var will act as the dataSource which backs your tableview. When a change occurs in firebase, the dataSource would be updated via Firebase observe (.childAdded, .childChanged or .childRemoved) and then the tableView reloaded.

To actually read the Firebase structure presented in your question, use the following code. This will both read Firebase as well as populating the dataSource for your tableView.

func fetchPrograms() {
let programsRef = self.ref.child("programs")
programsRef.observeSingleEvent(of: .value, with: { snapshot in
let allPrograms = snapshot.children.allObjects as! [DataSnapshot]
for programSnap in allPrograms {
let aProgram = ProgramClass(withSnapshot: programSnap)
self.programsArrayDataSource.append(aProgram)
}

self.programTableView.reloadData()
})
}

From there you can handle the tableView code; when a row needs refreshing, get that object from the dataSource via the index (row), read the data from that object and populate the tableView cell with it in whatever format you need.

Retrieve data from Firebase before loading view iOS

Once you set the count retrieved from the api, you need to reload the collectionView as below so that your new count is returned from the numberOfItemsInSection method,

ref.child("users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let count = value?["postCount"] as? Int ?? 0
self.count = count

// Reloading collectionView to reflect the new count
self.collectionView.reloadData()

}) { (error) in
print(error.localizedDescription)
}

How can I nest data in Firebase Database?

If you are using for example StreamBuilder and ListView.builder() for getting the categories snapshots if your stream is => Firestore.instance.collection("categories").snapshots() you should be able to get the category which is clicked => snapshot.data.documents[index] and the items => snapshot.data.documents[index]['items'].

My data from firebase database is not loading into my 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
}

Read data from firebase and populate TableViewCell

Assuming your database layout should instead look like this (see comments above):

...
placeLabel
|
-- XXY: "Veranda"
-- YYY: "Dio Con Dio"
rating
|
-- XXX: 4
-- YYY: 1
...

then try this:

private func loadData() {
dbRef!.child("placeLabel").observe(.childAdded) {
(snapshot) in
let label = snapshot.value as! String
self.updatePlace(snapshot.key, label: label)
}
dbRef!.child("rating").observe(.childAdded) {
(snapshot) in
let rating = snapshot.value as! Int
self.updatePlace(snapshot.key, rating: rating)
}
}

private var loadedLabels = [String: String]()
private var loadedRatings = [String: Int]()

private func updatePlace(_ key: String, label: String? = nil, rating: Int? = nil) {
if let label = label {
loadedLabels[key] = label
}
if let rating = rating {
loadedRatings[key] = rating
}
guard let label = loadedLabels[key], let rating = loadedRatings[key] else {
return
}
if let place = Places(name: label, rating: rating) {
places.append(place)
placesTableView.reloadData()
}
}

By the way, you can temporarily hack your database — using Firebase (nice!) web console — if you want to quickly validate the above solution.


Writing to Database. Try the following code to write the nodes in your database (i.e., this code reuses the same key across all place properties):

let key = dbRef!.child("placeLabel").childByAutoId().key

dbRef!.child("placeLabel").child(key).setValue(placeLab‌​el.text)
dbRef!.child("comment").child(key).setValue(commentText‌​Field.text)
dbRef!.child("rating").child(key).setValue(ratingContro‌​l.rating)

Hacking the Database. To edit the database manually, try:

  1. open http://console.firebase.google.com
  2. select your app
  3. open database option
  4. add a new node with the right key
  5. delete the old node

Swift - Firebase : How to search with multi data?


func filterContent(searchText:String, entries:[String]){
self.filteredUsers = self.userArray.filter{ user in
guard let user = user as? User else { return false }
var array:[Bool] = [Bool]()
for key in entries {
let value = user[key] as? String
let entry = value?.lowercased().contains(searchText.lowercased()) ?? false
array.append(entry)
}
return(array.contains(true))
}
tableView.reloadData()
}

How to receive upon clicking tableviewcell specific Firebase data to the viewcontroller?

Here's one way to fix problem you're facing. First, it's better to use a single variable to hold your collection of objects you're displaying in table: i.e.

// Define your model
struct Bar {
var name: String
var image: String
var address: String
}

// Change your variables
var bars = [Bar]()
var selectedBar: Bar?

Second, you'd need to get a single bar object upon cell's selection. i.e.

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
{
selectedBar = bars[indexPath.row]
}

Upon segueing, you can inject it to your detailsVC like so:

details.selectedBar = selectedBar

You've complete selectedBar object to inflate your UI instead of just a String.

Sample Addition: I've created a sample that does the required functionality. Perform the pod installation as you pull the code. Here's what my database looks like:

Sample Image

Repository: https://github.com/HassanSE/FirebaseSample



Related Topics



Leave a reply



Submit