cellForRowAt in UITableView isn't being called

The cellForRowAt method in line 240 is to return cells in a UITableView with a word from an array. None of the method's print statements appear in the debug console, but all others do (contents of words array, and ones that state where in the program its currently executing. Originally I had one userDefaults dictionary and the cellForRowAt method worked. The addNewWord and startTest methods completely work, so the words array is being created from the userDefaults dictionary when there are 2 keys. I believe the problem to be with cellForRowAt, although that's the same code as when it worked with 1 userDictionay key. Ever since 2 userDictionary keys were used, this problem has happened.


//
//  ViewController.swift
//  Polyglot
//
//  Created by Alan Dripps on 01/02/2019.
//  Copyright © 2019 Alan Dripps. All rights reserved.
//

import UIKit

class ViewController: UITableViewController {

    var words = [String]()
    var choosenLanguage = String()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let titleAttributes = [NSAttributedString.Key.font: UIFont(name: "AmericanTypewriter", size: 22)!]
        navigationController?.navigationBar.titleTextAttributes = titleAttributes
        title = "POLYGLOT"
        
        let chooseLanguage = UIAlertController(title: "Vocabulary Tutor", message: "Choose a Language", preferredStyle: .actionSheet)
        
        let germanButton = UIAlertAction(title: "German", style: .default, handler: { (action) -> Void in
            self.choosenLanguage = "german"
            print("Choosen language is: \(self.choosenLanguage)")
            self.loadInitialValues()
        })
        
        let frenchButton = UIAlertAction(title: "French", style: .default, handler: { (action) -> Void in
            self.choosenLanguage = "french"
            print("Choosen language is: \(self.choosenLanguage)")
            self.loadInitialValues()
        })
        
        chooseLanguage.addAction(germanButton)
        chooseLanguage.addAction(frenchButton)
        
