tableview to tableview protocol delegates - swift

I am trying to finish this app that is a form. once filled out a button later will be pushed for print. The first controller has a tableview that has page 1 page 2 page 3. page 1 opens and you fill in all info. when hit save it should take you back to first controller. then when you push print opens the tableview and loads all info.
I am struggling to use structs correctly. also struggling on the save button to the delegate to the print controller page.
my page controller code
class PagesController: UIViewController, UITableViewDelegate {
var pages = ["Page 1","Page 2","Page 3"]
#IBOutlet weak var pagesTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
pagesTableView.delegate = self
pagesTableView.dataSource = self
}
#IBAction func printBtnPressed(_ sender: Any) {
} }
extension PagesController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return pages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PagesCell")
cell?.textLabel?.text = pages[indexPath.row]
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0 {
performSegue(withIdentifier: "ChildInfoSegue", sender: self)
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 90.0
}
}
Heres my form controller
protocol ChildDelegate {
func saveInfoBlock(form: ChildInfo)
}
class ChildInfoTableViewController: UITableViewController {
var formDelegate: ChildDelegate!
var firstNameText: UITextField = UITextField()
var lastNameText: UITextField = UITextField()
var middleNameText: UITextField = UITextField()
//MARK: From ChildInfo.swift STRUCT
var childInfo = [ChildInfo]()
var basicChildInfo = ["Child's First Name","Child's Middle Name","Child's Last Name"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func numberOfSections(in 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 basicChildInfo.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ChildInfoCell") as! ChildInfoTableViewCell
cell.formLabel.text = basicChildInfo[indexPath.row]
return cell
}
//MARK: Segue to printController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let path = self.tableView.indexPathForSelectedRow!
let destViewController = segue.destination as! PrintPageTableViewController
}
//MARK: Cancel Button Pressed
#IBAction func cancelBtnPressed(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
#IBAction func saveBtnPressed(_ sender: Any) {
let firstname = firstNameText.text ?? ""
let middlename = middleNameText.text ?? ""
let lastname = lastNameText.text ?? ""
let formData = ChildInfo(cFirstName: firstname, cMiddleName: middlename, cLastName: lastname)
self.navigationController?.popViewController(animated: true)
formDelegate.saveInfoBlock(form: formData)
saveInfoBLock()
}
func saveInfoBLock() {
print("Saving Info")
dismiss(animated: true, completion: nil)
}
}
Heres my struct that I don't properly use
struct ChildInfo {
var childFirstName: String?
var childMiddleName: String?
var childLastName: String?
var childsMadeUpName: String = ""
init(cFirstName: String, cMiddleName: String, cLastName: String) {
self.childFirstName = cFirstName
self.childMiddleName = cMiddleName
self.childLastName = cLastName
}
}
and lastly my tableviewcell
class ChildInfoTableViewCell: UITableViewCell {
#IBOutlet weak var formText: UITextField!
#IBOutlet weak var formLabel: 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
}
}
Once the delegate protocol gets passed. What would a put in the printtableviewcontroller to make the data show? I had someone help me on here, but It was all programmatically and I'm no that advanced yet.

There are several things you can do:
Use delegation. I am assuming this is what you want to achieve here.
Pass your data through Segues. ( Recommended )
To use delegation, you have to have a clear concept of how delegation works.
Here is a link to an explanation I gave someone on another site. It breaks it down, I think this may help you a bit.
https://teamtreehouse.com/community/totally-confused-with-the-example-used
For now, I think the solution to your problem is to use Segues.
When you tap on the button to take you to the ChildInfoTableViewController where you fill the information is where you want to create your "ChildInfo" object. Then when you press save, you trigger the segue and pass the "ChildInfo" object to the "PagesController".
Then from there, that same object should be pass through the segue into the print view.

Related

DidSelectRow function is not called

