Search code examples
iosswiftuitableviewuiviewcell

Tableviews "cellForRowAt" is never called after adding real data


My program is built the following:

I have a UIViewController SingleEventViewController to layout and handle data of an event. One of its elements is an UIView TeilnehmerTableView with an UITableView participantsTableView in it.

As the name says its there for displaying the users, participating at the Event. First I implemented it without "real" data. I hardcoded the users array and built the table view based on it. This worked perfectly.

But since I implemented the fetchUsers Method and filled the array with that information, the tableView does not appear at all. Obviously it doesn't display anything when I first load the page, because numberOfRowsInSection returns 0. But even after the fetch, when I reload the tableview, and the numberOfRowsInSection returns a value, the tableview is empty. While debugging I noticed that the method for populating the cells cellForRowAt never gets called. I have no clue what's going on since it all worked before using real data.

Can someone tell me what's going on?

import UIKit
import Firebase

class TeilnehmerTableView: UIView, UITableViewDelegate, UITableViewDataSource {

var parentVC: SingleEventViewController?
var users = [User]()

let participantsTableView: UITableView = {
    let ctv = UITableView()
    return ctv
}()

override init(frame: CGRect) {
    super.init(frame: frame)
    self.backgroundColor = UIColor.green
    setupTableView()
    setupViews()
    confBounds()
    fetchUsers()
}

func setupTableView() {
    participantsTableView.delegate = self
    participantsTableView.dataSource = self
    participantsTableView.register(TeilnehmerTVCell.self, forCellReuseIdentifier: TeilnehmerTVCell.reuseIdentifier)
    participantsTableView.tintColor = .white
}

func setupViews() {
    addSubview(participantsTableView)
}

func confBounds(){
    participantsTableView.anchor(top: topAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
}


func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    print("Users: ",users.count)
    return users.count
}

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    print("heightForRowAt Called")
    return 44
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    print("cellForRowAt Called")
    let row = tableView.dequeueReusableCell(withIdentifier: TeilnehmerTVCell.reuseIdentifier, for: indexPath) as! TeilnehmerTVCell
    row.user = users[indexPath.item]
    return row
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.deselectRow(at: indexPath, animated: false)
}

//MARK: - Methods
func fetchUsers() {
    print("Fetching users..")

    let ref = Database.database().reference().child("users")
    ref.observeSingleEvent(of: .value, with: { (snapshot) in
        guard let dictionaries = snapshot.value as? [String: Any] else { return }

        dictionaries.forEach({ (key, value) in

            if key == Auth.auth().currentUser?.uid {
                print("Found myself, omit from list")
                return
            }

            guard let userDictionary = value as? [String: Any] else { return }

            let user = User(uid: key, dictionary: userDictionary)
            self.users.append(user)
        })

        self.users.sort(by: { (u1, u2) -> Bool in
            return u1.username.compare(u2.username) == .orderedAscending
        })
        DispatchQueue.main.async( execute: {
            self.participantsTableView.reloadData()
        })


    }) { (err) in
        print("Failed to fetch users for search:", err)
    }
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}
}

This is the code for the parent the SingleEventVC:

import UIKit
import MapKit

