Best way of working with ListView - ios

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.

Related

Calculating sum until the indexPath.row instead of total using tableView

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
}

Adding a section at specific cell

I have implemented a tableView using PLIST to set properties.
I would like to add three sections at specific row. (row 12, row24, row 35)
I have tried with following code but it will be too much code and not working well.
Images and code are added below.
import UIKit
class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tblStoryList: UITableView!
var array = PLIST.shared.mainArray
var array = PLIST.shared.mainArray
let sections: [String] = ["First stage","Second Stage","Third Stage"]
let s1Data : [String] = ["Row1","Row2","Row3"]
let s2Data : [String] = ["Row4","Row5","Row6"]
let s3Data : [String] = ["Row7","Row8","Row9"]
var sectionData: [Int: [String]] = [:]
override func viewDidLoad() {
super.viewDidLoad()
sectionData = [0: s1Data, 1: s2Data, 2: s3Data]
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (sectionData[section]?.count)!
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section]
}
func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->
UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "StoryTableviewCell", for: indexPath) as! StoryTableviewCell
//making plist file
let dict = self.array[indexPath.row]
let title = dict["title"] as! String
let imageName = dict["image"] as! String
let temp = dict["phrases"] as! [String:Any]
let arr = temp["array"] as! [[String:Any]]
let detail = "progress \(arr.count)/\(arr.count)"
//property to plist file
cell.imgIcon.image = UIImage.init(named: imageName)
cell.lblTitle.text = title
cell.lblSubtitle.text = detail
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
}
The indexPath.row you are getting in the tableView's cellForRowAt is relative to the section. You cannot use it directly as the index of your main array (which has all the rows).
You will need to perform a simple calculation to convert the indexPath.row to an index of that array (by offsetting the row with the total item count of previous sections) :
let index = [0,12,36][indexPath.section] + indexPath.row
let dict = array[index]
The same thing applies to the response you give to numberOfRowsInSection:
return [12,24,35][section]
I find it a bit odd that the data structure (PLIST) would be so rigid that it always contains exactly those number of entries and will never change. I would suggest a more generalized approach if only to avoid spreading hard coded numbers (e.g. 12,24,35,36) all over the place.
for example:
// declare section attributes in your class
let sectionTitles = ["First stage","Second Stage","Third Stage"]
let sectionSizes = [12,24,35] // central definition, easier to maintain (or adjust to the data)
let sectionOffsets = sectionSizes.reduce([0]){$0 + [$0.last!+$1] }
// and use them to respond to the table view delegate ...
let index = sectionOffsets[indexPath.section] + indexPath.row
let dict = array[index]
// ...
return sectionSizes[section] // numberOfRowsInSection
Using this approach, you shouldn't need to create sectionData (unless you're using it for other purposes elsewhere).
BTW, in your sample code, the sectionData content is hard coded with data that is not consistent with the expected section sizes so it would not work even with a correct index calculation.
you can try to use switch case in tableView:cellForRowAtIndexPath:

Confused about using UserDefaults to ensure deleted items stay removed in a table view

EDIT: I've been trying to figure this out for 8 hours now, if someone understands what I'm doing wrong with UserDefaults, please let me know what I need to do. This is driving me insane.
I am populating a tableview with a setPage() function in viewDidLoad (depending on which cell was tapped on the previous tableview)
func setPage() {
let pageTitle = self.navigationItem.title
let userDefaults = UserDefaults.standard
let productData = userDefaults.object(forKey: "products") as? [String]
let productImageData = userDefaults.object(forKey: "productImages") as? [String]
ProductController.products = productData!
ProductController.productImages = productImageData!
switch pageTitle! {
case "Apple":
ProductController.products = appleProducts
ProductController.productImages = appleImages
case "Google":
ProductController.products = googleProducts
ProductController.productImages = googleImages
case "Twitter":
ProductController.products = twitterProducts
ProductController.productImages = twitterImages
case "Tesla":
ProductController.products = teslaProducts
ProductController.productImages = teslaImages
default:
ProductController.products = samsungProducts
ProductController.productImages = samsungImages
}
tableView.reloadData()
}
and in cellForRowAt indexPath
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! ProductCell
cell.textLabel?.text = ProductController.products[indexPath.row]
DispatchQueue.main.async {
cell.productLogoView.image = UIImage(named: ProductController.productImages[indexPath.row])!
}
return cell
}
And here's where I handle deleting cells
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool
{
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath)
{
if editingStyle == .delete
{
tableView.beginUpdates()
ProductController.products.remove(at: indexPath.row)
ProductController.productImages.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
let userDefaults = UserDefaults.standard
userDefaults.set(ProductController.products, forKey: "products")
userDefaults.set(ProductController.productImages, forKey: "productImages")
tableView.endUpdates()
}
}
If I navigate off the page and then go back, the deleted cell is there again. I realize that this is because I populate the tableView in viewDidLoad and I think using UserDefaults is the way to solve this, but I'm unsure of how to implement it in this situation. I have read the docs and looked around for a solution but I can't find anything that helps me in this particular use case.
How can I get the deleted cell to stay deleted until the app is relaunched?
Initializing the arrays - I have empty arrays of products and productImages, so setPage() checks the title and then populates the empty arrays with the corresponding company's products and product images.
static var products = [String]()
var appleProducts = ["iPad", "iPod", "iPhone"]
var googleProducts = ["Search", "Firebase", "Pixel"]
var twitterProducts = ["Twitter", "Periscope", "Vine"]
var teslaProducts = ["Tesla Model S", "Tesla Model X", "Tesla Powerwall"]
var samsungProducts = ["Samsung Galaxy", "Samsung Note", "Samsung Tab"]
static var productImages = [String]()
var appleImages = ["iPad", "iPod", "iPhone"]
var googleImages = ["GoogleLogo", "Firebase", "Pixel"]
var twitterImages = ["TwitterLogo", "Periscope", "Vine"]
var teslaImages = ["TeslaS", "TeslaX", "TeslaPowerwall"]
var samsungImages = ["SamsungGalaxy", "SamsungNote", "SamsungTab"]
UserDefaults is just a bunch of pairs of keys and values. So, you could assign a boolean value ("hidden") for each product:
// get the current product - this depends on how your table is set up
let product = products[indexPath.row]
// set the boolean to true for this product
UserDefaults.set(true, forKey: product)
Then, wherever you're initializing your products, add:
// only include elements that should not be hidden
products = products.filter({ UserDefaults.boolForKey($0) == false })
You could also store all of the

What is the best aproach to feed data to a custom UITableViewCell that has multiple labels - Swift

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
}
}
}

How to load data in UITableView cell from structure

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.

Resources