How to pass using delegates in a tableview through the footer cell? - ios

What Im trying to do is pass data to another view controller through a button in the CalorieFooter cell using a Delegate to pass data in the cell.
I can't successfully pass data through the infoButton in the CalorieFooter cell to show the fat, carbs, and proteins for the calorie consumption of that day in the CalorieBreakdownController.
I am currently getting "0"'s in all labels of the CalorieBreakdownController (as seen in the far right of the image).
I think the issue might be because my calculationDelegate is done in the CalorieFooter cell. and the way I got the subtotal in the cell was by seperating the cells into sections. im a little confused at how to pass data to CalorieBreakdownController from the the footer, to get the "Subtotal" in the labels.
How would I be able to pass the "subTotal" data the CalorieBreakdownController from the calorieFooter cell (as seen in the image below)
thanks in advance for any help that is provided
import UIKit
class CalorieViewController: UIViewController {
var selectedFood: FoodList!
var additionalCalories: DailyCalories! // delegate code for footer
var calorieItems: [DailyCalories] = []
var groupedCalorieItems: [String: [DailyCalories]] = [:]
var dateSectionTitle: [DailyCalories] = []
#IBOutlet weak var calorieTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
groupedFoodItems = Dictionary(grouping: calorieItems, by: {$0.foodList.day})
dateSectionTitle = groupedCalorieItems.map{$0.key}.sorted()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? CalorieTotalController { // delegate code for footer
vc.additionalCalories = self.additionalCalories
}
}
}
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 calorieItemsToDisplay = groupedCalorieItems[date]![indexPath.row]
calorieCell.configure(withCartItems: calorieItemsToDisplay.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, viewForFooterInSection section: Int) -> UIView? {
let calorieFooter = tableView.dequeueReusableCell(withIdentifier: "CalorieFooter") as! CalorieFooter
let date = dateSectionTitle[section]
let arrAllItems = groupedCalorieItems[dispensary]!
var subtotal: Float = 0
for item in arrAllItems {
if item.foodList.selectedOption == 1 {
subtotal = subtotal + (Float(item.foodList.calorie1) * Float(item.foodList.count))
} else if item.foodList.selectedOption == 2 {
subtotal = subtotal + (Float(item.foodList.calorie2) * Float(item.foodList.count))
} else if item.foodList.selectedOption == 3 {
subtotal = subtotal + (Float(item.foodList.calorie3) * Float(item.foodList.count))
}
}
calorieFooter.cartTotal.text = "\(subtotal)"
calorieFooter.calculationDelegate = self // delegate code for footer
calorieFooter.additionalCalories! = ??? // can't get the right code set to allow the data to pass
calorieFooter.calculations! = ??? // can't get the right code set to allow the data to pass
return calorieFooter
}
}
extension CalorieViewController: CalculationDelegate { // delegate code for footer
func onTouchInfoButton(from cell: CalorieFooter) {
self.additionalCalories = cell.additionalCalories
self.performSegue(withIdentifier: "CalculateDailyCalorieCell", sender: self)
}
}
import UIKit
protocol CalculationDelegate: class { // delegate code for footer
func onTouchInfoButton(from cell: CalorieFooter)
}
class CalorieFooter: UITableViewCell {
weak var calculationDelegate: CalculationDelegate? // delegate code for footer
var additionalCalories: DailyCalories! // delegate code for footer
var calculations: [DailyCalories] = []
#IBOutlet weak var calorieTotal: UILabel!
#IBOutlet weak var additionalFeesBtn: UIButton!
#IBAction func overallTotalBtn(_ sender: Any) {
self.additionalFeesDelegate?.onTouchInfoButton(from: self) // delegate code for footer
}
}
class CalorieTotalController: UIViewController {
var additionalCalories: DailyCalories! // delegate code for footer
var calculations: [DailyCalories] = [] // delegate code for footer
#IBOutlet weak var calorieSubtotal: UILabel!
#IBOutlet weak var fatTotal: UILabel!
#IBOutlet weak var carbTotal: UILabel!
#IBOutlet weak var proteinTotal: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// calculations done for when data is passed through Delegate
var subtotal: Float = 0
for item in calculations {
if item.foodList.selectedOption == 1 {
subtotal = subtotal + (Float(item.foodList.calorie1) * Float(item.foodList.count))
} else if item.foodList.selectedOption == 2 {
subtotal = subtotal + (Float(item.productList.calorie2) * Float(item.foodList.count))
} else if item.foodList.selectedOption == 3 {
subtotal = subtotal + (Float(item.foodList.calorie3) * Float(item.foodList.count))
}
}
let protein = Float(subtotal * 0.25)
let carbs = Float(subtotal * 0.25)
let fat = Float(subtotal * 0.5)
calorieSubtotal.text = String(subtotal!)
proteinTotal.text = String(protein!)
fatTotal.text = String(fat!)
carbTotal.text = String(carbs!)
}
#IBAction func closeButton(_ sender: Any) {
dismiss(animated: true, completion: nil)
print("Calories BreakDown")
}
}

