1
votes

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.

2

2 Answers

1
votes

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.

0
votes

Well - viewDidLoad is loaded only for the first time controller loads his view (not sure about rotation).

If you really need - you can reload tableView in viewWillAppear - but I wouldn't do this.

Instead of

actsCollection.append(appendAct)
dismiss(animated: true, completion: nil)

create a method on the first controller like addObjectToList(appendAct) and in that method, just easily append object to your list array and reload tableView after adding.

You will be reloading tableView only when you really add something to your list and not every time controller appears, you also don't need notification observer.

EDIT - UPDATE

What is this?

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)
}

I mean - what is shit.AddObjects? Shit is defined as tableView - but you have to call this method on instance of your controller.

Another thing - change your setup from sections == number of items with 1 row to be one section with number of rows == number of items. :)