I had a question about calculation functions and I cant seem to understand the logic behind it yet as I just started to do coding in iOS about 3 months ago.
Here's a screenshot in the link and please take a look at it.
I would like to achieve results as explained Below
Expected Results : there will be more than 10000 cell of rows eventually, so my sum Cell will required to calculate based on the first index of the data instead of cell, I tried enumerated, mapping, for in loops but to no success
.
PLEASE PROVIDE CODE ALONG WITH YOUR EXPLANATION SO I CAN UNDERSTAND AS I AM NOT AN EXPERIENCED CODER
Example
UIVIEW
TotalBalance = $ 28.00 << Achieve by updateTotal()
TableView
TransactionType : $ 10.00 ($10.00) < this will sum cell 1 only as it is the first cell
TransactionType : $ 5.00 ($15.00) < this will sum cell 1 - 2 only
TransactionType : $ 3.00 ($18.00) < this will sum cell 1 - 3 only
TransactionType : $ 10.00 ($28.00) < this will sum cell 1 - 4 only
class Account: Object {
#objc dynamic var name : String = ""
#objc dynamic var balance : Double = 0
var ofTransaction = List<Transaction>()
}
class Transaction: Object {
#objc dynamic var name : String = ""
#objc dynamic var amount : Double = 0
var parentAccount = LinkingObjects(fromType: Account.self, property: "ofTransaction")
}
class AccountCell: UITableViewCell {
#IBOutlet weak var name: UILabel!
#IBOutlet weak var amount: UILabel!
#IBOutlet weak var sum: UILabel!
}
func updateTotal() {
let total = getTransactions
.map { $0.amount }
.reduce(0, +)
let totalFormatter = NumberFormatter()
totallabel.text = totalFormatter.string(for: total)!
}
override func viewDidLoad() {
super.viewDidLoad()
account = realm.objects(Account.self)
getTransactions = realm.objects(Transaction.self)
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! AccountCell
if let transCell = getTransactions?[indexPath.row] {
cell.name.text = transCell.name
cell.amount.text = "\(transCell.amount)"
///Wrong Logic : This logic updates the Sum to Total !
let sumCell = getTransactions
.map { $0.amount }
.reduce(0, +)
cell.sum.text = "\(sumCell)"
}
return cell
}
Use it like below-
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! AccountCell
if let transCell = getTransactions?[indexPath.row] {
cell.name.text = transCell.name
cell.amount.text = "\(transCell.amount)"
//Correct calculation in Loops
var sumUp = 0.0
for (index,trans) in getTransactions.enumerated() {
if (index == indexPath.row) { break }
sumUp += trans.amount
cell.sum.text = "\(sumUp)"
}
}
return cell
}
Related
I have a table consist of more than 20 rows and each row has 3 text field named Rate, minutes and total. calculation is as rate/60 * minutes = total. I want to get total of all 20 rows in another text field Balance that is not a part of table view cell but of view controller. I want to calculate the value in real time and get the addition of total text field named as Rupees Text field below table view cell enter image description here.I can see on 3 rows on screen and 17 other are down. I get for only 3 text field in row not for all. I h have written this code but can calculate for 3 rows not for all 20. Please do check Thanks in advance. second issue I have used notification to pass string and it send me the last value in string not the updated one . Please do check the image
import UIKit
class calculationTVC: UITableViewCell, UITextFieldDelegate{
var numberOfItems = 0
var myTV : UITableView!
#IBOutlet weak var minutes: UITextField!
#IBOutlet weak var total: UITextField!
#IBOutlet weak var rate: UITextField!
#IBAction func rateChanged(_ sender: UITextField) {
// print(minutes.text, total.text, rate.text)
ratesischanged()
}
public func ratesischanged(){
let first = Double(rate.text!)
let second = Double(minutes.text!)
let third = Double(0)
if minutes.text?.count == 0{
total.text = "\(third)"
}
if minutes.text?.count == 0{
// print("null")
total.text = "\(third)"
// print("got zero")
}else{
let output = Double((first!/60) * second!)
total.text = "\(output)"
let rows = myTV.numberOfRows(inSection: 0)
var add : Double!
var c = [Int]()
for i in 0..<rows{
let path = IndexPath(row: i, section: 0)
let cell = myTV.cellForRow(at: path) as? calculationTVC
add = (cell?.total.text as NSString?)?.doubleValue
// print(Int(add))
//print(add)
if add != nil{
c += [Int(add)]
// print(c)
}
}
var sum = 0
var counter = 0
// Enter your code below
while counter < c.count {
var newValue = c[counter]
sum += newValue
counter += 1
// print(sum, "sum")
}
var myString = String(describing: sum)
NotificationCenter.default.post(name: calculationScreen.notificationName, object: nil, userInfo: ["DataMy": myString ?? ""])
//print(ar, "new ar")
}
}
func numberOfRows(numberInt : Int,tableView : UITableView){
numberOfItems = numberInt
myTV = tableView
}
}
you can define protocol on tableViewCell and fire it when the UITextField on each cell modified, and in your viewController implement of delegate:
protocol TableViewCellDelegate: class {
func textViewValueChanged(minutes: String, rate: String, total: String)
}
class calculationTVC: UITableViewCell { // delete UITextFieldDelegate
weak var delegate: TableViewCellDelegate?
// ... the rest of your code
#IBAction func textFieldEditingChanged(_ sender: UIButton) { //action of ***Editing Changed*** and connected to all *UITextField*
//calculate total
delegate?.textViewValueChanged(minutes: self.minutes, rate: self.rate, total: self.total)
}
}
class viewController: UIViewController, UITableView, UITableViewDelegate, UITableViewdataSource, TableViewCellDelegate { //and what you want to implement
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellIdentifier", for: IndexPath) as? calculationTVC
cell.delegate = self
// ... the rest of your code
return cell
}
func textViewValueChanged(minutes: String, rate: String, total: String) {
var total = 0
for i in 1...20 {
let cell = tableView.cellForRow(at: i) as? calculationTVC
total += cell.rate/60 * cell.minutes
}
//set your totalLabel
}
}
Hey guys I´m in the process of learning swift right now and I try to program a Game. I want to show a list with items and different attributes to these Items.
So first I have User choice of where they can select either Food or Toys or other stuff coming in the future. Here I tried to only do one ViewController and change the stuff inside depending on the choice. Right now I have these Items in an Array from a Class.
They look like this:
class FoodData {
var name: String
var description = "Basic Food"
var owned = 0
var taste = 0
var price = 0
var water = 0
var image = "default.png"
init(name: String){
self.name=name
}
}
class ToyData {
var name: String
var description = "Basic Item"
var owned = 0
var price = 0
var joy = 0
var image = "default.png"
init(name: String){
self.name=name
}
}
I initialise these with:
var foodLoad=[FoodData]()
func loadFoodData(){
foodLoad.append(FoodData(name: "IceCream"))
foodLoad[0].description="Very Cold"
foodLoad[0].owned=10
}
Same style for the Toys. Now I have these two Classes in two Arrays called foodLoad[i] and toyLoad[I]
For the Table View I fill it with the protocols
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let shopCell = tableView.dequeueReusableCell(withIdentifier: shopCellIdentifier, for: indexPath) as! ShopCellStyle
shopCell.shopNameLabel?.text = shopData[indexPath.row].name
shopCell.shopImageView?.image = UIImage(named: shopData[indexPath.row].image)
shopCell.shopPriceLabel?.text = String(shopData[indexPath.row].price) + currency
return shopCell
So my Idea was to assign shopData with the User choice.
But if I assign shopData = foodLoad, I can't change that to toyLoad anymore. So maybe you can give me a hint of how to solve this the best way.
for your cellForRowAt indexPath:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let shopCell = tableView.dequeueReusableCell(withIdentifier: "shopCellIdentifier", for: indexPath) as! ItemCell
if selection == "food" {
shopCell.shopNameLabel?.text = foodLoad[indexPath.row].name
shopCell.shopImageView?.image = UIImage(named:
foodLoad[indexPath.row].image)
shopCell.shopPriceLabel?.text = String(foodLoad[indexPath.row].price) + currency
}
else if selection == "toys" {
shopCell.shopNameLabel?.text = toyLoad[indexPath.row].name
shopCell.shopImageView?.image = UIImage(named:
toyLoad[indexPath.row].image)
shopCell.shopPriceLabel?.text = String(toyLoad[indexPath.row].price) + currency
}
return shopCell
}
You'd also want the numberOfRowsInSection UITableView function. Call tableview.reloadData() when the user changes the type selection.
I'm a beginning iOS developer and I've been stuck on an issue for quite some time now.
Background: I have a single viewcontroller with a tableview. It holds 4 dynamic prototype cells: prototypecell 1 has an UITextField and a couple of labels, prototypecell 2 has an image, prototypecell 3 and 4 only have a single label. (Note: prototype cell 4 is based on an array in the object and can have 1 or more cells)
Upon opening the screen, only the first cell with the UITextField should be visible. When an user enters a number (max. 3 digits) in this UITextField, the number has to be compared to check if it is an existing number in an array of objects. If this number proves correct, 2 things will have to happen:
The labels in the first cell will need to change layout (colour,
font, size, ...). And the first cell changes rowHeight.
The other 3 cells will have to appear and show data that corresponds to the number.
If the number is wrong, no extra cells appear and the first cell updates to tell the user it was incorrect.
Problem: I'm having issues with animating the way the 3 cells appear and disappear based on the numberinput in the first cell. More specifically the method "toggleCellsVisibility" and how it relates to the creation of the cells.
I tried multiple combinations of: beginUpdates(), endUpdates(), reloadData(), reloadRowsAtIndexPath(), DispatchQueue.main.async {} and UIView.Animate(). But the only way my code works is if I implement the toggleCellsVisibility method as below, which does not give me any animation options.
I seem to be doing something wrong with the creation and reloading of the data. I think this is related to the use of the global variables indexOfObject and tempObject which hold dummy data when the screen is loaded, and are then shown and overridden with the actual data when a correct number is given.
Summary: Could I somehow insert/create the 3 cells when a correct number is inputted in the first cell, while also animating this change with this particular setup?
Additional:
Can I show/hide the extra cells by not using the heightForRowAtIndexPath? It would be better if the cells could self-size.
I selected and simplified the relevant pieces of code:
1) TableView Class:
class TableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, updateUITableView {
var objects: [Object] = [
name: "test1", number: "test2", image: "imageName1", color: UIColor.black, array: ["test3", "test4"],
[name: "test1", number: "test2", image: "imageName2", color: UIColor.white, array: ["test3", "test4", "test5"]
//...
]
var cellsRowHeight = false
var indexOfObject: Int = 0
var tempObject = Object[name: "test1", number: "test2", color: UIColor.blue, image: "imageName3", array: ["test3"]]
#IBOutlet var tableView: UITableView!
// MARK: - Table view data source
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let numberOfRows = 3 + tempObject.subArray.count
return numberOfRows
}
//START - Input received from textfield in first cell
func checkNumberInput(senderCell: SearchInputTableViewCell, number: String) {
// step 1: Check if number exists
let match = checkNumberMatch(numberInput: number)
// step 2: Change lay-out of input cell
senderCell.changeLayout(numberMatch: match.0, name: match.1?.name, color: match.1?.color)
// step 3: Show/hide cells
toggleCellsVisibility(numberMatch: match.0)
}
//STEP 1: Check if the number corresponds to an existing number
func checkNumberMatch(numberInput: String) -> (Bool, Object?) {
var returnObject: Object?
var tuple = (false, returnObject)
for (index, object) in objects.enumerated() where object.number == numberInput {
returnObject = object
tuple = (true, returnObject)
tempObject = object
indexOfObject = index
}
return tuple
}
//STEP 3 = !!PROBLEM!!
func toggleCellsVisibility(numberMatch: Bool) {
cellsRowHeight = numberMatch
// if/else because of additional future implementation
if numberMatch == true { //Cells appear
DispatchQueue.main.async {
self.tableView?.reloadData()
}
} else { //Cells disappear
DispatchQueue.main.async {
self.tableView?.reloadData()
}
}
}
//STEP 4:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
switch (indexPath.row) {
case 0 where !cellsRowHeight:
return 187
case 0 where cellsRowHeight:
return 170
case 1 where !cellsRowHeight:
return 0
case 1 where cellsRowHeight:
return 170
case 2 where !cellsRowHeight:
return 0
case 2 where cellsRowHeight:
return 54
case 3...200 where !cellsRowHeight:
return 0
case 3...200 where cellsRowHeight:
return 44
default:
return 44
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cellIdentifier = "InputCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! InputCell
cell.delegate = self
return cell
}
else if indexPath.row == 1 {
let cellIdentifier = "Cell2"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! Cell2
cell.image?.image = UIImage(named: objects[indexOfObject].image)
return cell
}
else if indexPath.row == 2 {
let cellIdentifier = "Cell3"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! Cell3
cell.name?.text = objects[indexOfObject].name
return cell
}
else {
let cellIdentifier = "Cell4"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! Cell4
cell.name?.text = objects[indexOfObject].subArray[(indexPath as IndexPath).row - 3]
return cell
}
}}
2) Cell Class:
protocol updateUITableView: class {
func checkNumberInput(senderCell: SearchInputTableViewCell, number: String)
}
class InputCell: UITableViewCell, UITextFieldDelegate {
var delegate: updateUITableView?
#IBOutlet var textField: UITextField!
#IBOutlet var nameLabel: UILabel!
//... and other labels
func changeLayout(numberMatch: Bool, name: String?, color: UIColor?) {
if numberMatch == true {
//lay-out changes to labels
}
else {
//lay-out changes to labels
}
}
//Set maximum character limit in textField and dismiss keyboard when character limit (3) is reached.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let currentCharacterCount = textField.text?.characters.count ?? 0
let newLength = currentCharacterCount + string.characters.count - range.length
if (newLength == 3) {
textField.text = (textField.text! as NSString).replacingCharacters(in: range, with: string)
//Get text from textField
let numberInput: String? = textField.text! //Get number if 3 characters entered
if numberInput != nil {
delegate?.checkNumberInput(senderCell: self, number: numberInput!)
}
textField.resignFirstResponder()
if (range.length + range.location > currentCharacterCount) {
return false
} else {
return true
}
}
return true
}
}
class Cell2: UITableViewCell {
#IBOutlet var image: UIImageView!
}
class Cell3: UITableViewCell {
#IBOutlet var name: UILabel!
}
class Cell4: UITableViewCell {
#IBOutlet var name: UILabel!
}
Any help would be immensely appreciated! Thank you!
When your action is done (checking that the input match whatever you want), populate the data source you're using for your tableview (you should declare an empty array of objects you want to use as a datasource, and use this), then use reloadData() on your tableview.
For the height of your cells, you can use tableView.rowHeight = UITableViewAutomaticDimension (https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithSelf-SizingTableViewCells.html)
I have a tableView with a custom viewCell which has three labels, displayCarName, displayMpg and displayCarPrice. I'm using three different arrays to feed each label in the tableView, everything is working fine but I feel like there is a better way to do this, may be using a single dictionary instead or something like that.
Here is the code I'm using.
private var carNameList = [String]()
private var mpgList = [Int]()
private var carPriceList = [Double]()
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return carNameList.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reusableCell", forIndexPath: indexPath) as! CustomCell
cell.displayCarName!.text= carNameList[indexPath.row]
cell.displayMpg!.text = String(mpgList[indexPath.row])
cell.displayCarPrice!.text = String(carPriceList[indexPath.row])
return cell
}
Is this a common way to feed multiple labels in a table row? If not, can someone help me improve the code shown above?
FYI- I actually have more than three labels in each row but for simplicity I'm only showing three.
Thanks
What you are using is sometimes referred to as parallel arrays. This approach was commonplace in languages lacking support for structures.
Since Swift does have support for structures, a better approach would be to define a class representing a Car, with three properties for car's Name, Mpg, and Price, and then using a single list of [Car] in place of three separate lists:
class Car {
let Name : String
let Mpg : Int
let Price : Double
init(name: String, mpg : Int, price : Double ) {
Name = name
Mpg = mpg
Price = price
}
}
...
private var carList = [Car]()
...
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reusableCell", forIndexPath: indexPath) as! CustomCell
Car car = carList[indexPath.row]
cell.displayCarName!.text= car.Name
cell.displayMpg!.text = String(car.Mpg)
cell.displayCarPrice!.text = String(car.Price)
return cell
}
Best way is to make a class for your data source
class SingleCellData {
var displayCarName : String!
var displayMpg : Int!
var displayCarPrice : Double!
}
In table View
var cellData : [SingleCellData] = []
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reusableCell", forIndexPath: indexPath) as! CustomCell
let sData = cellData[indexPath.row]
cell.displayCarName!.text= sData.displayCarName
cell.displayMpg!.text = String(sData.displayMpg)
cell.displayCarPrice!.text = String(StringsData.displayCarPrice)
return cell
}
You can also move the text assignment logic to the CustomCell class itself:
// Car.swift
class Car {
var name : String
var mpg : Int
var price : Double
}
// ViewController.swift
...
private var cars = [Car]()
...
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reusableCell", forIndexPath: indexPath) as! CustomCell
cell.car = cars[indexPath.row]
return cell
}
// CustomCell.swift
class CustomCell: UITableViewCell {
...
var car: Car? {
didSet {
displayCarName.text = car?.name
displayMpg.text = car?.mpg
displayPrice.text = car?.price
}
}
}
I am trying to load data from a structure to table view cell, I created a custom cell with three label in it. I have three text field in the view controller and a add button I want that when I fill these three text field and press add it will store these three values in a structure and reload the data of table view. Structure is in other file.
Here is code for structure in DataMaster.swift
struct jobData
{
var CompanyName:Array<String> = ["ram"]
var job:Array<String> = ["shyam"]
var desc:Array<String> = ["dfdf"]
}
Code for addButton function
#IBAction func addButtonTapped(sender: AnyObject) {
var company = txtCompName.text
var job = txtJob.text
var description = txtDesc.text
data.CompanyName.append(company)
data.desc.append(description)
data.job.append(job)
self.jobTableView.reloadData()
print(data.CompanyName)
txtCompName.resignFirstResponder()
txtJob.resignFirstResponder()
txtDesc.resignFirstResponder()
}
The problem is in this code
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath:indexPath) as jobTableViewCell
cell.compLabel.text = data.CompanyName[indexPath.row]
cell.jobLabel.text = data.job[indexPath.row]
cell.descLabel.text = data.desc[indexPath.row]
return cell
}
when it reaches to this code to load data in table it crashes
Thread 1:EXC_BREAKPOINT(code=EXC_I386_BPT,subcode=0x0)
Here below is code.
struct jobData
{
var CompanyName:String!
var job:String!
var desc:String!
}
Take an array as var datas = [jobData]()
Now in Action method
#IBAction func addButtonTapped(sender: AnyObject) {
var company = txtCompName.text
var job = txtJob.text
var description = txtDesc.text
let dataObject = jobData(company: company, job: job, desc: description)
datas.append(dataObject)
self.jobTableView.reloadData()
txtCompName.resignFirstResponder()
txtJob.resignFirstResponder()
txtDesc.resignFirstResponder()
}
Now in cellForRowAtIndex method
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath:indexPath) as! jobTableViewCell
let data = datas[indexPath.row]
if let companyName = data.CompanyName {
cell.compLabel.text = companyName
}
if let job = data.job {
cell.jobLabel.text = job
}
if let descr = data.desc {
cell.descLabel.text = descr
}
return cell
}
in numberofRowsInSection method return datas.count
Check why data.CompanyName is empty and make sure all text field will have text.