Using delegates is probably a good idea. I just wrote some code (not tested!) to give you an idea how it could work:
Extend the delegate class:
protocol CalculationDelegate: class {
func onTouchInfoButton(from cell: CalorieFooter)
func getAdditionalCalories() -> Int
func getCalculations() -> Int // or whatever type this object should be
}
Make sure your view controller follows the protocol:
extension CalorieViewController: CalculationDelegate {
func onTouchInfoButton(from cell: CalorieFooter) { ... }
func getAdditionalCalories() -> Int {
return self.calories
}
func getCalculations() -> Int {
return self.calculations
}
}
Add local variables to CalorieViewController that stores the current state (amount of calories/calculations)
class CalorieViewController: UIViewController {
private var calories: Int = 0
private var calculations: Int = 0
... other code that is already in the UIViewController
}
Make sure to initialize these variables somewhere! Something like:
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
... same as before...
self.calories = subtotal // I guess?
self.calculations = calculations // put this somewhere where calculations is initialized
}
Now, the data should be available inCalorieFooter. I added the default values 0 for when the calculationDelegate is nil:
let calories = calculationDelegate?.getAdditionalCalories() ?? 0
let calculations = calculationDelegate?.getCalculations() ?? 0
Good luck!

I guess, In viewForFooterInSection() function you forgot to set any value for calorieFooter.additionalCalories !
calorieFooter.additionalCalories should be of type DailyCalories not array of DailyCalories.
For example you can set -
let date = dateSectionTitle[section]
calorieFooter.additionalCalories = date
Without "!" sign, whenever you are assigning some value to any variable you don't need to use ! in the end.

Related

How to pass data with delegate from footer cell to view controller?

Ive been stuck trying to pass data from the FoodEatenController(FEC) Footer to the TotalCaloriesController. The code that I have now it shows NOTHING in the calorieLbl of the TotalCalorieController(TCC).
The delegate that ive been using to pass the data from the FEC to the TCC does not pass the text/string data that is in the FoodFooter calorieTotallbl to the TEC calorieLbl
the data that populates the cells of the FEC is retrieved from Cloud Firestore and passed in from anotherView Controller (FoodPickerController)
import UIKit
class FoodEatenController: UIViewController{
var selectedFood: FoodList! // allows data to be passed into the VC
// allows data to be sepearted into sections
var foodItems: [FoodItem] = []
var groupedFoodItems: [String: [FoodItem]] = [:]
var dateSectionTitle: [String] = []
#IBOutlet weak var tableView: UITableView!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? TotalCalorieController {
}
}
}
extension FoodEatenController: UITableViewDelegate, UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return dateSectionTitle.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let date = dateSectionTitle[section]
return groupedFoodItems[date]!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let foodCell = tableView.dequeueReusableCell(withIdentifier: "FoodCell") as! FoodCell
let date = dateSectionTitle[indexPath.section]
let foodItemsToDisplay = groupedFoodItems[date]![indexPath.row]
foodCell.configure(withCartItems: fooditemsToDisplay.foodList)
return foodCell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let foodHeader = tableView.dequeueReusableCell(withIdentifier: "FoodHeader") as! FoodHeader
let headerTitle = dateSectionTitle[section]
foodHeader.dateLbl.text = "Date: \(headerTitle)"
return foodHeader
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let foodFooter = tableView.dequeueReusableCell(withIdentifier: "FoodFooter") as! FoodFooter
let date = dateSectionTitle[section]
let arrAllItems = groupedFoodItems[date]!
var total: Float = 0
for item in arrAllItems {
let eaten = item.productList
let selectedMeal = item.foodList.selectedOption
if selectedMeal == 1 {
total = total + (Float(eaten!.calorie))
}
}
foodFooter.calorieTotal.text = String(subtotal!)
foodFooter.delegate = self
return foodFooter
}
}
extension FoodEatenController: EatenFoodDelegate {
func onTouchCaloireInfo(info: String) {
let popUp = self.storyboard?.instantiateViewController(withIdentifier: "AdditionalCostsVC") as! TotalCalorieController
popUp.calorieLbl.text = info
}
}
import UIKit
protocol EatenFoodDelegate: class {
func onTouchCaloireInfo(info: String)
}
class FoodFooter: UITableViewCell {
weak var delegate: EatenFoodDelegate? = nil
#IBOutlet weak var calorieTotal: UILabel!
#IBOutlet weak var totalInfoBtn: UIButton!
#IBAction func totalOnClicked(_ sender: AnyObject) {
self.delegate?. onTouchCaloireInfo(info: calorieTotal.text!)
}
}
class TotalCalorieController: UIViewController, EatenFoodDelegate {
func onTouchCaloireInfo(info: String) {
calorieLbl.text = info
}
#IBOutlet weak var calorieLbl: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func returnButton(_ sender: Any) {
dismiss(animated: true, completion: nil)
print("Close Taxes and Fees")
}
}
Add the following line at the end of the func onTouchCaloireInfo(info:)
self.present(popUp, animated: true, completion: nil)
If you would like to be sure that the function onTouchCaloireInfo(info:) gets called, just add the following line:
debugPrint("onTouchCaloireInfo")
And check, if it prints the given string in the console of the Xcode
extension FoodEatenController: EatenFoodDelegate {
func onTouchCaloireInfo(info: String) {
debugPrint("onTouchCaloireInfo")
let popUp = self.storyboard?.instantiateViewController(withIdentifier: "AdditionalCostsVC") as! TotalCalorieController
self.present(popUp, animated: true) {
popUp.calorieLbl.text = info
}
}
}