I'm trying to implement the didSelectRow function and perform a segue but when running the cells select and nothing happens.
I created a print statement that also doesn't run which proves that the function doesn't appear to be firing. Why would this be?
I have checked the identifier is correct and have researched these for a few hours going through many stack overflow threads but with little luck.
import UIKit
import CoreData
class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let viewController = ListNameViewController()
let context = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext
var itemChoosen = 0
override func viewDidLoad() {
super.viewDidLoad()
homeListsTableView.delegate = self
homeListsTableView.dataSource = self
viewController.loadList()
}
#IBOutlet weak var homeListsTableView: UITableView!
#IBAction func templatesButton(_ sender: Any) {
tabBarController?.selectedIndex = 2
}
#IBAction func allListsButton(_ sender: Any) {
tabBarController?.selectedIndex = 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewController.listName.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let result = viewController.listName[indexPath.row]
cell.textLabel?.text = ("\(String(result.listName!))")
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
context!.delete(viewController.listName[indexPath.row])
viewController.listName.remove(at: indexPath.row)
viewController.saveList()
homeListsTableView.reloadData()
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "items2", sender: self)
print("selected")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(false)
viewController.loadList()
homeListsTableView.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
homeListsTableView.reloadData()
}
}
ListNameViewController:
import UIKit
import CoreData
class ListNameViewController: UIViewController, UITableViewDelegate {
let context = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext
var listName : [ListName] = []
override func viewDidLoad() {
super.viewDidLoad()
createButtonChange.isEnabled = false
//Objective-C Line used to keep checking if the text field is vaild before enabling the submit button
listNameValue.addTarget(self, action: #selector(textValidation), for: UIControl.Event.editingChanged)
}
#IBOutlet weak var listNameValue: UITextField!
#IBOutlet weak var locationOption: UITextField!
#IBOutlet weak var createButtonChange: UIButton!
#objc func textValidation() {
//Text Field Validation check before button is enabled
if listNameValue.text!.isEmpty {
createButtonChange.isEnabled = false
} else {
createButtonChange.isEnabled = true
}
}
// Create a new List
#IBAction func createButton(_ sender: Any) {
let newList = ListName(context: context!)
newList.listName = listNameValue.text
saveList()
self.navigationController!.popViewController(animated: true)
viewWillAppear(false)
}
func saveList() {
do {
try context!.save()
} catch {
print("Error saving context \(error)")
}
}
func loadList() {
let request : NSFetchRequest<ListName> = ListName.fetchRequest()
do{
listName = try context!.fetch(request)
} catch {
print("Error loading categories \(error)")
}
}
//Pass data to the HomeViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// let vc = segue.destination as! HomeViewController
}
}
// commented out core data and just used a normal array for testing.
Did you add segue in your storyboard for the tableview ? In this case the didSelect is not call but the prepare(for segue) of the tableview controller is called.
Ok solved it - There was a rouge tap gesture recognizer on the page. Removed it and works on one click. I have seen that if you wish to keep the gesture just add this line of code at the top of the function:
tap.cancelsTouchesInView = false
Took three days but I got there. Thanks for the help!

Get and pass text from a TextField which is in a custom TableViewCell subclass

How can I use the text in a textField from a custom Cell?
This is the Receiving Controller:
class ShowName: UIViewController {
#IBOutlet weak var showName: UILabel!
#IBAction func unwindToShowNameData(_ unwindSegue: UIStoryboardSegue) {
let sourceViewController = unwindSegue.source as! enterName
showName.text = sourceViewController.name
}
#IBAction func unwindToShowName(_ unwindSegue: UIStoryboardSegue) {
}
}
This is the Sending Controller:
class enterName: UIViewController, UITableViewDataSource, UITableViewDelegate {
var name: String?
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "nameCell", for: indexPath) as! nameCell
return cell
}
#IBAction func clickSave(_ sender: UIBarButtonItem) {
let cell = nameCell()
name = cell.nameText.text
performSegue(withIdentifier: "passData", sender: self)
}
}
This is the class Cell with TextField:
class NameCell: UITableViewCell {
#IBOutlet weak var nameText: UITextField!
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
}
}
Any ideas?
Actually that's the same solution as my suggestion in your previous question.
Add a callback in the cell and call it when the text field delegate method is called. Don't forget to connect the text field delegate in Interface Builder.
And please name classes and structs always with starting uppercase letter
class NameCell: UITableViewCell, UITextFieldDelegate {
#IBOutlet weak var nameText: UITextField!
var callback : ((UITableViewCell, String) -> Void)?
func textFieldDidEndEditing(_ textField: UITextField) {
callback?(self, nameText.text)
}
}
If you have more than one row you need to declare a data source array to maintain the values of the text fields. This is an example for 4 rows
var values = ["", "", "", ""]
These are the data source methods, in cellForRow the callback updates the model
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return values.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "nameCell", for: indexPath) as! NameCell
cell.nameText.text = values[indexPath.row]
cell.callback = { [unowned self] cCell, cName in
let currentIndexPath = tableView.indexPath(for: cCell)!
self.values[currentIndexPath.row] = cName
}
return cell
}
The benefit is you are independent of the cells in the clickSave method. Get the values from the data source array. And you can pass the name as sender parameter and hand it over in prepare(for segue
#IBAction func clickSave(_ sender: UIBarButtonItem) {
let name = values[0]
performSegue(withIdentifier: "passData", sender: name)
}