class SingleEventViewController: UIViewController {

var thisEvent: Event
var eventName: String?
var eventDescription: String?
var eventLocation: CLLocationCoordinate2D?
var eventStartingDate: Date?
var eventFinishingDate: Date?
var eventNeedsApplication: Bool?

let padding: CGFloat = 20

//MARK: - GUI Objects
let scrollView: UIScrollView = {
    let view = UIScrollView()
    return view
}()

let teilnehmerLabel: UILabel = {
    let label = UILabel()
    label.font = UIFont.systemFont(ofSize: 20)
    label.text = "Teilnehmer"
    label.textColor = .black
    return label
}()

let teilnehmerTV: TeilnehmerTableView = {
    let tvt = TeilnehmerTableView()
    tvt.backgroundColor = .brown
    return tvt
}()

let buttonDividerView: UIView = {
    let tdv = UIView()
    tdv.backgroundColor = UIColor.gray
    return tdv
}()

let participateButton: UIButton = {
    let button = UIButton()
    button.backgroundColor = CalendarSettings.Colors.buttonBG
    button.setTitle("Teilnehmen", for: .normal)
    button.setTitleColor(CalendarSettings.Colors.darkRed, for: .normal)
    return button
}()

init(event: Event) {
    thisEvent = event
    super.init(nibName: nil, bundle: nil)
    setupDefaultValues()
}

override func viewDidLoad() {
    super.viewDidLoad()
    applyDefaultValues()
    setupNavBar()
    setupViews()
    confBounds()
    getSnapshotForLocation()
}


//MARK: - Setup
func setupDefaultValues() {
    eventName = thisEvent.eventName
    eventDescription = thisEvent.eventDescription
    eventLocation = thisEvent.eventLocation
    eventStartingDate = thisEvent.eventStartingDate
    eventFinishingDate = thisEvent.eventFinishingDate
    eventNeedsApplication = thisEvent.eventNeedsApplication
}

func applyDefaultValues() {
    titleLabel.text = eventName
    descLabel.text = eventDescription
    if let start = eventStartingDate, let finish = eventFinishingDate {
        timeLabel.text = "Von \(start.getHourAndMinuteAsStringFromDate()) bis \(finish.getHourAndMinuteAsStringFromDate())"
    }

    if let location = eventLocation {
        locationLabel.text = getStringFromLocation(location: location)
        mapLabel.text = getStringFromLocation(location: location)
    }
    if let date = eventStartingDate {
        dateLabel.text = formatDate(date: date)
    }
}

func setupNavBar() {
    self.navigationItem.title = "Event"
}

func setupViews() {
    view.addSubview(scrollView)
    view.addSubview(participateButton)
    participateButton.addTarget(self, action: #selector (buttonClick), for: .touchUpInside)
    view.addSubview(buttonDividerView)

    scrollView.addSubview(teilnehmerLabel)
    scrollView.addSubview(teilnehmerTV)
    teilnehmerTV.parentVC = self
}

func confBounds(){
    let tabbarHeight = self.tabBarController?.tabBar.frame.height ?? 0
    let tableviewHeight: CGFloat = CGFloat(teilnehmerTV.users.count) * 44

    participateButton.anchor(top: nil, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: tabbarHeight, paddingRight: 0, width: 0, height: 50)
    buttonDividerView.anchor(top: nil, left: view.leftAnchor, bottom: participateButton.topAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0.5)

    scrollView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: buttonDividerView.topAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)



    teilnehmerLabel.anchor(top: descLabel.bottomAnchor, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 20, paddingLeft: padding, paddingBottom: 0, paddingRight: padding, width: 0, height: 0)
    teilnehmerTV.anchor(top: teilnehmerLabel.bottomAnchor, left: view.leftAnchor, bottom: scrollView.bottomAnchor, right: view.rightAnchor, paddingTop: 5, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: tableviewHeight)
}

override func viewDidLayoutSubviews() {
    let objHeight = titleLabel.frame.height + locationLabel.frame.height + dateLabel.frame.height + timeLabel.frame.height + mapView.frame.height + notizenLabel.frame.height + descLabel.frame.height + teilnehmerLabel.frame.height + teilnehmerTV.frame.height
    let paddingHeight = 10+0+50+padding+20+5 - 15
    print(objHeight, paddingHeight)

    scrollView.contentSize = CGSize(width: view.frame.width, height: objHeight+paddingHeight)
}

//MARK: - Methods
@objc func buttonClick() {
    teilnehmerTV.participantsTableView.reloadData()
    print(teilnehmerTV.participantsTableView.numberOfRows(inSection: 0))
    scrollView.reloadInputViews()
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}
}

Solution

  • It's because you have not added TeilnehmerTableView to the view in SingleEventViewController when you make the fetch request - you make the request during initialization of TeilnehmerTableView. Try calling fetchUsers from SingleEventViewController after you have added TeilnehmerTableView to the view; it should then work.