Search code examples
iosarraysswiftuitableviewreloaddata

TableView Only Updates after Screen Rotates


Image of the tableview

I have a tableview with a collection view in each cell, all linked to an array. Each collection view has tags, so when I have stuff in the array from the beginning, all tableview cells and collection view cells appear properly in the app. But when I add an element to the array in the app itself (I have a second view controller with the stuff to do that), it works but the new table view cell only appears after the screen rotates (really odd). I have tried adding an object of the view controller with the table view in the second view controller where I add an element to the array. Then in the second view controller in ViewWillDisappear, I reloadData() through that object like this:

var vc : ViewController? = ViewController()

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    vc?.listOfActs.reloadData()
}

But this results in an EXC_BAD_INSTRUCTION

Then I tried adding self.listOfActs.reloadData() in the prepareForSegue in the view controller with the table view just so that I could see that it at least refreshes the data at some point in time but even that doesn't work when I click on add scene a second time.

UPDATE: New MainViewController

This is the new first view controller with the table view. I renamed it and have implemented the method for adding to array and reloading. It kind of works if I use an if let on the reloadData but then I'm back to square one where it only updates when I rotate the screen. When I get rid of the if let so it can actually try to update the table view, it gives me a Fata error: unexpectedly found a nil while unwrapping.

class MainViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

//The Table View

@IBOutlet var AddActButton: UIButton!

@IBOutlet weak var listOfActs: UITableView!

var sectionTapped : Int?
var indexitemTapped : Int?

override func viewDidLoad() {

    super.viewDidLoad()

    listOfActs.delegate = self

    listOfActs.dataSource = self

}

//Table View Functions

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

    return actsCollection.count
}

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

    return 1
}

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

    let cell = tableView.dequeueReusableCell(withIdentifier: "actCell", for: indexPath) as! ActCell

    cell.setCollectionViewDataSourceDelegate(self, forSection: indexPath.section)

    return cell
}

//Add To Table View

func addObjects(appendage: Act) {
    actsCollection.append(appendage)

    if let shit = listOfActs {

        shit.reloadData()

    }
}

//Header Cell

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {

    let cellHeader = tableView.dequeueReusableCell(withIdentifier: "headerCell") as! HeaderCell

    cellHeader.headerName.text = actsCollection[section].actName

    return cellHeader
}

func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 40
    }

}

//Scene Collection in Act Cell

extension MainViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

    return actsCollection[collectionView.tag].actScenes.count
}

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

    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "sceneCell", for: indexPath) as! SceneCell

    cell.sceneTitle.text = actsCollection[collectionView.tag].actScenes[indexPath.item].sceneTitle

    return cell
}

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {

    sectionTapped = collectionView.tag
    indexitemTapped = indexPath.item

    performSegue(withIdentifier: "showDetail", sender: self)

}

//Segue Prepare

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "showDetail" {

        let detailsVC = segue.destination as! SceneDetailController

        detailsVC.textToAppearInSceneName = actsCollection[sectionTapped!].actScenes[indexitemTapped!].sceneTitle

    }
}

}

UPDATE:New second view controller, the one that adds to the array.

class AddActController: UIViewController, UITextFieldDelegate {

@IBOutlet var sceneLiveName: UILabel!
@IBOutlet var sceneNameTextField: UITextField!

@IBOutlet var sceneDescriptionTextField: UITextField!

@IBOutlet var AddSceneButton: UIButton!

@IBOutlet var cardBounds: UIView!

var newName: String? = ""

@IBOutlet var cardShadow: UIView!

var shit = MainViewController()

override func viewDidLoad() {
    super.viewDidLoad()

    sceneNameTextField.delegate = self

    AddSceneButton.alpha = 0.0

    cardBounds.layer.cornerRadius = 20.0
    cardShadow.layer.shadowRadius = 25.0
    cardShadow.layer.shadowOpacity = 0.4


    // Do any additional setup after loading the view.
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    UIView.animate(withDuration: 0.2){
        self.AddSceneButton.alpha = 1.0
    }
}


@IBAction func exitButton(_ sender: UIButton) {
    dismiss(animated: true, completion: nil)
}

@IBAction func addSceneButton(_ sender: UIButton) {
    if newName == "" {
        sceneLiveName.text = "Enter Something"
        sceneNameTextField.text = ""
    }
    else {
        let appendAct: Act = Act(actName: newName!, actTheme: "Action", actScenes: [Scene(sceneTitle: "Add Act", sceneDescription: "")])
        shit.addObjects(appendage: appendAct)

        dismiss(animated: true, completion: nil)
    }
}

//MARK: textField

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    let text: NSString = (sceneNameTextField.text ?? "") as NSString
    let resultString = text.replacingCharacters(in: range, with: string)
    sceneLiveName.text = resultString
    newName = String(describing: (sceneLiveName.text)!.trimmingCharacters(in: .whitespacesAndNewlines))
    return true
}

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    sceneNameTextField.resignFirstResponder()
    return true
}

/*
// MARK: - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    // Get the new view controller using segue.destinationViewController.
    // Pass the selected object to the new view controller.
}
*/

}

Here is the class for the uitableviewcell that contains its own collection view.

class ActCell: UITableViewCell {

@IBOutlet fileprivate weak var sceneCollection: UICollectionView!

}

extension ActCell {
    func setCollectionViewDataSourceDelegate<D: UICollectionViewDataSource & UICollectionViewDelegate>(_ dataSourceDelegate: D, forSection section: Int) {

    sceneCollection.delegate = dataSourceDelegate
    sceneCollection.dataSource = dataSourceDelegate
    sceneCollection.tag = section
    sceneCollection.reloadData()
    }

}

And here is the model with the user's data including the acts and scenes.

struct Scene {
var sceneTitle: String
var sceneDescription: String
//var characters: [Character]
//var location: Location
}

struct Act {
    var actName: String
    var actTheme: String
    var actScenes : [Scene] = []
}

var actsCollection : [Act] = [
    Act(actName: "dfdsfdsfdsf", actTheme: "Action", actScenes: [Scene(sceneTitle: "Example Act", sceneDescription: "")])
]

Any help is greatly appreciated. Thank you to all.


Solution

  • So if I'm not mistaken I believe the viewDidLoad method gets call during screen rotations. So this explains why it update during so. Now to get it to update without rotating the device, I would add an observer in the notificationCenter to watch for any updates to the tableView then call a #selector to do the reloadData(). So here is an example of this. In the viewDidLoad method add

    NotificationCenter.default.addObserver(self, selector: #selector(refreshTable), name: NSNotification.Name(rawValue: "load"), object: nil)
    

    Then add the method refreshTable()

    func refreshTable() {
    listOfActs.reloadData()
    }
    

    This is basically how I handle keeping the tableView refreshed.