How to design the array structure for Multiple cells in UITableview?

This is my first ViewController ,when I will click on plus(+) button, it will move to the SecondViewController
This is my SecondViewController, after giving the required field when I will click on save button it should add one more row in my tableview respective cell(executive,seniormanagement,staff). But it's not adding the row in respective cells.
This my code what i have tired
FirstViewController
class TableViewHeader: UIViewController ,UITableViewDelegate,UITableViewDataSource,EmployeeProtocol{
#IBOutlet weak var tableviewHeader: UITableView!
var employee = [ EmployeeStatus(status:"Executive",Name:["Smith","Dev","Aryan"], Date:["Nov 14 1889","Dec 01 1980","Sep 18 1997"]),
EmployeeStatus(status:"SeniorManagement",Name:["Sourav","Smarak"], Date:[" April 10 1879"," Dec 11 1990"]),
EmployeeStatus(status:"Staff",Name:["Smith","Dev","Aryan","Suman"], Date:["Feb 14 1234"," Jan 01 1480","Sep 23 1994","July 10 1991"])]
#IBAction func plusButtonTapped(_ sender: UIButton) {
performSegue(withIdentifier: "showSegue", sender:self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showSegue" {
let createSegue = segue.destination as! CreateEmployeeViewController
createSegue.myEmpProtocol = self
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return employee.count
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return employee[section].Name.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 70
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let Label = UILabel()
Label.text = employee[section].status
Label.backgroundColor = UIColor.gray
return Label
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableviewHeader.dequeueReusableCell(withIdentifier: "TableHeaderCell", for: indexPath) as! TableViewHeadercell
cell.NameLabel.text = employee[indexPath.section].Name[indexPath.row]
cell.DateLabel.text = employee[indexPath.section].Date[indexPath.row]
return cell
}
func addData(status:String, Name:[String], Date:[String]) {
employee.append(EmployeeStatus(status:status, Name:Name, Date: Date))
tableviewHeader.reloadData()
}
}
SeconViewController
protocol EmployeeProtocol {
func addData(status:String, Name:[String], Date:[String])
}
class CreateEmployeeViewController: UIViewController {
var myEmpProtocol:EmployeeProtocol?
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var birthdayTextField: UITextField!
#IBOutlet weak var StatusSegment: UISegmentedControl!
var name = [String]()
var DOB = [String]()
var status:String = ""
#IBAction func saveButtonTapped(_ sender: UIButton) {
self.name = [nameTextField.text!]
self.DOB = [birthdayTextField.text!]
if StatusSegment.selectedSegmentIndex == 0 {
self.status = "Executive"
} else if StatusSegment.selectedSegmentIndex == 1{
self.status = "SeniorManagement"
} else if StatusSegment.selectedSegmentIndex == 2 {
self.status = "Staff"
}
myEmpProtocol?.addData(status:status, Name:name, Date:DOB)
}
#IBAction func CancelButtonTapped(_ sender: UIButton) {
dismiss(animated: true, completion: nil)
}
}
MyClass
import Foundation
struct EmployeeStatus {
var status:String
var Name:[String]
var Date:[String]
init(status:String, Name:[String], Date:[String]){
self.status = status
self.Name = Name
self.Date = Date
}
Your current code is adding the new EmployeeStatus to employee array, not the item's EmployeeStatus.Name array and EmployeeStatus.Date array.
Please try this code.
func addData(status:String, Name:[String], Date:[String]) {
var arr = [EmployeeStatus]()
employee.forEach { ele in
if ele.status == status {
var e = ele
e.Name.append(Name[0])
e.Date.append(Date[0])
arr.append(e)
} else {
arr.append(ele)
}
}
employee = arr
tableviewHeader.reloadData()
}
Your data looks interesting. It might work better with
enum Level: Int {
case executive = 0, seniorManagement, staff
var stringValue: { return (String:description: self).capitalizingFirstLetter }
}
struct Employee {
let name: String
let date: String
}
var employees: [Level: [Employee]] = [
.executive: [Employee(name: "Smith", date: "Nov 14 1889"],
.seniorManagement: [],
.staff: []]
Then you can add employees with:
guard let level = Level(rawValue: status) else {
throw Errors.levelDoesNotExist(status)
}
employees[.level] = newEmployee
Reloading the tableView works as you do already. Just the data is in a format where its weird to add to.
And best dismiss the second viewController once its finished after the addData delegate call so its going back to the first to show the updated data.
You have to set your view controller as a table view delegate and datasource.
Add to the end of the viewDidLoad at TableViewHeader class:
override func viewDidLoad() {
super.viewDidLoad()
tableviewHeader.delegate = self
tableviewHeader.dataSource = self
}
In this way, you can pass value from one view controller to another tab:
First of all open main.storyboard
Select the First view controller and drag and drop UItextField and UIButton on the field.

Update initialized Data in array with variable then pass array to next view controller

I'm having issues moving the data from the selected cells from the (service2viewcontroller) to the (confirmorderviewcontroller). I am trying to move the cell data (cells with a stepper.value above 0(var quantity > 0.0 (in Service2ViewController))), I was told to pass the array to the next view controller, to do so for a stepper value above 0 I would need to also send the indexpath.row for the rows with a quantity variable above 0 correct? I don't know how to do this if anyone can help I would greatly appreciate it. also the label is not updating when I use the stepper it stays at 0, can I place the quantity variable inside of the array? the total price label in the view controller continues to function and the data is sent to the (confirmorderviewcontroller) with no issues.
first TableView (data is input and forwarded)
class Service2ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var service2Total: UILabel!
#IBOutlet weak var service2TableView: UITableView!
// service data
var Wash: [Service2] = []
//stepper and price calculation
var quantity = Double()
var totalPrice : Double = 0.0
var priceList = [Int : Double]()
var totalProductPrice = [Int : Double]()
var label : Int!
override func viewDidLoad() {
super.viewDidLoad()
Wash = Options2()
if Int(quantity) > 0{
service2TableView.reloadData()
}
priceList[0] = 3.51//price list
priceList[1] = 5.51
service2Total.text = "$0.00"
}
// create data array
func Options2() -> [Service2]{
var washOptions: [Service2] = []
let option1 = Service2(titled: "Test", pricing: "$3.51", image: #imageLiteral(resourceName: "Wash&Fold"), description:"Testing the description box", quantity: Int(quantity))
let option2 = Service2(titled: "Test", pricing: "$5.51", image: #imageLiteral(resourceName: "Wash&Fold"), description: "Testing the description box", quantity: Int(quantity))
washOptions.append(option1)
washOptions.append(option2)
return washOptions
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Wash.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let Wash1 = Wash[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "Service2Cell", for: indexPath) as! Service2TableViewCell
cell.setService(Wash: Wash1)
cell.selectionStyle = .none
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 133
}
#IBAction func stepperAcn(_ sender: UIStepper) {
//change label value with stepper
let cellPosition = sender.convert(CGPoint.zero, to: service2TableView)
let indPath : IndexPath = service2TableView.indexPathForRow(at: cellPosition)!
quantity = sender.value
let getCurrentProductPrice : Double = priceList[indPath.row]! * sender.value
totalPrice = gettingPriceLabel(indPath: indPath, getCurrentProductPrice: getCurrentProductPrice)
if totalPrice == 0{
service2Total.text = ("$0.00")
}
else{
service2Total.text = ("$")+String(totalPrice)
}
print("total price",totalPrice)
print("quantity double",quantity)
service2TableView.reloadData()
}
func gettingPriceLabel(indPath: IndexPath, getCurrentProductPrice : Double) -> Double
{
totalProductPrice[indPath.row] = getCurrentProductPrice
var totalCost : Double = 0.0
let valuesArr = Array(totalProductPrice.values)
for i in 0..<valuesArr.count
{
totalCost = totalCost + valuesArr[i]
}
return totalCost
}
// add function to collect (didSelectRowAt) and send selected data to cart and prepare for segue
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
// change sender to
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let DestViewController: ConfirmorderViewController = segue.destination as! ConfirmorderViewController
if totalPrice > 0.00{
DestViewController.amount = totalPrice
}
}
}
service initializer
class Service2
{
var service2Title: String
var service2Image: UIImage
var Service2Pricing: String
var service2Description: String
var service2Quantity: Int
init(titled: String, pricing: String, image: UIImage, description: String, quantity: Int){
self.service2Title = titled
self.Service2Pricing = pricing
self.service2Image = image
self.service2Description = description
self.service2Quantity = quantity
}
}
Service 2 TableViewCell
class Service2TableViewCell: UITableViewCell {
#IBOutlet weak var service2Title: UILabel!
#IBOutlet weak var service2Stepper: UIStepper!
#IBOutlet weak var service2StepperLbl: UILabel!
#IBOutlet weak var service2Pricing: UILabel!
#IBOutlet weak var service2Image: UIImageView!
#IBOutlet weak var service2Description: UILabel!
func setService(Wash: Service2){
service2Image.image = Wash.service2Image
service2Pricing.text = Wash.Service2Pricing.description
service2Title.text = Wash.service2Title
service2Description.text = Wash.service2Description
service2StepperLbl.text = Wash.service2Quantity.description
}
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
}
}
Second TableView (receives data)
class ConfirmorderViewController: UIViewController{
#IBOutlet weak var Total: UILabel!
#IBOutlet weak var confirmOrderTableView: UITableView!
var titled = [String]()
var amount: String = ""
//var quantity = String()
var image1 = [UIImage]()
var Price = [Double]()
override func viewDidLoad() {
super.viewDidLoad()
Total.text = amount
confirmOrderTableView.reloadData()
}
}
extension ConfirmorderViewController: UITableViewDataSource, UITableViewDelegate{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return titled.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ConfirmOrderTableViewCell") as! ConfirmOrderTableViewCell
cell.coTitle?.text = titled[indexPath.row]
cell.coImg?.image = image1[indexPath.row]
//cell.coQuantity.text = quantity
cell.coPrice?.text = Price.description
return cell
}
}
I have tried here. I got list of row numbers having more than 0 order. I have it stored in whichRowToBeAdd. If user decreased to Zero, respective rows will removed from this array.
With the help of Singleton Class, we can store whatever we need to show in NextViewController
var whichRowToBeAdd = [Int]() // GLOBAL
#IBAction func stepperAcn(_ sender: UIStepper) {
//change label value with stepper
let cellPosition = sender.convert(CGPoint.zero, to: service2TableView)
let indPath : IndexPath = service2TableView.indexPathForRow(at: cellPosition)!
if Int(sender.value) == 0
{
if whichRowToBeAdd.contains(indPath.row)
{
whichRowToBeAdd.remove(at: whichRowToBeAdd.index(of: indPath.row)!)
}
else
{
}
}
else
{
if whichRowToBeAdd.contains(indPath.row)
{
}
else
{
whichRowToBeAdd.append(indPath.row)
}
}
//....
//..... Your Code in your answer
}
// After stepper Action, final click of Button, which moves to Next ViewController
#IBAction func goToConfirmOrder(_ sender: UIBarButtonItem) {
print("\n\n Val_ ", whichRowToBeAdd)
singleTon.sharedInstance.orderDict.removeAll()
for i in 0..<whichRowToBeAdd.count
{
let indPath = IndexPath(row: whichRowToBeAdd[i], section: 0)
let newCell = tblVw.cellForRow(at: indPath) as! Service2TableViewCell
print("qweqwe ",newCell.testLbl.text)
let name : String = newCell.service2Title.text!
let image : UIImage = newCell.service2Image.image
let quantity : Int = Int(newCell.service2StepperLbl.text!)!
getOrderOneByOne(productName: name, productImage: image, productQuantity: quantity)
if i == (whichRowToBeAdd.count - 1)
{
self.performSegue(withIdentifier: "confirmOrderVC", sender: nil)
}
}
}
func getOrderOneByOne(productName: String, productImage : UIImage, productQuantity: Int)
{
let createDict = ["productName" : productName, "productImage" : productImage, "productQuantity" : productQuantity] as [String : Any]
singleTon.sharedInstance.orderDict.append(createDict)
}
Singleton Class
class singleTon: NSObject {
static let sharedInstance = singleTon() // Singleton Instance
var orderDict = [[String : Any]]() // Dictionary Declaration
}
Next ViewController
class ConfirmOrderViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("\n\norderDict.coun ", singleTon.sharedInstance.orderDict)
}
}
With this, you can display datas in TableView in this ConfirmOrderViewController.

