I have a view and delete button in each row of the table viewenter image description here. I would like to click each to delete or view the item inside. I've added two button functions, but how to know when I click delete or view details row number 2 then it will remove or view the row 2?
#IBAction func deleteBtn(_ sender: Any) {
let refreshAlert = UIAlertController(title: "Message", message: "Are you sure you want to remove this item from the cart?", preferredStyle: UIAlertControllerStyle.alert)
refreshAlert.addAction(UIAlertAction(title: "No", style: .cancel, handler: { (action: UIAlertAction!) in
}))
refreshAlert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { (action: UIAlertAction!) in
self.removeCartAPI()
let buttonTag = (sender as AnyObject).tag
//self.navigationController?.popViewController(animated: true)
}))
present(refreshAlert, animated: true, completion: nil)
}
#IBAction func viewDetailsBtn(_ sender: Any) {
let vc: ParcelSendParcelSummaryViewController? = self.storyboard?.instantiateViewController(withIdentifier: "pSummaryVC") as?ParcelSendParcelSummaryViewController
self.navigationController?.pushViewController(vc!, animated: true)
}
I recommend the delegate pattern here, something like this:
protocol YourCellNameDelegate {
func didTapDelete(at cell: YourCellName)
func didTapView(at cell: YourCellName)
}
class YourCellName: UITableViewCell {
weak var delegate: YouCellNameDelegate?
...
#IBAction func didTouchDeleteButton(sender: Any) {
delegate?.didTapDelete(at: self)
}
// same for did tap view
}
class ParcelSendParcelSummaryViewController {
....
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(
withIdentifier: "Your identifier",
for: indexPath
) as? YourCellName {
cell.delegate = self
}
}
}
extension ParcelSendParcelSummaryViewController: YouCellNameDelegate {
func didTapDelete(at cell: YourCellName){
if let index = tableView.indexPathForCellView(cell: cell) {
tableView.beginUpdates()
tableView.deleteRows(at: index, with: .automatic)
// Make sure you delete the item from the data source here, between begin and end updates
tableView.endUpdates()
}
}
func didTapView(at cell: YourCellName) {
// same as above, with your view logic
}
}
Related
Essentially I have a tableView that is separated by sections.
The tableView allows for multiple row selections and displays an accessory .checkmark on all selected rows.
If the user begins to select rows under one section and try to select a different row under a different I would like for an alert message to appear and the selection not be made.
The following is the code so far:
import UIKit
class TableViewController: UITableViewController {
var Name = UserDefaults.standard.string(forKey: "name")!
var sections = [Section]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cellId")
navigationController?.navigationBar.prefersLargeTitles = true
fetchJSON()
self.tableView.allowsMultipleSelection = true
}
}
Implement UITableViewDelegate method tableView(_:willSelectRowAt:).
Use map(_:) to get the sections from indexPathsForSelectedRows.
Check if the indexPath's section in tableView(_:willSelectRowAt:) is contained in the previously obtained sections array using contains(_:)
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
if let sections = tableView.indexPathsForSelectedRows?.map({ $0.section }) {
if !sections.contains(indexPath.section) {
//Show Alert here....
let alert = UIAlertController(title: "Alert..!!", message: "You're selection row from Section:\(indexPath.section)", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
return nil
}
}
return indexPath
}
tableView(_:willSelectRowAt:) return value : An index-path object
that confirms or alters the selected row. Return an NSIndexPath object
other than indexPath if you want another cell to be selected. Return
nil if you don't want the row selected.
Check out tableView(_:willSelectRowAt:). You can return nil from this delegate method to prevent a row from being selected.
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
if let
indexPathsForSelectedRows = tableView.indexPathsForSelectedRows, indexPathsForSelectedRows.count > 0,
indexPath.section != indexPathsForSelectedRows[0].section
{
// If there is at least one row already selected
// And the section of that row is different than the section of the row about to be selected
// Don't allow the selection
let alertController = UIAlertController(title: "Whoops!", message: "Don't do that!", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Okay", style: .default, handler: nil))
present(alertController, animated: true, completion: nil)
return nil
}
// There's either nothing selected yet or the section of this row is the same as those already selected
return indexPath
}
I think, you need to create an extra logic for it, per example.
You can put in one array all the indexPath you selected, and compare always the first item(indexPath) with each item you will go to add, the indexPath contains the property Section so just need to compare if the section are equals or not, if it isn't, you show de alert you want, after that you deselect the item manually.
var selectedItems = [IndexPath]()
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let firstIndexPath = selectedItems.first{
//If the section with the first element selected and the new selection are not equals, display your message
if indexPath.section != firstIndexPath.section{
let alert = UIAlertController(title: "Warning", message: "Message", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: {
a in
self.selectIndexPath(indexPath: indexPath, isSelected: false)
}))
//You can put it here if you prefer not wait for the closure for realize the deselection
//self.selectIndexPath(indexPath: indexPath, isSelected: false)
self.present(alert, animated: true, completion: nil)
return
}
self.selectIndexPath(indexPath: indexPath, isSelected: true)
}else{
self.selectIndexPath(indexPath: indexPath, isSelected: true)
}
}
func selectIndexPath(indexPath: IndexPath, isSelected: Bool){
tableView.cellForRow(at: indexPath)?.accessoryType = isSelected ? .checkmark : .none
if isSelected{
selectedItems.append(indexPath)
}else{
selectedItems.removeAll(where: {$0 == indexPath})
tableView.deselectRow(at: indexPath, animated: true)
}
let section = sections[indexPath.section]
let item = section.items[indexPath.row]
// for all selected rows assuming tableView.allowsMultipleSelection = true
}
From what I understand you can do the following:
Add a bool value on your section struct to check if you have any item selected
struct Section {
let name : String
var hasItemsSelected: Bool
let items : [Portfolios]
}
change your didSelectRowAt
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//when you select a row check if there is another row selected on another section by checking if the hasItemsSelected is true on all other sections
//1. create an array containing all section besides the current section
let allOtherSections = sections
.enumerated()
.filter { $0.offset != indexPath.section }
.map { $0.element }
//2. if allOtherSections does not have seledcted items
if allOtherSections.allSatisfy { $0.hasItemsSelected == false } {
self.sections[indexPath.section].hasItemsSelected = true
tableView.cellForRow(at: ind exPath)?.accessoryType = .checkmark
} else {
let numberOfSelectedRowsOnCurrentIndexPath = tableView.indexPathsForSelectedRows?.enumerated().filter { $0.offset != 1 }.count
if numberOfSelectedRowsOnCurrentIndexPath == 0 {
self.sections[indexPath.section].hasItemsSelected = false
}
tableView.deselectRow(at: indexPath, animated: true)
//show the alert here
let alertController = UIAlertController(title: "Title", message: "Your message", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alertController, animated: true, completion: nil)
}
}
I hope I have helped you
This question already has an answer here:
Issue with UITableView: Action only works every second time
(1 answer)
Closed 3 years ago.
I already the TableView for displaying JSON Values from API. But the result of the click does not match the existing title, but it is worth the title from the previous click. More clearly see the picture
Code InfoViewCell.swift
This code for cell in tableview
import UIKit
class InfoViewCell: UITableViewCell {
#IBOutlet weak var imgInfo: UIImageView!
#IBOutlet weak var lblInfo: UILabel!
#IBOutlet weak var lblBerita: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Code Info.swift this code for models
class Info {
var id_informasi: Int?
var tgl_informasi: String?
var judul: String?
var berita: String?
var foto: String?
init(id_informasi:Int?,judul: String?,berita: String?,foto: String?) {
self.id_informasi = id_informasi
self.judul = judul
self.berita = berita
self.foto = foto
}
}
Code InfoViewController.swift
import UIKit
import Alamofire
import AlamofireImage
class InformasiViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableInfo: UITableView!
var activityIndicator:UIActivityIndicatorView = UIActivityIndicatorView()
var infoes = [Info]()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return infoes.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellInfo", for: indexPath) as! InfoViewCell
//getting the hero for the specified position
let inpo: Info
inpo = infoes[indexPath.row]
//displaying values
cell.lblInfo.text = inpo.judul
cell.lblBerita.text = inpo.berita
//displaying image
Alamofire.request(inpo.foto!).responseImage { response in
debugPrint(response)
if let image = response.result.value {
cell.imgInfo.image = image
}
}
return cell
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let info: Info
info = infoes[indexPath.row]
//building an alert
let alertController = UIAlertController(title: info.judul, message: "", preferredStyle: .alert)
//the confirm action taking the inputs
let confirmAction = UIAlertAction(title: "Enter", style: .default) { (_) in
}
//the cancel action doing nothing
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (_) in }
//adding action
alertController.addAction(confirmAction)
alertController.addAction(cancelAction)
//presenting dialog
present(alertController, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
let defaultValues = UserDefaults.standard
let token = defaultValues.string(forKey: "token")
//the Web API URL
let URL_GET_DATA = "https://api.landslidepad.com/api/admin_desa/informasi_penting?token=" + token!
activityIndicator.center = self.view.center
activityIndicator.hidesWhenStopped = true
activityIndicator.style = UIActivityIndicatorView.Style.gray
view.addSubview(activityIndicator)
activityIndicator.startAnimating()
//fetching data from web api
Alamofire.request(URL_GET_DATA, method: .get).responseJSON
{
response in
//printing response
print(response)
self.activityIndicator.stopAnimating()
//getting the json value from the server
if let result = response.result.value {
let jsonData = result as! NSDictionary
//if there is no error
if((jsonData.value(forKey: "message") as! String == "Sukses!")){
//getting the user from response
let user = jsonData.value(forKey: "values") as! NSArray
for i in 0..<user.count{
//adding hero values to the hero list
self.infoes.append(Info(
id_informasi: (user[i] as AnyObject).value(forKey: "id_informasi") as? Int,
judul: (user[i] as AnyObject).value(forKey: "judul") as? String,
berita: (user[i] as AnyObject).value(forKey: "berita") as? String,
foto: (user[i] as AnyObject).value(forKey: "foto") as? String
))
}
//displaying data in tableview
self.tableInfo.reloadData()
}else{
let alert = UIAlertController(title: "Ada yang salah?", message: "Silahkan Ulangi Kembali!.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: nil))
alert.addAction(UIAlertAction(title: "No", style: .cancel, handler: nil))
self.present(alert, animated: true)
}
}
}
self.tableInfo.reloadData()
// Do any additional setup after loading the view, typically from a nib.
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I already tried to create func tableview didDeselectRowAt indexPath,
but the value I want to display is not in line with my expectations. I will pass this value to the detailed view
Thanks
When you click on a row, that row is selected — and the previous selected row is deselected. Well, you have implemented didDeselect, so the previous selected row is displayed. Instead, implement didSelect.
instead of this
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let info: Info
info = infoes[indexPath.row]
//building an alert
let alertController = UIAlertController(title: info.judul, message: "", preferredStyle: .alert)
//the confirm action taking the inputs
let confirmAction = UIAlertAction(title: "Enter", style: .default) { (_) in
}
//the cancel action doing nothing
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (_) in }
//adding action
alertController.addAction(confirmAction)
alertController.addAction(cancelAction)
//presenting dialog
present(alertController, animated: true, completion: nil)
}
please use this
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let info: Info
info = infoes[indexPath.row]
//building an alert
let alertController = UIAlertController(title: info.judul, message: "", preferredStyle: .alert)
//the confirm action taking the inputs
let confirmAction = UIAlertAction(title: "Enter", style: .default) { (_) in
}
//the cancel action doing nothing
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (_) in }
//adding action
alertController.addAction(confirmAction)
alertController.addAction(cancelAction)
//presenting dialog
present(alertController, animated: true, completion: nil)
}
I am trying to call a UIAlertController from within my UITtableViewCell when my function is called. It gives me an error saying present is not available. I understand it's not within a ViewController. I am looking for an approach to access it.
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
let tapGestureShareImageView = UITapGestureRecognizer(target: self, action: #selector(self.shareImageTouchUpInside))
shareImageView.addGestureRecognizer(tapGestureShareImageView)
shareImageView.isUserInteractionEnabled = true
}
#objc func shareImageTouchUpInside() {
showAction()
}
func showAction() {
let alertController = UIAlertController(title: "Action Sheet", message: "What do you like to do", preferredStyle: .alert)
let okButton = UIAlertAction(title: "Done", style: .default, handler: { (action) -> Void in
print("Ok button tapped")
})
let deleteButton = UIAlertAction(title: "Skip", style: .destructive, handler: { (action) -> Void in
print("Delete button tapped")
})
alertController.addAction(okButton)
alertController.addAction(deleteButton)
present(alertController, animated: true, completion: nil)
}
You can try to use delegate
protocol AlertShower{
func showAlert(TableCustomCell)
}
class TableCustomCell: UITableViewCell {
var delegate: AlertShower?
#IBAction func showClicked(_ sender: UIButton) {
self.delegate?.alertShower(sender:self)
}
}
in the VC
class viewController: UIViewController, AlertShower {
override func viewDidLoad() {
super.viewDidLoad()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = areaSettTable.dequeueReusableCell(withIdentifier:CellIdentifier1) as! TableCustomCell
cell.delegate = self
return cell
}
func showAlert(sender:TableCustomCell) {
// show alert here
}
}
Present is only available to ViewControllers. You are going to have to redirect the touch event to your view controller. The most common way of doing this would be having a delegate property in your UITableViewCell.
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID276
I ran into a similar problem myself when creating a custom activity indicator from a subclassed UIView. What I did was create the 'show' function (in the subclass) and pass in a UIViewController parameter, like so:
public func play(inView view: UIViewController) {
//Perform action here
view.present(alertController, animated: true, completion: nil)
}
Simply call it in your view controller like so:
CustomClass.play(inView: self)
Hopefully this helps!
I am using table view in my Swift project. The problem is with table view cell index path value. When the table gets loaded initially, the index values are ok. But as soon as I scroll my table view the cell index paths change and the ids I get from a data array are wrong. Googling results that it is because of reusable cell like thing. Here's my view controller code:
//
// ProjectsController.swift
// PMUtilityTool
//
// Created by Muhammad Ali on 9/23/16.
// Copyright © 2016 Genetech Solutions. All rights reserved.
//
import UIKit
class ProjectsController: UIViewController {
//MARK: Properties
var ProjectsArray: Array<Project> = []
#IBOutlet weak var ProjectsTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.navigationController?.navigationBarHidden = true
// Remove indenting of cell
if self.ProjectsTableView.respondsToSelector(Selector("setSeparatorInset:")) {
self.ProjectsTableView.separatorInset = UIEdgeInsetsZero
}
if self.ProjectsTableView.respondsToSelector(Selector("setLayoutMargins:")) {
self.ProjectsTableView.layoutMargins = UIEdgeInsetsZero
}
self.ProjectsTableView.layoutIfNeeded()
// Get projects
getProjects()
}
override func viewWillAppear(animated: Bool) {
self.navigationController?.navigationBarHidden = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
// MARK: - TableView Datasource
func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return self.ProjectsArray.count
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// print("clicked")
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> ProjectTableViewCell {
print(indexPath.row)
let cell = tableView.dequeueReusableCellWithIdentifier("ProjectViewCell", forIndexPath:indexPath) as! ProjectTableViewCell
// Remove indenting of cell
cell.separatorInset = UIEdgeInsetsZero
cell.layoutMargins = UIEdgeInsetsZero
// Set project name
cell.ProjectName.text = "\((indexPath.row)+1). \(self.ProjectsArray[indexPath.row].ProjectName)"
// Set action button
cell.ActionButton.tag = indexPath.row
cell.ActionButton.addTarget(self, action: #selector(ProjectsController.projectActions(_:)), forControlEvents: .TouchUpInside)
return cell
}
func reloadTableViewAfterDelay()
{
ProjectsTableView.performSelector(#selector(UITableView.reloadData), withObject: nil, afterDelay: 0.1)
}
#IBAction func projectActions(sender: UIButton) {
let index = sender.tag
let optionMenu = UIAlertController(title: nil, message: self.ProjectsArray[index].ProjectName, preferredStyle: .ActionSheet)
// Report Progress
let reportProgressAction = UIAlertAction(title: "Report Progress", style: .Default, handler: {
(alert: UIAlertAction!) -> Void in
self.performSegueWithIdentifier("ShowReportProgress", sender: sender)
})
// Cancel
let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel, handler: {
(alert: UIAlertAction!) -> Void in
})
optionMenu.addAction(reportProgressAction)
optionMenu.addAction(cancelAction)
self.presentViewController(optionMenu, animated: true, completion: nil)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let button = sender where segue.identifier == "ShowReportProgress" {
let upcoming: ReportProgressController = segue.destinationViewController as! ReportProgressController
print(self.ProjectsArray[button.tag].ProjectId)
upcoming.ProjectId = self.ProjectsArray[button.tag].ProjectId
upcoming.ProjectName = self.ProjectsArray[button.tag].ProjectName
}
}
// MARK: Get Projects Function
func getProjects() -> Void
{
var params = Dictionary<String,AnyObject>()
params = ["user_id":CFunctions.getSession("id")]
WebServiceController.getAllProjects(params){ (type, response, message) -> Void in
if (type == ResponseType.kResponseTypeFail)
{
// Show Error
let alert = UIAlertController(title: "Error(s)", message:"Unable to load projects.", preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "OK", style: .Default) { _ in })
self.presentViewController(alert, animated: true){}
}
else
{
//debugPrint(response)
if(response.count>0)
{
self.ProjectsArray = response
}
else
{
// Show Error
let alert = UIAlertController(title: "Error(s)", message:"No projects found.", preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "OK", style: .Default) { _ in })
self.presentViewController(alert, animated: true){}
}
self.reloadTableViewAfterDelay()
}
}
}
}
I want each cell's index value to be intact whether I scroll down or up.
In your iPhone screen, for example you can see 5 cells at a time.
So, first time, when you load tableView cellForRowAtindexPAth method will be called for first 5
cells.
"As you mentioned, first time you are loading and tableView indexes
are correct."
Now when you scroll down, cellForRowAtIndexPath method will be called
for only 6 and 7.
"Till this time everything works ok, as you mentioned. AS you can see overall indexpath as intact 1,2, 3,4,5,6,7."
*Dark cells are currently visible on your screen.
Now when you scroll up {2 cells}. Now you can see the current visible cell's on your screen are 1,2,3,4,5.
Here, cellForRowAtIndexPath method will be called for ONLY cells numbered 2,1.
Because cell numbers 3,4,5 are already loaded/visible in your screen.
So, your Print log will be 1,2,3,4,5,6,7,2,1.
You should add UITableViewDelegate and UITableViewDataSource to your class. and set it ProjectsTableView.delegate = self in viewDidLoad.
You need to get indexPath of button using tableViewCell's hierarchy.
#IBAction func projectActions(sender: UIButton) {
let button = sender as! UIButton
let view = button.superview!
let cell = view.superview as! UITableViewCell
let indexPath = self.reloadTableViewAfterDelay.indexPathForCell(cell)
let index = indexPath.row
}
if let button = sender where segue.identifier == "ShowReportProgress" {
let upcoming: ReportProgressController = segue.destinationViewController as! ReportProgressController
let view = button.superview!
let cell = view.superview as! UITableViewCell
let indexPath = self.reloadTableViewAfterDelay.indexPathForCell(cell)
let index = indexPath.row
print(self.ProjectsArray[index].ProjectId)
upcoming.ProjectId = self.ProjectsArray[index].ProjectId
upcoming.ProjectName = self.ProjectsArray[index].ProjectName
}
Need to get indexPath of button using tableViewCell's than used below code
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellId: NSString = "Cell"
let cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellId as String)! as UITableViewCell
let ActionButton : UIButton = cell.viewWithTag(10) as! UIButton
ActionButton.addTarget(self, action: #selector(ViewController.projectActions(_:)), forControlEvents: UIControlEvents.TouchUpInside)
return cell
}
#IBAction func projectActions(sender: UIButton) {
let buttonPosition = sender.convertPoint(CGPointZero, toView: self.tableView)
let indexPath = self.tableView.indexPathForRowAtPoint(buttonPosition)
print(indexPath?.row)
}
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
}
}
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.