        self.navigationController!.present(chooseLanguage, animated: true, completion: nil)
    }
    
    func loadInitialValues(){
        if let defaults = UserDefaults(suiteName: "group.co.uk.tirnaelectronics.polyglot") {
            if choosenLanguage == "german" {
                print("In load initial values german choosen")
                if let savedWords = defaults.object(forKey: "germanWords") as? [String] {
                    words = savedWords
                } else {
                    saveInitialGermanValues(to: defaults)
                }
            } else if choosenLanguage == "french" {
                print("In load initial values french choosen")
                if let savedWords = defaults.object(forKey: "frenchWords") as? [String] {
                    words = savedWords
                } else {
                    saveInitialFrenchValues(to: defaults)
                }
            }
            print(words)
            print("Number of words: \(words.count)")
        }
    }
        
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addNewWord))
        
        navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .play, target: self, action: #selector(startTest))
        navigationItem.backBarButtonItem = UIBarButtonItem(title: "End Test", style: .plain, target: nil, action: nil)
    }
    
    @objc func startTest() {
        guard let vc = storyboard?.instantiateViewController(withIdentifier: "Test") as? TestViewController else { return }
        vc.words = words
        
        navigationController?.pushViewController(vc, animated: true)
    }
    
    func saveInitialGermanValues(to defaults: UserDefaults) {
        words.append("Bear::Baissespekulant")
        words.append("Camel::Kamel")
        words.append("Cow::Rind")
        words.append("Fox::Fuchs")
        words.append("Goat::Geiß")
        words.append("Monkey::Affe")
        words.append("Pig::Schwein")
        words.append("Rabbit::Karnickel")
        words.append("Sheep::Schaf")
        print(words)
        defaults.set(words, forKey: "germanWords")
        print("At end of saveInitialGermanValues")
    }
    
    func saveInitialFrenchValues(to defaults: UserDefaults) {
        words.append("Bear::l'ours")
        words.append("Camel::le chameau")
        words.append("Cow::la vache")
        words.append("Fox::le renard")
        words.append("Goat::la chèvre")
        words.append("Monkey::le singe")
        words.append("Pig::le cochon")
        words.append("Rabbit::le lapin")
        words.append("Sheep::le mouton")
        print(words)
        defaults.set(words, forKey: "frenchWords")
        print("At end of saveInitialFrenchValues")
    }
    
    @objc func addNewWord() {
        // create our alert controller
        let ac = UIAlertController(title: "Add new word", message: nil, preferredStyle: .alert)
        
        // add two text fields, one for English and one for French
        ac.addTextField { textField in
            textField.placeholder = "English"
        }
        
        ac.addTextField { (textField) in
            switch self.choosenLanguage {
            case "german":
                    textField.placeholder = "German"
            case "french":
                    textField.placeholder = "French"
            default:
                print("Choose a language")
            }
        }
        
        // create an "Add Word" button that submits the user's input
        let submitAction = UIAlertAction(title: "Add Word", style: .default) { [unowned self, ac] (action: UIAlertAction!) in
            // pull out the English and French words, or an empty string if there was a problem
            let firstWord = ac.textFields?[0].text ?? ""
            let secondWord = ac.textFields?[1].text ?? ""
            
            // submit the English and French word to the insertFlashcard() method
            self.insertFlashcard(first: firstWord, second: secondWord)
        }
        
        // add the submit action, plus a cancel button
        ac.addAction(submitAction)
        ac.addAction(UIAlertAction(title: "Cancel", style: .cancel))
        
        // present the alert controller to the user
        present(ac, animated: true)
    }
    
    func insertFlashcard(first: String, second: String) {
        guard first.count > 0 && second.count > 0 else { return }
        
        let newIndexPath = IndexPath(row: words.count, section: 0)
        
        words.append("\(first)::\(second)")
        tableView.insertRows(at: [newIndexPath], with: .automatic)
        
        saveWords()
    }
    
    override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
        let delete  = UITableViewRowAction(style: .default, title: "Delete") { action, indexPath in
            self.words.remove(at: indexPath.row)
            tableView.beginUpdates()
            tableView.deleteRows(at: [indexPath], with: .left)
            tableView.endUpdates()
            // delete item at indexPath
        }
        
        let edit = UITableViewRowAction(style: .normal, title: "Edit") { (action, indexPath) in
            let ac = UIAlertController(title: "Edit word", message: nil, preferredStyle: .alert)
            
            // add two text fields, one for English and one for French
            ac.addTextField { textField in
                let word = self.words[indexPath.row]
                let split = word.components(separatedBy: "::")
                let englishWord = split[0]
                textField.placeholder = "\(englishWord)"
            }
            
            ac.addTextField { (textField) in
                let word = self.words[indexPath.row]
                let split = word.components(separatedBy: "::")
                let foreignWord = split[1]
                textField.placeholder = "\(foreignWord)"
            }
            
            // create an "Add Word" button that submits the user's input
            let submitAction = UIAlertAction(title: "Edit Word", style: .default) { [unowned self, ac] (action: UIAlertAction!) in
                // pull out the English and French words, or an empty string if there was a problem
                let firstWord = ac.textFields?[0].text ?? ""
                let secondWord = ac.textFields?[1].text ?? ""
            
                guard firstWord.count > 0 && secondWord.count > 0 else { return }
                // edit item at indexPath
                self.words.remove(at: indexPath.row)
                self.words.insert("\(firstWord)::\(secondWord)", at: indexPath.row)
                tableView.beginUpdates()
                tableView.deleteRows(at: [indexPath], with: .automatic)
                tableView.insertRows(at: [indexPath], with: .automatic)
                tableView.endUpdates()
                
                self.saveWords()
            }
            
            // add the submit action, plus a cancel button
            ac.addAction(submitAction)
            ac.addAction(UIAlertAction(title: "Cancel", style: .cancel))
            
            // present the alert controller to the user
            self.present(ac, animated: true)
        }
        
        edit.backgroundColor = UIColor.blue
        
        return [delete, edit]
    }
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return words.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        print("In cellForRowAt function")
        let cell = tableView.dequeueReusableCell(withIdentifier: "Word", for: indexPath)
        
        let word = words[indexPath.row]
        let split = word.components(separatedBy: "::")
        
        print(split[0])
        
        cell.textLabel?.text = split[0]
        
        cell.detailTextLabel?.text = ""
        
        print(cell)
        return cell
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        
        if let cell = tableView.cellForRow(at: indexPath) {
            if cell.detailTextLabel?.text == "" {
                let word = words[indexPath.row]
                let split = word.components(separatedBy: "::")
                cell.detailTextLabel?.text = split[1]
            } else {
                cell.detailTextLabel?.text = ""
            }
        }
    }
    
    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        words.remove(at: indexPath.row)
        tableView.deleteRows(at: [indexPath], with: .automatic)
        saveWords()
    }
    
    func saveWords() {
        if let defaults = UserDefaults(suiteName: "group.co.uk.tirnaelectronics.polyglot") {
            if choosenLanguage == "german" {
                defaults.set(words, forKey: "germanWords")
            } else if choosenLanguage == "french" {
                defaults.set(words, forKey: "frenchWords")
            }
        }
    }
}

So, you don't get the print from line 227 ?


First thing to check: did you set delegate and datasource for the tableView ? Should be if you created tableViewController in IB, but better check.


Second check: what do you get from print at line 60 and 61 ?

They've been set from when 1 userDefault dictionary key was in use, and I just double checked.

Correct, there's no print output from line 227. I created tableView datasource and delegate by ctrl->click and drag from Table View to View Controller in interface builder document outline.


From line 60 I got the contents of the words array (the string values that are appended to the words array in saveInitialGermanValues method. Line 61 prints: Number of words: 9.

Accepted Answer

I cannot find any lines calling `tableView.reloadData()`.

You need one after you update your `words`, maybe putting one inside `loadInitialValues()` would be the easiest.

Why does it now need a reload of tableView data, when using more than one userDefaults dictionary key. I'm surmizing that userDefaults has something to do with this?

userDefaults has something to do with this?

No, UserDefaults has nothing to do with this behavior.


With your code and settings, iOS displays initial contents of the table view immediately after `viewDidLoad()` is finished, when `words` is still empty.

You know that you need to call `reloadData()` when the whole contents of the data source are replaced.

(Please remember, `viewDidLoad()` finishes before `loadInitialValues()` is called.)


Give `words` an initial value:

var words: [String] = ["a", "b", "c"]


This may illustrate what's happening more clearly.

I understand now. It's nothing more than a beginner's inexperience!


Much appreciated.

cellForRowAt in UITableView isn't being called
 
 
Q