Bryan Miller Bryan Miller - 3 months ago 16
iOS Question

how to get data to save to specific category in tableview instead on all categories?

I have a groceryList app
when you add an item to the category list it adds to the entire list of categories when is should not!

https://github.com/mrbryankmiller/Grocery-TableView-.git

class GroceryItemsTableViewController: UITableViewController {

//var groceryItem = ["Item1", "Item2", "Item3"]

//var groceryList = ["Breakfast","Lunch", "Dinner"]


@IBOutlet var groceryItemTableView: UITableView!


@IBAction func addGroceryItemButtonPressed(sender: UIBarButtonItem) {

///new way///

let alertController: UIAlertController = UIAlertController(title: "Add Grocery Item", message: "", preferredStyle: .Alert)

//Cancel Button

let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .Cancel) { action -> Void in
//cancel code
}
alertController.addAction(cancelAction)
let saveAction: UIAlertAction = UIAlertAction(title: "Save", style: .Default) { action -> Void in

let textField = alertController.textFields![0]
groceryItem.items.append(textField.text!)
self.tableView.reloadData()

}

alertController.addAction(saveAction)


//Add text field

// alertController.addTextFieldWithConfigurationHandler { (textField) -> Void in
// textField.textColor = UIColor.blackColor()

alertController.addTextFieldWithConfigurationHandler { (textField : UITextField!) -> Void in
textField.placeholder = "Enter an Item"
//alertController.textFields
}

//Present the AlertController
self.presentViewController(alertController, animated: true, completion: nil)
}

override func viewDidLoad() {
super.viewDidLoad()

//self.navigationItem.leftBarButtonItem = self.editButtonItem()

}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}


// MARK: - Table view data source

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows

return groceryItem.items.count

}


override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("groceryItem1", forIndexPath: indexPath)
cell.textLabel!.text = groceryItem.items [indexPath.row]
return cell

}
}

Answer

If you see carefully the declaration of your class groceryItem you have a static array of elements for every item in your grocery list so every time you add a new element it's shared among all the grocery items.

Instead you should have for each grocery item a list associated with each of its items.

You could define a new struct to save for each grocery item its list of item associated like in the following way:

struct GroceryItem {
   var name: String
   var items: [String]
}

The we are going to change a little the code in your GroceryListTableViewController to refactor the code according your new model, so it should be like the following:

GroceryListTableViewController:

class GroceryListTableViewController: UITableViewController, GroceryItemsTableViewControllerProtocol {


    var groceryList = [GroceryItem]()

    @IBAction func addButton(sender: UIBarButtonItem) {

       let alertController: UIAlertController = UIAlertController(title: "Add Grocery Category", message: "", preferredStyle: .Alert)

       //Cancel Button

       let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .Cancel) { action -> Void in
        //cancel code
       }
       alertController.addAction(cancelAction)

       let saveAction: UIAlertAction = UIAlertAction(title: "Save", style: .Default) { action -> Void in

          let textField = alertController.textFields![0]
          self.groceryList.append(GroceryItem(name: textField.text!, items: [String]()))
          self.tableView.reloadData()
       }
       alertController.addAction(saveAction)

       alertController.addTextFieldWithConfigurationHandler { (textField : UITextField!) -> Void in
          textField.placeholder = "Enter an Item"
          //alertController.textFields
        }

       //Present the AlertController
       self.presentViewController(alertController, animated: true, completion: nil)
    }

    override func viewDidLoad() {

       super.viewDidLoad()

       //edit button
       self.navigationItem.leftBarButtonItem = self.editButtonItem()

       groceryList.append(GroceryItem(name: "Breakfast", items: ["Item1", "Item2",  "Item3"]))
       groceryList.append(GroceryItem(name: "Lunch", items: ["Item1", "Item2",  "Item3"]))
       groceryList.append(GroceryItem(name: "Dinner", items: ["Item1", "Item2",  "Item3"]))

    }

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

       return groceryList.count
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
       let cell = tableView.dequeueReusableCellWithIdentifier("prototype1", forIndexPath: indexPath) as UITableViewCell

       cell.textLabel!.text = groceryList [indexPath.row].name

       return cell
    }

   // pass a tableview cell value to navigationBar title in swift//

   override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
      let destinationVC = segue.destinationViewController as! GroceryItemsTableViewController
      let cell = sender as! UITableViewCell

      let idx = self.tableView.indexPathForSelectedRow?.row

      destinationVC.delegate = self
      destinationVC.itemList = groceryList[idx!].items
      destinationVC.navigationItem.title = cell.textLabel?.text
   }

   func didAddGroceryItem(itemName: String) {
      let idx = self.tableView.indexPathForSelectedRow?.row
      groceryList[idx!].items.append(itemName)
   }

   func didRemoveGroceryItem(index: Int) {
    let idx = self.tableView.indexPathForSelectedRow?.row
    groceryList[idx!].items.removeAtIndex(index)
   }  
}

In the above I have refactored all the code regarding the new model, I put only the places where the code change the rest keep the same.

The thing you need to pass the item associated with the cell selected to the another UIViewController and you can do it very easily in your prepareForSegue. For that we need to get the index for the selected cell and pass the elements to the another UIViewController where we have a new array of [String] created as data source to show the items.

The another important point in the code is that the GroceryListTableViewController now implements a new protocol called GroceryItemsTableViewControllerProtocol. This protocol it's the way to notify to GroceryListTableViewController from the GroceryItemsTableViewController every time a new item is added to the list it's called the delegate pattern.

GroceryItemsTableViewController:

protocol GroceryItemsTableViewControllerProtocol: class {

   func didAddGroceryItem(itemName: String)

   func didRemoveGroceryItem(index: Int)
}

class GroceryItemsTableViewController: UITableViewController {

     weak var delegate: GroceryItemsTableViewControllerProtocol?

     var itemList: [String]!

     @IBAction func addGroceryItemButtonPressed(sender: UIBarButtonItem) {

        ///new way///

       let alertController: UIAlertController = UIAlertController(title: "Add Grocery Item", message: "", preferredStyle: .Alert)

       //Cancel Button

       let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .Cancel) { action -> Void in
          //cancel code
       }
       alertController.addAction(cancelAction)


       let saveAction: UIAlertAction = UIAlertAction(title: "Save", style: .Default) { [weak self] action -> Void in

          guard let s = self else { return }

          let textField = alertController.textFields![0]

          s.itemList.append(textField.text!)
          s.delegate?.didAddGroceryItem(textField.text!)
          s.tableView.reloadData()

       }

      alertController.addAction(saveAction)

      alertController.addTextFieldWithConfigurationHandler { (textField : UITextField!) -> Void in
         textField.placeholder = "Enter an Item"
        //alertController.textFields
      }

      //Present the AlertController
      self.presentViewController(alertController, animated: true, completion: nil)
  }

  override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
     return itemList.count
   }


  override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
      let cell = tableView.dequeueReusableCellWithIdentifier("groceryItem1", forIndexPath: indexPath)


      cell.textLabel!.text = itemList[indexPath.row]
      return cell

  }

  override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
    if editingStyle == .Delete {
        // Delete the row from the data source
        itemList.removeAtIndex(indexPath.row)
        delegate?.didRemoveGroceryItem(indexPath.row)
        tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
    } else if editingStyle == .Insert {
        // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
    }
  }
}

EDIT: To handle properly the deletion you should create a new delegate method no notify the GroceryListTableViewController that a item has been deleted and then delete it properly and you can see in the updated code above.

I hope this help you.

Comments