How to use KVO to update tableViewCells based on underlying array element changes?

I have a table view representing an underlying array. The cells have a label and a slider which should show the value of the percentage property of the array.
I want to use key-value observing to update the label whenever the percentage property changes. (I know KVO is overkill in this example but eventually sliding one slider will affect the other cells including the position of the slider and the underlying array will be set from multiple places in the app and at any time so KVO is the way to go.)
I've had a bunch of help from this answer, but I can't get it to fire and update the label. I'm including all my code here. Not sure where I'm going wrong.
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, CustomCellDelegate {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
for i in 0...4 {
items.append(Items(ID: i, percentage: 50))
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: myTableViewCell.ID) as? myTableViewCell {
cell.object = items[indexPath.row]
cell.mySlider.tag = indexPath.row
return cell
} else {
return UITableViewCell()
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
#IBAction func sliderValueChanged(_ sender: UISlider) {
items[sender.tag].percentage = Double(sender.value)
print("percentage at \(items[sender.tag].ID) is \(items[sender.tag].percentage)")
}
func didUpdateObject(for cell: UITableViewCell) {
if let indexPath = tableView.indexPath(for: cell) {
tableView.reloadRows(at: [indexPath], with: .automatic)
print("hello")
}
}
}
class myTableViewCell: UITableViewCell {
static let ID = "myCell"
weak var delegate: CustomCellDelegate?
private var token: NSKeyValueObservation?
var object: Items? {
willSet {
token?.invalidate()
}
didSet {
myLabel.text = "\(object?.percentage ?? 0)"
token = object?.observe(\.percentage) { [weak self] object, change in
if let cell = self {
cell.delegate?.didUpdateObject(for: cell)
}
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
}
#IBOutlet weak var myLabel: UILabel!
#IBOutlet weak var mySlider: UISlider!
}
class Items: NSObject {
let ID: Int
#objc dynamic var percentage: Double
init(ID: Int, percentage: Double){
self.ID = ID
self.percentage = percentage
super.init()
}
}
var items: [Items] = []
protocol CustomCellDelegate: class {
func didUpdateObject(for cell: UITableViewCell)
}
To do the KVO in Swift 4, you have to declare the property as dynamic and call observe(_:options:changeHandler:) on that object, saving the resulting NSKeyValueObservation token. When that token falls out of scope (or replaced with another token), the original observer will automatically be removed.
In your case, you have your observer calling the delegate, which then reloads the cell. But you never appear to set that delegate property, so I suspect that method isn't getting called.
But this all seems a bit fragile. I'd be inclined to just update the label directly in the observer's changeHandler. I also think you can do a more direct updating of the cell (I'd put the "value changed" IBAction in the cell, not the table view), and eliminate that rather awkward use of the tag to identify which row in the model array had its slider updated (which can be problematic if you insert or delete rows).
So consider this object:
class CustomObject: NSObject {
let name: String
#objc dynamic var value: Float // this is the property that the custom cell will observe
init(name: String, value: Float) {
self.name = name
self.value = value
super.init()
}
}
You could then have a table view controller that populates an array of objects with instances of this model type. The details here are largely unrelated to the observation (which we'll cover below), but I include this just to provide a complete example:
class ViewController: UITableViewController {
var objects: [CustomObject]!
override func viewDidLoad() {
super.viewDidLoad()
// self sizing cells
tableView.estimatedRowHeight = 60
tableView.rowHeight = UITableViewAutomaticDimension
// populate model with random data
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
objects = (0 ..< 1000).map {
CustomObject(name: formatter.string(for: $0)!, value: 0.5)
}
}
}
// MARK: - UITableViewDataSource
extension ViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objects?.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.object = objects[indexPath.row]
return cell
}
}
Having done that, you can now have the base class for your cell (a) update the model object if the slider changes; and (b) observe changes to that dynamic property, in this example updating the label when the value changes are observed in the model object:
class CustomCell: UITableViewCell {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var valueLabel: UILabel!
#IBOutlet weak var valueSlider: UISlider!
static private let formatter: NumberFormatter = {
let _formatter = NumberFormatter()
_formatter.maximumFractionDigits = 2
_formatter.minimumFractionDigits = 2
_formatter.minimumIntegerDigits = 1
return _formatter
}()
private var token: NSKeyValueObservation?
weak var object: CustomObject? {
didSet {
let value = object?.value ?? 0
nameLabel.text = object?.name
valueLabel.text = CustomCell.formatter.string(for: value)
valueSlider.value = value
token = object?.observe(\.value) { [weak self] object, change in
self?.valueLabel.text = CustomCell.formatter.string(for: object.value)
}
}
}
#IBAction func didChangeSlider(_ slider: UISlider) {
object?.value = slider.value
}
}
That yields:
For more information, see the "Key-Value Observing" section of the Using Swift with Cocoa and Objective-C: Adopting Cocoa Patterns.
hi #sean problem is in UITableview cell class you have already make diSet Method , so you dont need to pass value for cell.lable and slider Just try below code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: myTableViewCell.ID) as? myTableViewCell {
//pass the object to which you wanna add observer to cell
cell.object = items[indexPath.row]
return cell
} else {
return UITableViewCell()
}
}

