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)
}
}
Here is a screenshot of the test database I made.
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(placeLabel.text)
dbRef!.child("comment").child(key).setValue(commentTextField.text)
dbRef!.child("rating").child(key).setValue(ratingControl.rating)
Hacking the Database. To edit the database manually, try:
- open http://console.firebase.google.com
- select your app
- open database option
- add a new node with the right key
- 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:
Repository: https://github.com/HassanSE/FirebaseSample
Related Topics
Clearing Uiwebview's Cache in Swift
How to Block Users on Firebase in a Social Media App? for iOS
How to Use Dispatch Groups to Wait to Call Multiple Functions That Depend on Different Data
Convert Float Value to String in Swift
Swift "Where" Array Extensions
How to Append a Character to a String in Swift
Swift: How to Change a Property's Value Without Calling Its Didset Function
Include an Extension for a Class Only If iOS11 Is Available
How to Change the Uinavigationbar Title's Position
Timedmetadata' Deprecated. Another Method? <Updated>
How to Resolve This Build Issue - Cannot Assign to Property: 'Date' Is a Get Only Property
Key-Value Coding (Kvc) with Array/Dictionary in Swift
How Is a Type-Erased Generic Wrapper Implemented
What Is the Role of Avcapturedevicetype.Builtindualcamera
How to Get the Realy Fixed Device-Id in Swift
How to Hide the Navigationbar When Embedding Swiftui in Uikit