How to delete a cell in the tableview from another View controller?

I am trying to delete a cell in the tableview from another view controller. I have modeled my code similar to the question posted below but I still can't seem to successfully delete the selected row/cell in the CalorieVC when the delete button is pressed in the DeleteVC
Deleting a row of a tableview from another viewcontroller
SideNote: there is button in the cells to popup the DeleteVC, I am also getting an error upon pressing the the deleteBtn in the CalorieVC: DeleteRowInTableviewDelegate on let picked saying Thread 1: Fatal error: Index out of range
import UIKit
class CalorieViewController: UIViewController {
var selectedFood: FoodList! // allows data to be passed into the CalorieVC
var deleteItems: CalorieItem? // passes data to DeleteVC
// allows data to be sepearted into sections
var calorieItems: [CalorieItem] = []
var groupedCalorieItems: [String: [CalorieItem]] = [:]
var dateSectionTitle: [String] = []
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
tableView.dataSource = self
tableView.delegate = self
// Allows data in cells to seperate by section
groupedCalorieItems = Dictionary(grouping: calorieItems, by: {$0.foodList.date})
dateSectionTitle = groupedCalorieItems.map{$0.key}.sorted()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "DeleteSegue" {
let vc: DeleteViewController = segue.destination as! DeleteViewController
vc.deleteItems = self.deleteItems
// vc.delegate = self
}
}
}
extension CalorieViewController: UITableViewDelegate, UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return dateSectionTitle.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let date = dateSectionTitle[section]
return groupedCalorieItems[date]!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let calorieCell = tableView.dequeueReusableCell(withIdentifier: "CalorieCell") as! CalorieCell
let date = dateSectionTitle[indexPath.section]
let caloriesToDisplay = groupedCalorieItems[date]![indexPath.row]
calorieCell.configure(withCalorieItems: caloriesToDisplay.foodList)
return calorieCell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let calorieHeader = tableView.dequeueReusableCell(withIdentifier: "CalorieHeader") as! CalorieHeader
let headerTitle = dateSectionTitle[section]
calorieHeader.dateLbl.text = "Date: \(headerTitle)"
return calorieHeader
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 45
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let calorieFooter = tableView.dequeueReusableCell(withIdentifier: "CalorieFooter") as! CalorieFooter
//Cell Total Code
let date = dateSectionTitle[section]
let subtotal = groupedCalorieItems[dispensary]?.map { $0.getCalorieTotal() }.reduce(0, +) ?? 0
calorieFooter.calorieTotal.text = String(subtotal!)
return calorieFooter
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 150
}
}
extension CalorieViewController: DeleteRowInTableviewDelegate {
func deleteRow(inTableview rowToDelete: Int) {
let picked = dateSectionTitle[rowToDelete]
let selectedCell = groupedCalorieItems[dod]
delete(selectedCell)
// calorieItems.remove(at: rowToDelete) // tried using this and I get an error code upon segueing back to the CalorieVC
tableView.reloadData()
}
}
import UIKit
protocol DeleteRowInTableviewDelegate: NSObjectProtocol {
func deleteRow(inTableview rowToDelete: Int)
}
class DeleteViewController: UIViewController {
var modifyItems: CartItem!
var delegate: DeleteRowInTableviewDelegate?
#IBOutlet weak var deleteLbl: UILabel!
#IBOutlet weak var deleteBtn: UIButton!
#IBOutlet weak var cancelBtn: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if isMovingFromParent {
delegate!.deleteRow(inTableview: 1)
}
deleteLbl.text = "Are you sure you want to delete this Food Item from your calorie List?"
}
#IBAction func decline(_ sender: Any) {
dismiss(animated: true)
delegate!.deleteRow(inTableview: 1)
print("Delete Item")
}
#IBAction func cancel(_ sender: Any) {
dismiss(animated: true)
print("Cancel Delete")
}
}
Remove the value from the dataSource
Remove the table cell
extension CalorieViewController: DeleteRowInTableviewDelegate {
func deleteRow(inTableview rowToDelete: Int) {
if caloriesItems.count > rowToDelete {
calorieItems.remove(at: rowToDelete)
tableView.deleteRows(at: [IndexPath(row: rowToDelete, section: 0)], with: .automatic)
} else {
print("index not present")
}
}
}
Do not call reloadData just to delete one row. This is a bad practice.
Use deleteRows instead.

