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)
}
Related
I'm new to Swift and need your help.
I created a TableViewController with a custom cell.
Also I created a "add" Button in navigation bar to add a new value to my tableview.
Saving the values in Core Data and fetch them in viewWillAppear.
When pressing the add button a UIAlertController shows up which i had customized like i needed. I added a cancel action and a ok action but when i press the ok button from the alert the new value don't shows up in my tableview. I have to switch to an other viewcontroller that the tableview shows it.
I added groupsTableView.reloadData()on different points in my code but cant get it to work.
Hope someone can help me out!
Code from MasterViewController:
import UIKit
import CoreData
class MasterViewController: UITableViewController {
var groups: [Groups] = []
#IBOutlet weak var groupsTableView: UITableView!
var groupsTextField: UITextField?
override func viewDidLoad() {
super.viewDidLoad()
groupsTableView.delegate = self
groupsTableView.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewWillAppear(_ animated: Bool) {
// Core date initialization
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest: NSFetchRequest<Groups> = Groups.fetchRequest()
do {
groups = try managedContext.fetch(fetchRequest)
groupsTableView.reloadData()
} catch {
// TODO: error handling
print("Could not fetch groups")
}
navigationItem.leftBarButtonItem = editButtonItem
let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(insertNewObject))
navigationItem.rightBarButtonItem = addButton
}
// MARK: - add new Group
#objc func insertNewObject() {
let addButtonAlert = UIAlertController(title: "Neue Gruppe", message: "Füge eine neue Gruppe deiner Liste hinzu", preferredStyle: .alert)
addButtonAlert.addTextField { (UITextField) in
self.groupsTextField = UITextField
self.groupsTextField?.placeholder = "Name der Gruppe"
self.groupsTextField?.clearButtonMode = .whileEditing
}
let okAction = UIAlertAction(title: "Hinzufügen", style: .default, handler: addNewGroup)
let cancelAction = UIAlertAction(title: "Abbrechen", style: .cancel, handler: nil)
addButtonAlert.addAction(okAction)
addButtonAlert.addAction(cancelAction)
self.present(addButtonAlert, animated: true, completion: nil)
}
func addNewGroup(_:UIAlertAction) -> Void {
let group = Groups(groupId: UUID(), groupTitle: groupsTextField!.text ?? "")
do {
try group?.managedObjectContext?.save()
groupsTableView.reloadData()
} catch {
// TODO: error handling
print("Could not save group")
}
}
// MARK: - Segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let destination = segue.destination as? DetailViewController,
let selectedRow = self.groupsTableView.indexPathForSelectedRow?.row else {
return
}
destination.group = groups[selectedRow]
destination.title = groups[selectedRow].groupTitle
}
// MARK: - delete Group
func deleteGroup(at indexPath: IndexPath) {
let group = groups[indexPath.row]
guard let managedContext = group.managedObjectContext else {
return
}
managedContext.delete(group)
do {
try managedContext.save()
groups.remove(at: indexPath.row)
groupsTableView.deleteRows(at: [indexPath], with: .automatic)
} catch {
//TODO: error handling
print("Could not delete Group")
groupsTableView.reloadRows(at: [indexPath], with: .automatic)
}
}
// MARK: - Table View
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return groups.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = groupsTableView.dequeueReusableCell(withIdentifier: "GroupsTableViewCell", for: indexPath) as! GroupsTableViewCell
let object = groups[indexPath.row]
cell.groupTitleLabel?.text = object.groupTitle
return cell
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
deleteGroup(at: indexPath)
}
}
}
Add group item to your groups array and after that reload your tableview as shown below:-
func addNewGroup(_:UIAlertAction) -> Void {
let group = Groups(groupId: UUID(), groupTitle: groupsTextField!.text ?? "")
do {
try group?.managedObjectContext?.save()
self.groups.append(group)
groupsTableView.reloadData()
} catch {
// TODO: error handling
print("Could not save group")
}
}
So I am creating this todo app. It is on a tableview. And each cell when tapped or clicked should take you to a ask.com search to search for the item if it is not clear what the Item is. I have gotten it to search on ask.com with the code I have written. But the issue that I have coming up is that after the first click. The page doesn't refresh or update. I can click on the second or third cell and it wont search for what is in that particular cell. It keeps showing what is in the first cell. and won't change. I have tried clearing cells and it still keeps going through as the old search from the first time. Ex: Cell 1 : cleaning products Cell 2: a bike Cell 3: dog. No matter what cell I pick it will only show cleaning product. Even if I change cell 1 to another item. How can I fix this. Source code would be amazing.
import UIKit
class NewTableViewController: UITableViewController, NewCellDelegate, {
var news:[News]!
override func viewDidLoad() {
super.viewDidLoad()
loadData()
func loadData() {
news = [News]()
news = DataManager.loadAll(News.self).sorted(by: {$0.createdAt < $1.createdAt})
self.tableView.reloadData()
}
#IBAction func Save(_ sender: Any) {
let addAlert = UIAlertController(title: "ADD", message: "TODO", preferredStyle: .alert)
addAlert.addTextField { (textfield:UITextField) in
textfield.placeholder = "TODO"
}
addAlert.addAction(UIAlertAction(title: "Save", style: .default, handler: { (action:UIAlertAction) in
guard let title = addAlert.textFields?.first?.text else {return}
let newsave = News(title: title, completed: false, createdAt: Date(), itemIdentifier: UUID())
newsave.saveItem()
self.news.append(newsave)
let indexPath = IndexPath(row: self.tableView.numberOfRows(inSection: 0), section: 0)
self.tableView.insertRows(at: [indexPath], with: .automatic)
}))
addAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
self.present(addAlert, animated: true, completion: nil)
}
};
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return news.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! NewTableViewCell
cell.delegte = self
let news = self.news[indexPath.row]
cell.label.text = news.title
return cell
}
func tableView(tableView: UITableView, didSelectRowAt indexPath:
NSIndexPath) {
//getting the index path of selected row
let indexPath = tableView.indexPathForSelectedRow
//getting the current cell from the index path
let currentCell = tableView.cellForRow(at: indexPath!)! as UITableViewCell
//getting the text of that cell
let TODO = currentCell.textLabel!.text
let appURL = NSURL(string: "https://www.ask.com/web?q=\
(TODO))&o=0&qo=homepageSearchBox)")
if UIApplication.shared.canOpenURL(appURL! as URL) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(appURL! as URL, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(appURL! as URL)
}
}
}
}
I think it relates to search content change this
let currentCell = tableView.cellForRow(at: indexPath!)! as UITableViewCell
to
let currentCell = tableView.cellForRow(at: indexPath!) as! NewTableViewCell
&&& change this
let TODO = currentCell.textLabel!.text
to
let TODO = currentCell.label.text
I am working on an app which has TableViewCells in three Scenes. I would like to populate by using an AlertBox to take the "item tapped" and pass that data to a TableViewCell in another Scene..
Here is the first Scene I have the alertBox in
class SupplementScene: UIViewController, UITableViewDataSource,
UITableViewDelegate
{
#IBOutlet weak var lblSupplement: UILabel!
#IBOutlet weak var sbSupplement: UISearchBar!
#IBOutlet weak var tvSupplement: UITableView!
let stackScene = StackScene()
var suppArray:[Supplement] = [Supplement]()
var stackArray:[Stack] = [Stack]()
let cellReuseIdentifier = "cell1"
var count:Int = 0
// create the managed object context
// it is used for CoreData. Must be created in the ViewController
let managedObjectContext = (UIApplication.shared.delegate
as! AppDelegate).persistentContainer.viewContext
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view.
applyDesign()
tvSupplement.dataSource = self
tvSupplement.delegate = self
suppArray = CoreDataHandler.getAllSupplementObjects(managedObjectContext: managedObjectContext)
}
// functions from the protocols
// number of rows in table view
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return suppArray.count
}
// create a cell for each table view row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
// create a new cell if needed or reuse an old one
// had to change cell to cell1 since identified was used before
let cell:SupplementTableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell1") as! SupplementTableViewCell!
// set the text from the array into the cells labels
cell.lblName.text = suppArray[indexPath.row].name
cell.backgroundColor = UIColor.darkGray
cell.lblName.textColor = UIColor.white
return cell
}
// method to run when table view cell is tapped
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
// print("You tapped cell number \(indexPath.row).")
// get the tableViewCell
let cell = tableView.cellForRow(at: indexPath) as! SupplementTableViewCell
// print the text property in the label in the tableViewCell
print("TableView tapped = \(cell.lblName.text!)")
updateStack(name: cell.lblName.text!)
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// update function
func updateStack(name:String)
{
// get the objectby name
_ = CoreDataHandler.getSupplementByName(managedObjectContext: managedObjectContext, name: name)!
// create an Alert with a textFields for all ContactBusiness fields
let alertController = UIAlertController(title: "Udpate \(name)",
message: "",
preferredStyle: UIAlertControllerStyle.alert)
// create a default action for the Alert
let defaultAction = UIAlertAction(
title: "Ok",
style: UIAlertActionStyle.default,
handler: {(alertAction: UIAlertAction!) in
// get the input from the alert controller
// put the user updated fields back in the contactBusiness object
// save the managedObject
CoreDataHandler.addStackObject(managedObjectContext: self.managedObjectContext)
// get all Contacts from CoreData
self.stackArray = CoreDataHandler.getAllStackObjects(managedObjectContext: self.managedObjectContext)
// reload the data into the TableView
self.tvSupplement.reloadData()
self.stackScene.tvStack.reloadData()
})
let cancelAction = UIAlertAction(
title: "Cancel",
style: UIAlertActionStyle.cancel,
handler:nil)
// add the action to the Alert
alertController.addAction(defaultAction)
alertController.addAction(cancelAction)
// display the Alert
present(alertController, animated: true, completion: nil)
}
}
and now the class in which I want to retrieve the data from.
class StackScene: UIViewController, UITableViewDelegate,
UITableViewDataSource
{
#IBOutlet weak var lblStack: UILabel!
#IBOutlet weak var sbStack: UISearchBar!
#IBOutlet weak var tvStack: UITableView!
let cellReuseIdentifier = "cell3"
var stackArray:[Stack] = [Stack]()
// var suppArray:[Supplement] = [Supplement]()
var count : Int = 0
// create the managed object context
// it is used for CoreData. Must be created in the ViewController
let managedObjectContext = (UIApplication.shared.delegate
as! AppDelegate).persistentContainer.viewContext
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view.
tvStack.delegate = self
tvStack.dataSource = self
stackArray = CoreDataHandler.getAllStackObjects(managedObjectContext: managedObjectContext)
// doing this to apply the design changes to the app
applyDesign()
}
// functions from the protocols
// number of rows in table view
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return stackArray.count
}
// create a cell for each table view row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
// create a new cell if needed or reuse an old one
// had to change cell to cell1 since identified was used before
let cell:StackTableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell3") as! StackTableViewCell!
// set the text from the array into the cells labels
cell.lblName.text = stackArray[indexPath.row].name
cell.backgroundColor = UIColor.darkGray
cell.lblName.textColor = UIColor.white
return cell
}
#IBAction func longPressDelete(_ sender: UILongPressGestureRecognizer)
{`enter code here`
// get the location of the long press in the table
// location returns a CGPoint
let p = sender.location(in: tvStack)
// get indexPath at location of the long press
let indexPath = tvStack.indexPathForRow(at: p)
// if nil, selected the table, not a row
// if not nil, check for long touch began.
// we are not interested in the long touch ended
if indexPath == nil
{
print("Long press on table view, not row.")
}
else if (sender.state == UIGestureRecognizerState.began)
{
// get the tableViewCell
let cell = tvStack.cellForRow(at: indexPath!) as! StackTableViewCell
print("TableView LongPress = \(cell.lblName.text!), Pos: \(indexPath!.row)")
// call the deleteContact function
deleteContact(name: cell.lblName.text!, position: indexPath!.row)
// do something for long press here
}
else if(sender.state == UIGestureRecognizerState.ended)
{
print("Long press ended")
}
}
// function to display alert and confirm deletion
func deleteContact(name:String, position:Int)
{
// create the AlertController
let alertController = UIAlertController(title: "Delete",
message: name + ": Confirm Delete",
preferredStyle: UIAlertControllerStyle.alert)
// create a default action button
let deleteAction = UIAlertAction(title: "Delete",
style: UIAlertActionStyle.default,
handler: {(alertAction: UIAlertAction!) in
// remove from array
self.stackArray.remove(at: position)
// reload the tableView
self.tvStack.reloadData()
// remove from CoreData
CoreDataHandler.deleteStackByName(managedObjectContext: self.managedObjectContext, name: name)
})
// Alerts can only have one cancel action
// It is bolded and always comes last
let cancelAction = UIAlertAction(title: "Cancel",
style: UIAlertActionStyle.cancel,
handler: nil)
// Add the actions to the Alert
alertController.addAction(deleteAction)
alertController.addAction(cancelAction)
// present or display the Alert
present(alertController, animated: true, completion: nil)
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I know I am not using a segue to perform this type of transaction. But I have searched these threads and google for any info and am struggling on what is the best solution to this. The alertBox pops and has the tapped item in the textField, but I cannot get the data from that alertBox textField to populate into the TableViewCell of the "stack" class.
Let me know if there is any other info I can provide. I am still learning Swift.
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.
I have a UICollectionViewCell on VC1, The cell contains, a image, a label, and 3 buttons.
When i click on the cell. That triggers my didSelectItemAtIndexPath to take me to an Edit item screen.
How can i access each button and relate it to the cell i am clicking on?
So if i have added 6 cells, and i click on cell 1, button 1, it takes me to a bio page with info on that person. if i click on cell 2 button 1, it brings me to same bio VC but with different info related to the cell i clicked on.
My confusion lies in where or how to set this up?
Thank you!
import UIKit
import Parse
class TrainersViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, AddNewTrainerViewControllerDelegate {
var trainers: [TrainerArray]
required init?(coder aDecoder: NSCoder) {
trainers = [TrainerArray]()
super.init(coder: aDecoder)
loadTrainerItems()
}
//connection to the collection view
#IBOutlet weak var collectionView: UICollectionView!
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
print("selected")
saveTrainerItems()
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return trainers.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("TrainerCell", forIndexPath: indexPath)
var buttonOne = cell.viewWithTag(10)
buttonOne = indexPath.row
let trainer = trainers[indexPath.row]
configureTrainerForCell(cell, withTrainerArray: trainer)
return cell
}
func configureTrainerForCell(cell: UICollectionViewCell, withTrainerArray trainer: TrainerArray) {
if trainer.trainerImage == nil {
let label = cell.viewWithTag(1000) as! UILabel
trainer.trainerImage = UIImage(named: "defaultImage")
label.text = trainer.name
} else {
let image = cell.viewWithTag(2000) as! UIImageView
image.image = trainer.trainerImage
let label = cell.viewWithTag(1000) as! UILabel
label.text = trainer.name
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//This method adds a new trainer to the trainer array
func addNewTrainerViewController(controller: AddNewTrainerViewController, didFinishAddingItem item: TrainerArray) {
let newRowIndex = trainers.count
trainers.append(item)
let indexPath = NSIndexPath(forRow: newRowIndex, inSection: 0)
let indexPaths = [indexPath]
collectionView.insertItemsAtIndexPaths(indexPaths)
dismissViewControllerAnimated(true, completion: nil)
saveTrainerItems()
}
func addNewTrainerViewController(controller: AddNewTrainerViewController, didFinishDeletingItem item: TrainerArray) {
if let index = trainers.indexOf(item) {
let indexPath = NSIndexPath(forRow: index, inSection: 0)
let indexPaths = [indexPath]
if let _ = collectionView.cellForItemAtIndexPath(indexPath) {
self.trainers.removeAtIndex(index)
self.collectionView.deleteItemsAtIndexPaths(indexPaths)
}
}
dismissViewControllerAnimated(true, completion: nil)
saveTrainerItems()
}
//This Method Edits a Trainer
func addNewTrainerViewController(controller: AddNewTrainerViewController, didFinishEditingItem trainer: TrainerArray) {
if let index = trainers.indexOf(trainer) {
let indexPath = NSIndexPath(forRow: index, inSection: 0)
if let cell = collectionView.cellForItemAtIndexPath(indexPath){
configureTrainerForCell(cell, withTrainerArray: trainer)
}
}
saveTrainerItems()
dismissViewControllerAnimated(true, completion: nil)
}
func addNewTrainerViewControllerDidCancel(controller: AddNewTrainerViewController) {
dismissViewControllerAnimated(true, completion: nil)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "AddTrainer" {
let navigationController = segue.destinationViewController as! UINavigationController
let controller = navigationController.topViewController as! AddNewTrainerViewController
controller.delegate = self
} else if segue.identifier == "EditTrainer" {
let navigationController = segue.destinationViewController as! UINavigationController
let controller = navigationController.topViewController as! AddNewTrainerViewController
controller.delegate = self
if let indexPath = collectionView.indexPathForCell(sender as! UICollectionViewCell) {
controller.trainerToEdit = trainers[indexPath.row]
}
}
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
}
func documentsDirectory() -> String {
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
return paths[0]
}
func dataFilePath() -> String {
return (documentsDirectory() as NSString)
.stringByAppendingPathComponent("Trainers.plist")
}
func saveTrainerItems() {
let data = NSMutableData()
let archiver = NSKeyedArchiver(forWritingWithMutableData: data)
archiver.encodeObject(trainers, forKey: "TrainersArray")
archiver.finishEncoding()
data.writeToFile(dataFilePath(), atomically: true)
}
func loadTrainerItems() {
let path = dataFilePath()
if NSFileManager.defaultManager().fileExistsAtPath(path) {
if let data = NSData(contentsOfFile: path) {
let unarchiver = NSKeyedUnarchiver(forReadingWithData: data)
trainers = unarchiver.decodeObjectForKey("TrainersArray") as! [TrainerArray]
unarchiver.finishDecoding()
}
}
}
#IBAction func logOut(sender: AnyObject) {
let alert = UIAlertController(title: "Are You Sure You Want To Log Out?", message: "Please Enter Your Username", preferredStyle: UIAlertControllerStyle.Alert)
alert.addTextFieldWithConfigurationHandler { (textField) -> Void in
}
alert.addAction(UIAlertAction(title: "Log Out", style: UIAlertActionStyle.Default, handler: { (action) -> Void in
let textF = alert.textFields![0] as UITextField
if textF.text! != PFUser.currentUser()?.username {
self.displayGenericAlert("Incorrect Username!", message: "Please Enter a Valid Username")
} else if textF.text! == PFUser.currentUser()?.username {
PFUser.logOut()
_ = PFUser.currentUser()
self.dismissViewControllerAnimated(true, completion: nil)
}
}))
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Default, handler: { (action) -> Void in
if action == true {
self.dismissViewControllerAnimated(false, completion: nil)
}}))
self.presentViewController(alert, animated: true, completion: nil)
}
func displayGenericAlert(title: String, message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Ok", style: .Default, handler: { (action) -> Void in
}))
self.presentViewController(alert, animated: true, completion: nil)
}
#IBAction func bioSegueButton(sender: AnyObject) {
}
}
You can get data from your array from index 'indexpath.row' .
You can add a tag to the button that corresponds to the index of the cell it is in. In the cellForItemAtIndexPath you would add something like...
button.tag = indexPath.row
And in the selector for the button you can access it...
func buttonSelector(sender: UIButton) {
let index = sender.tag
let trainer = trainers[index]
}
EDIT:
A more complete version of what you might do in cellForRowAtIndexPath:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("TrainerCell", forIndexPath: indexPath) as? MyCollectionViewCell
var buttonOne = cell?.button1
buttonOne?.tag = indexPath.row
let trainer = trainers[indexPath.row]
configureTrainerForCell(cell, withTrainerArray: trainer)
return cell!
}
Your collection view cell class:
class MyCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var button1: UIButton! // connect this to the button in your interface builder's collection view cell.
// Do the same for any other subviews.
}
Also in interface builder change the class of the collection view cell prototype to this new custom class.