iOS development Swift Custom Cells

I'm developing an app in Swift and I have problem with Custom Cells. I have one Custom Cell and when you click on Add button it creates another Custom Cell. Cell has 3 textfields, and 2 buttons. Those textfields are name, price, and amount of the ingredient of meal that I am creating. When I use only one Custom Cell or add one more to make it two, the data is stored properly. But when I add 2 Custom Cell (3 Custom Cells in total) I have problem of wrong price, only last two ingredients are calculated in price. When I add 3 Custom Cells (4 Custom Cells in total) it only recreates first cell with populated data like in first cell.
On finish button tap, I get an fatal error: Found nil while unwrapping Optional value.
View Controller
import UIKit
import CoreData
class AddMealViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate {
#IBOutlet weak var mealNameTF: UITextField!
#IBOutlet weak var addMealsCell: UITableViewCell!
#IBOutlet weak var finishButton: UIButton!
#IBOutlet weak var resetButton: UIButton!
#IBOutlet weak var addButton: UIButton!
#IBOutlet weak var addMealTableView: UITableView!
#IBOutlet weak var productPrice: UILabel!
let currency = "$" // this should be determined from settings
var priceTotal = "0"
override func viewDidLoad() {
super.viewDidLoad()
addMealTableView.delegate = self
addMealTableView.dataSource = self
borderToTextfield(textField: mealNameTF)
mealNameTF.delegate = self
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.view.endEditing(true)
return true;
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return counter
}
var counter = 1
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return counter
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier:
"addMealsCell", for: indexPath) as! AddMealsTableViewCell
borderToTextfield(textField: (cell.amountTF)!)
borderToTextfield(textField: (cell.ingredientNameTF)!)
//borderToTextfield(textField: cell.normativeTF)
borderToTextfield(textField: (cell.priceTF)!)
return cell
}
#IBAction func addButton(_ sender: UIButton) {
counter += 1
addMealTableView.register(AddMealsTableViewCell.self, forCellReuseIdentifier: "addMealsCell")
addMealTableView.reloadData()
}
#IBAction func resetButton(_ sender: UIButton) {
mealNameTF.text = ""
for c in 0..<counter{
let indexPath = IndexPath(row: c, section:0)
let cell = addMealTableView.cellForRow(at: indexPath) as! AddMealsTableViewCell
cell.amountTF.text = ""
cell.ingredientNameTF.text = ""
// cell.normativeTF.text = ""
cell.priceTF.text = ""
}
productPrice.text = "\(currency)0.00"
priceTotal = "0"
counter = 1
}
#IBAction func finishButton(_ sender: UIButton) {
for c in (0..<counter){
if let cell = addMealTableView.cellForRow(at: IndexPath(row: c, section: 0)) as? AddMealsTableViewCell {
cell.amountTF.delegate = self
cell.ingredientNameTF.delegate = self
// cell.normativeTF.delegate = self
cell.priceTF.delegate = self
guard cell.priceTF.text?.isEmpty == false && cell.amountTF.text?.isEmpty == false && mealNameTF.text?.isEmpty == false && cell.ingredientNameTF.text?.isEmpty == false
else {
return
}
if cell.priceTF.text?.isEmpty == false{
// if (true) {
let tfp = Double((cell.priceTF.text!))!*Double((cell.amountTF.text!))!
var ttp = Double(priceTotal)
ttp! += tfp
priceTotal = String(ttp!)
// }
}}
}
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let newMeal = NSEntityDescription.insertNewObject(forEntityName: "Meal", into: context)
let mealName = mealNameTF.text
newMeal.setValue(mealName, forKey: "name")
newMeal.setValue(priceTotal, forKey: "price")
do {
try context.save()
print("Spremljeno")
} catch {
print("Neki error")
}
productPrice.text = currency + priceTotal
}
#IBAction func addNewIngredientButton(_ sender: UIButton) {
}
func borderToTextfield(textField: UITextField){
let border = CALayer()
let width = CGFloat(2.0)
border.borderColor = UIColor.white.cgColor
border.frame = CGRect(x: 0, y: textField.frame.size.height - width, width: textField.frame.size.width, height: textField.frame.size.height)
border.borderWidth = width
textField.layer.addSublayer(border)
textField.layer.masksToBounds = true
textField.tintColor = UIColor.white
textField.textColor = UIColor.white
textField.textAlignment = .center
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.view.endEditing(true)
return true;
}
}
Cell
class AddMealsTableViewCell: UITableViewCell, UITextFieldDelegate{
#IBOutlet weak var DropMenuButton: DropMenuButton!
#IBOutlet weak var addNewIngredient: UIButton!
#IBOutlet weak var ingredientNameTF: UITextField!
// #IBOutlet weak var normativeTF: UITextField!
#IBOutlet weak var amountTF: UITextField!
#IBOutlet weak var priceTF: UITextField!
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.endEditing(true)
return true;
}
override func prepareForReuse() {
self.amountTF.text = ""
self.priceTF.text = ""
self.ingredientNameTF.text = ""
}
#IBAction func DropMenuButton(_ sender: DropMenuButton) {
DropMenuButton.initMenu(["kg", "litre", "1/pcs"], actions: [({ () -> (Void) in
print("kg")
sender.titleLabel?.text = "kg"
}), ({ () -> (Void) in
print("litre")
sender.titleLabel?.text = "litre"
}), ({ () -> (Void) in
print("1/pcs")
sender.titleLabel?.text = "1/pcs"
})])
}
#IBAction func addNewIngredient(_ sender: UIButton) {
let name = ingredientNameTF.text
let amount = amountTF.text
let price = priceTF.text
// let normative = normativeTF.text
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let newIngredient = NSEntityDescription.insertNewObject(forEntityName: "Ingredient", into: context)
newIngredient.setValue(name, forKey: "name")
// newIngredient.setValue(normative, forKey: "normative")
newIngredient.setValue(amount, forKey: "amount")
newIngredient.setValue(price, forKey: "price")
do {
try context.save()
print("Spremljeno")
} catch {
print("Neki error")
}
}
}
Your code is very difficult to read, but I suspect the problem may be here:
func numberOfSections(in tableView: UITableView) -> Int {
return counter
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return counter
}
You're returning the same number of rows for both the sections and rows for sections. So if you have 1 ingredient you are saying there is 1 section with 1 row. But if you have 2 ingredients you are saying there are 2 sections, each with 2 cells (4 cells total).
There are many other things to fix with your code, here are a few:
The biggest thing is that you are making this very difficult with the counter variable you have. If instead you have an array of ingredients
var ingredients = [Ingredient]()
You can use that to setup everything for the count of your table. Something like this:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ingredients.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier:
"addMealsCell", for: indexPath) as! AddMealsTableViewCell
let ingredient = ingredients[indexPath.row]
cell.ingredientNameTF.text = ingredient.name
cell.normativeTF.text = ingredient.normative
cell.amountTF.text = ingredient.amount
cell.priceTF.text = ingredient.price
return cell
}
All of these can be set in interface builder (you dont need lines of code in your view did load for them):
addMealTableView.delegate = self
addMealTableView.dataSource = self
mealNameTF.delegate = self
This line should be in your viewDidLoad function, you only need to register the class once, you're doing it everytime add is pressed.
addMealTableView.register(AddMealsTableViewCell.self, forCellReuseIdentifier: "addMealsCell")
Your action names should be actions
#IBAction func addButtonPressed(_ sender: UIButton)
#IBAction func resetButtonPressed(_ sender: UIButton)
#IBAction func finishButtonPressed(_ sender: UIButton)
Thanks. Now when that ViewController loads I have no cells. My array is now empty so I have to implement some code to addButton which creates another cell and reloads tableView. How can I do that?
You just need to add a new ingredient object to the ingredients array and reload the data of the table view.
#IBAction func addButtonPressed(_ sender: UIButton) {
let newIngredient = Ingredient()
ingredients.append(newIngredient)
tableView.reloadData()
}

Resources