Search code examples
swiftfirebasefirebase-realtime-databaseuicollectionviewnsobject

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


I want to change my model object datasource to firebase. I have a file that serves as the datasource for the UICollection view, homeViewController.swift . homeViewController.swift is a vertically arranged collectionViewCell and each cell has its own horizontally arranged collectionViewcell.

This is the models.swift file

import UIKit
import Firebase

class BusinessCategory: NSObject {

var name: String?
var featurebusiness: [SampleBusinesses]?
var type: String?


static func sampleBusinessCategories() -> [BusinessCategory] {
 let FastfoodCategory = BusinessCategory()
    FastfoodCategory.name = "Fast Food"
    var topFastFood = [SampleBusinesses]()

    let FastfoodApp = SampleBusinesses()
    FastfoodApp.name = "Papa Johns"
    FastfoodApp.imageName = "PJ"
    topFastFood.append(FastfoodApp)
    FastfoodCategory.featurebusiness = topFastFood


    let MobilePhoneCategory = BusinessCategory()
    MobilePhoneCategory.name = "Mobile Phones"
    var topMobilePhoneProvider = [SampleBusinesses]()
    //logic
    let MobilePhoneApp = SampleBusinesses()
    MobilePhoneApp.name = "Verizon"
    MobilePhoneApp.imageName = "verizon"
    topMobilePhoneProvider.append(MobilePhoneApp)
    MobilePhoneCategory.featurebusiness = topMobilePhoneProvider

    return [ FastfoodCategory, MobilePhoneCategory ]

I want to change the object file so that it is populated by my firebase database (BusinessCategories). I have tried many options but I have not been able to figure it out. How do I change my object file from the physically entered data to firebase data?

Here is my Firebase data if it helps. For example "Banks" will be category name and the cell will be populated by all the entries under banks.

Update: What I am trying to achieve is similar to Appstore UI that different categories of apps and each category is a collection view with an horizontal scroll. In my application, Businesses are in different categories listed in firebase and each category is scrollable horizontally.

enter image description here

how do I update my collection view attributes below?

 override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
     let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CategoryCell

        cell.businessCategory = businessCategories?[indexPath.item]

    return cell
}

override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    if let count = businessCategories?.count{

        return count 
    }
    return 0
}

Solution

  • 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