Fatal error: Index out of range when click specific cells for segue

I am trying to create a segue so every time I click a tableView row, show another ViewController with the information of the specific row.
I populate my data from Firestore.
Basically every document contains an array and then I populate the strings of the array in rows
var ingredientsArray = [Ingredients]()
func numberOfSections(in tableView: UITableView) -> Int {
return ingredientsArray.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ingredientsArray[section].compName.count
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
performSegue(withIdentifier: "SearchDetails", sender: self)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "IngredientCell", for: indexPath) as! IngredientTableViewCell
cell.populate(ingredient: ingredientsArray[indexPath.section])
let item1 = ingredientsArray[indexPath.section].compName[indexPath.row]
cell.ingredientNameLabel.text = ("\(item1)")
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? DetailViewController{
//HERE IS THE ERROR.
destination.ingredient = ingredientsArray[(tableView.indexPathForSelectedRow?.row)!]
}
}
When I click some rows then my app crashes and gives me Fatal error: Index out of range
DetailViewController
class DetailViewController: UIViewController {
#IBOutlet weak var compNameLbl: UILabel!
var ingredient : Ingredients?
override func viewDidLoad() {
super.viewDidLoad()
compNameLbl.text = "\((ingredient?.compName)!)"
}
}
Also when I try to show the name in the label the whole array appears.
Get the string value from the compName array and pass the value
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? DetailViewController, let indexPath = tableView.indexPathForSelectedRow {
destination.ingredient = ingredientsArray[indexPath.section].compName[indexPath.row]
}
}
Change the ingredient type to String in DetailViewController
class DetailViewController: UIViewController {
#IBOutlet weak var compNameLbl: UILabel!
var ingredient : String?
override func viewDidLoad() {
super.viewDidLoad()
compNameLbl.text = ingredient
}
}

TableviewCells with different contents

im trying to make an App where the User can add a Cell and where he can add different Contents to the Cell like Name, Birthday and Hobbys for example.
So far so good.
But how can i show him the individual Contents of each Cell?
Do I have to add a ViewController with different labels, where i load the text saved for the specific cell, for example with NSuserdefaults/Coredata?
Or am I totally wrong?
What i have right now : in my viewController where i can add an item
#IBAction func doneButtonPressed(sender: AnyObject) {
name = txtFieldName.text!
vc.items.append(name)
vc.tableView.reloadData()
self.dismissViewControllerAnimated(false, completion:nil)
}
in my tableviewController:
var items = [String]();
override func viewDidLoad() {
super.viewDidLoad()
print(items)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("itemCell", forIndexPath: indexPath)
cell.textLabel!.text = items[indexPath.row]
return cell
}
You need to implement kinda this thing
NavigationController ->(segue) TableView or TVController ->(show item segue) TableView or TVController
Using this example we should have files for each tableview and cell
first pair(newsvc.swift newscell.swift)
second pair(newsitemvc.swift newsitemcell.swift)
So as we understand it's a MVC
We need implement some static data
Create Model file (add struct and initialize it)
struct News {
var time: String?
var title: String?
var text: String?
init(time: String?, title: String?,text: String?) {
self.time = time
self.title = title
self.text = text
}
}
in second file we create data for struct
let newsData = [ News(time: "10:00", title: "Test", text: "Some text"),
News(time: "11:00", title: "Test1", text: "Some text1")]
NewsCell.swift
class NewsCell: UITableViewCell {
#IBOutlet weak var timeLabel: UILabel!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var textLabel: UILabel!
var news: News! {
didSet {
timeLabel = news.time
titleLabel = news.title
textLabel.text = news.text
}
}
}
in NewsViewController we need add "News" array and make it get data from "newsData"
var newsItems: [News] = newsData
update functions
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return newsItems.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("NewsCell", forIndexPath: indexPath) as! NewsCell
let news = newsItems[indexPath.row] as News
cell.news = news
return cell
}
after it we should add segue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let indexPath: NSIndexPath = self.tableView.indexPathForSelectedRow!
tableView.deselectRowAtIndexPath(indexPath, animated: true)
if segue.identifier == "showItemSegue" {
if let destViewController = segue.destinationViewController as? NewsItemViewController {
let seguedArray = newsItems[indexPath.row] as News
destViewController.newSeguedArray = seguedArray
}
}
}
You would append the data to an array before setting it to said cell. You can pull this data based on what they clicked by using the index in the array and the index.row variable.