Save an array of multiple selected UITableViewCell values - ios

I'm stuck with a very specific problem while using a Table View (XCode 9, Swift 4). What I want to do is, make an array named foodDetailInfoArray with text values of the foodName label in the table cells which have been selected manually by the user. Currently, while the .setSelected method works for changing the UI for a cell as I want, it isn't helping me record the foodName.text value properly. The problem is that the text values get recorded even while scrolling the table view and the array values get replaced as well. Below is the code and a sample of the printed output.
var foodDetailInfoArray: [String] = []
#IBOutlet var unselectedCell: UIView!
#IBOutlet var foodName: UILabel!
#IBOutlet var carbonValue: UILabel!
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
if selected == true {
self.unselectedCell.backgroundColor = UIColor.init(red: 4/255, green: 206/255, blue: 132/255, alpha: 1)
self.foodName.textColor = UIColor.white
self.carbonValue.textColor = UIColor.white
foodDetailInfoArray.append(foodName.text!)
} else {
self.unselectedCell.backgroundColor = UIColor.clear
self.foodName.textColor = UIColor.black
self.carbonValue.textColor = UIColor.black
}
print(foodDetailInfoArray)
}
The print statement gives me this sort of result:
(This is when the cells are not even selected and I'm just scrolling the table view.)
["pepper"]
["pasta"]
["pasta", "pepper"]
["pepper"]
["pepper", "pasta"]
["stir-fry"]
["stir-fry", "stir-fry"]
["vegetable"]
["vegetable", "vegetable"]
Whereas, what I ideally want would be (in the order of clicking the cell that contains given foodName):
["pasta"]
["pasta", "pepper"]
["pasta", "pepper", "tomato"]
["pasta", "pepper", "tomato", "stir-fry"]
and if a certain cell is deselected then the name has to be dropped, ie if tomato is deselected, then array would be
["pasta", "pepper", "stir-fry"]
... and so on
PS: I'm not a programmer by profession and altogether self taught recently, so please let me know if the question is unclear in any way.

I would handle the selection and deselection of the cell via the view controller, so you can also use your foodDetailInfoArray better. With the help of this answer you could do it like that way:
import UIKit
class ViewController: UITableViewController {
// example data
let names = [ "pepper", "pasta", "stir-fry", "vegetable"]
var foodDetailInfoArray: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
// allow multiselection
tableView.allowsMultipleSelection = true
}
// MARK: UITableViewDataSource
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 4
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: "cell")
cell.textLabel?.text = names[indexPath.row]
// Don't show highlighted state
cell.selectionStyle = .none
return cell
}
// MARK: UITableViewDelegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// also do your UI changing for the cell here for selecting
// Add your food detail to the array
foodDetailInfoArray.append(names[indexPath.row])
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
// also do your UI changing for the cell here for deselecting
// Remove your food detail from the array if it exists
if let index = foodDetailInfoArray.index(of: names[indexPath.row]) {
foodDetailInfoArray.remove(at: index)
}
}
}
Result

I would try the delegate method didSelectRowAtIndexPath for tableViews. Have your view controller adopt the UITableViewDelegate protocol and implement the following.
Suppose you have a foods array, and a foodsSelected array that's initially empty.
let foods:[String] = ["Apples","Avocado","Bananas"]
var foodsSelected:[String] = []
Now whenever a cell is selected, this delegate method is called and add or remove the selected food from the foodsSelected array.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//Check if the selected food is in the foodsSelect array
if(!foodsSelected.contains(foods[indexPath.row])){
//If it's not, append it to the array
foodsSelected.append(foods[indexPath.row])
}else{
//If it is, remove it from the array.
//Note there are many ways to remove an element from an array; I decided to use filter.
foodsSelected = foodsSelected.filter({$0 != foods[indexPath.row]})
}
print(foodsSelected)
}
Here is the output when I select these items in order: Apples, Avocado,Bananas,Avocado
["Apples"]
["Apples", "Avocado"]
["Apples", "Avocado", "Bananas"]
["Apples", "Bananas"]

Related

sort tableViewCells by numbers inside a label

I try to sort the tableViewCells by numbers inside a label, so the cell which includes the highest number in a label should be last, and vice versa.
I tried it with different solutions like following, but it's simply not working, it also doesn't show any error code
I don't know if there is just a small mistake or if it is all completely wrong, but if so, I hope that you know a completely different way to solve it.
TableView:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// download jobs
jobsRef.observe(.value, with: { (snapshot) in
self.jobs.removeAll()
for child in snapshot.children {
let childSnapshot = child as! DataSnapshot
let job = Job(snapshot: childSnapshot)
print(job)
self.jobs.insert(job, at: 0)
}
filterLocation()
self.tableView.reloadData()
})
}
var jobArr = JobTableViewCell.jobDistance!.jobArr
func filterLocation() {
jobArr.sort() { $0.distance.text > $1.distance.text}
}
TableViewCell:
#IBOutlet weak var distance: UILabel!
static var jobDistance: JobTableViewCell?
var jobArr = [JobTableViewCell.jobDistance!.distance.text]
override func layoutSubviews() {
super.layoutSubviews()
JobTableViewCell.jobDistance = self
}
lets check out apple doc for the table view https://developer.apple.com/documentation/uikit/uitableviewdatasource
as it says there is method:
func tableView(UITableView, cellForRowAt: IndexPath) -> UITableViewCell
we can read it like "give me[UITableView] cell[-> UITableViewCell] for this index[cellForRowAt]"
so all we need is just map our data source to tableview indexes:
e.g.
we have datasource array of strings
var dataSource = ["String", "Very long string", "Str"]
sort...
> ["Str", "String", "Very long string"]
and then just provide our data to cell (your tableview must conform UITableViewDataSource protocol)
// Provide a cell object for each row.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Fetch a cell of the appropriate type.
let cell = tableView.dequeueReusableCell(withIdentifier: "cellTypeIdentifier", for: indexPath)
// Configure the cell’s contents.
cell.textLabel!.text = dataSource[indexPath]
return cell
}
The problem is you sort another array jobArr
jobArr.sort() { $0.distance.text > $1.distance.text}
and append values to another one jobs

UITableViewCell dequeuereusablecellwithidentifier returns the same cell

I am creating a UITableView that enables the user to add a variable amount of data. Table looks like this initially:
When the user clicks on the "+" button, i would like to add a new cell with a UITextField for entering data. This new cell is a Custom UITableViewCell called "RecordValueCell". Here's what is looks like:
//Custom UITableViewCell
class RecordValueCell : UITableViewCell {
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var deleteButton: UIButton!
var onButtonTapped : ((_ sender : UIButton)->Void)?
#IBAction func deleteButtonTouched(_ sender: Any) {
guard let senderButton = sender as? UIButton else {
return
}
onButtonTapped?(senderButton)
}
}
However when i try to add another cell, using the tableView.dequeueReusableCell(withIdentifier: ) function, it seems to return the same cell. And here is what my UI looks like:
Empty space at the top of the section where my new cell should be. Here is the code to add the cell:
func addNewValueCell() {
guard let reusableValueCell = self.tableView.dequeueReusableCell(withIdentifier: "valueCell") as? RecordValueCell else {
fatalError("failed to get reusable cell valueCell")
}
var cell = Cell() //some custom cell Object
//add the gray horizontal line you see in the pictures
reusableValueCell.textField.addBorder(toSide: .Bottom, withColor: UIColor.gray.cgColor, andThickness: 0.5)
reusableValueCell.onButtonTapped = { (sender) in
self.removeValue(sender: sender)
}
cell.cell = reusableValueCell
self.sections[self.sections.count - 1].cells.insert(cell, at: 0)
//When i put a break point at this spot, i find that reusableValueCell is the same object as the cell that is already being used.
tableView.reloadData()
reusableValueCell.prepareForReuse()
}
When i debug it, i find that dequeueReusableCell(withIdentifier: ) returns the exact same RecordValueCell multiple times.
Here is my cellForRowAt:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = self.sections[indexPath.section].cells[indexPath.row].cell else {
fatalError("error getting cell")
}
return cell
}
numberOfRowsInSection
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.sections[section].cells.count
}
First of all, you will need to set the View Controller Class that this table is contained in as the table's UITableViewDataSource
tableView.dataSource = self // view controller that contains the tableView
Create an array of strings as member of your View Controller class which contains the data for each cell:
var strings = [String]()
Then you will need to implement the following method for the UITableViewDataSource protocol:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return strings.count
}
You should also be dequeueing the cells in your cellForRowAt method like so:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: yourIdentifier) as! YourCellClass
cell.textLabel = strings[indexPath.row]
return cell
}
Then whenever the user enters into the textField, their input will be appended to this array:
let input = textField.text
strings.append(input)
tableView.reloadData()
Once the data is reloaded, the cell will be added to the table automatically since the number of rows are defined by the String array's length and the label is set in the cellForRowAt method.
This feature is very easy to implement if you will do in a good way.
First, you have to create two TableCell. First to give the option to add a record with plus button and second for entering a value with textfield. Now always return first cell (AddRecordTableCell) in the last row in tableView, and return the number of rows according to entered values like
return totalValues.count + 1

Table View data appears on tableview with row selection code

I guess this could be one of my rookie mistakes I couldn't figure out.
I have an app which has a table view. It has text label and detail text label.
When I select a row, I takes me to another story board using segue...all of this works fine except the table view display on my simulator.
detail text label shows up on the simulator shown in this picture circled.
Here is the code I am using to detect cell/row selected. When I comment it out this issue goes away...
What you see in the red circle is gradeselected which is also in the detail text label in the tableview.
func sectionIndexTitles(for tableView: UITableView) -> [String]? {
let gradeselected = String(describing: sgrade)
return [gradeselected]
}
Screenshot of simulator with the issue
Please help in resolving this issue. Let me know if you need any more info.
Xcode 9.1
Swift 4
#Caleb here is my code.
import UIKit
class StudentsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var cellButton: UIButton!
#IBOutlet weak var studentDetailTable: UITableView!
var sname:[String]?
var sgrade:[Int]?
var gradetext = "Grade:"
var sstudentname = ""
override func viewDidLoad() {
super.viewDidLoad()
studentDetailTable.delegate = self
studentDetailTable.dataSource = self
// Do any additional setup after loading the view.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sname!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = studentDetailTable.dequeueReusableCell(withIdentifier: "cell")
cell?.textLabel?.text = sname[indexPath.row] + gradetext + String(sgrade[indexPath.row])
sstudentname = sname![indexPath.row]
cell?.detailTextLabel?.text = String(sgrade![indexPath.row])
cell?.layer.cornerRadius = (cell?.frame.height)!/2
cell?.backgroundColor = UIColor.blue
cell?.textLabel?.textColor = UIColor.white
cell?.layer.borderWidth = 6.0
cell?.layer.cornerRadius = 15
cell?.layer.borderColor = UIColor.white.cgColor
cell?.textLabel?.textColor = UIColor.white
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedIndex = tableView.dataSource?.sectionIndexTitles!(for: studentDetailTable)
let indexPath = tableView.indexPathForSelectedRow
let currentCell = tableView.cellForRow(at: indexPath!)!
let scell = currentCell.detailTextLabel!.text!
sstudentname = (currentCell.textLabel?.text)!
}
// - If I comment this section of the code issue goes away.
func sectionIndexTitles(for tableView: UITableView) -> [String]? {
let gradeselected = String(describing: sgrade)
return [gradeselected]
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let myKLVC = segue.destination as! KindergartenLevelViewController
myKLVC.klvstudentname = sstudentname
}
The text in the red circle says [1, 2], which looks like the array that probably holds all the grades, not just the one for a specific cell that we see in the string gradeselected. If you have such an array in your code, look for places where you might be converting it to a string and drawing it. Maybe you did that in an earlier iteration of your code to make sure that the array contained what you thought, or something?
Arrays don't just mysteriously draw themselves on the screen — somewhere, there's some code that causes that to happen. We can't really help you find it because you haven't shown very much of your code, but just knowing what to look for may help you find it yourself.
You can query the selected row via table view's property indexPathForSelectedRow.
The method you have implemented does exactly what you see in the simulator.
Just have a look at the documentation:
property indexPathForSelectedRow: https://developer.apple.com/documentation/uikit/uitableview/1615000-indexpathforselectedrow
func sectionIndexTitles: https://developer.apple.com/documentation/uikit/uitableviewdatasource/1614857-sectionindextitles

UITableView Duplicate cells (custom cells with textfields)

I have spent days on resolving this issue and after trying much I am asking a question here. I am using a custom UITableViewCell and that cell contains UITextFields. On adding new cells to the table view, the table view behaves abnormal like it duplicates the cell and when I try to edit the textfield of new cell, the textfield of previous cel gets edited too.
The behavior of duplication is as follows: 1st cell is duplicated for 3rd cell. I don't know this is due to reusability of cells but could anyone tell me about the efficient solution?
I am attaching the screenshot of UITableViewCell.
The code for cellForRow is as follows:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : Product_PriceTableViewCell = tableView.dequeueReusableCell(withIdentifier: "product_priceCell") as! Product_PriceTableViewCell
cell.dropDownViewProducts.index = indexPath.row
cell.txtDescription.index = indexPath.row
cell.tfPrice.index = indexPath.row
cell.dropDownQty.index = indexPath.row
cell.tfTotalPrice_Euro.index = indexPath.row
cell.tfTotalPrice_IDR.index = indexPath.row
cell.dropDownViewTotalDiscount.index = indexPath.row
cell.dropDownViewDeposit.index = indexPath.row
cell.tfTotalDeposit_Euro.index = indexPath.row
cell.tfRemaingAfterDeposit_IDR.index = indexPath.row
return cell
}
The issue is the cell is being reused by the UITableView, which is what you want to happen for good scrolling performance.
You should update the data source that supports each row in the table to hold the text the user inputs in the field.
Then have the text field's text property assigned from your data source in cellForRowAt.
In other words, the UITableViewCell is the same instance each time you see it on the screen, and so is the UITextField and therefore so is it's text property. Which means it needs to be assigned it's correct text value each time cellForRowAt is called.
I'm unsure of your code so I have provided an example of how I would do something like what you want:
class MyCell: UITableViewCell {
#IBOutlet weak var inputField: UITextField!
}
class ViewController: UIViewController {
#IBOutlet weak var table: UITableView!
var items = [String]()
fileprivate func setupItems() {
items = ["Duck",
"Cow",
"Deer",
"Potato"
]
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
setupItems()
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// the # of rows will equal the # of items
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// we use the cell's indexPath.row to
// to get the item in the array's text
// and use it as the cell's input field text
guard let cell = tableView.dequeueReusableCell(withIdentifier: "myCell") as? MyCell else {
return UITableViewCell()
}
// now even if the cell is the same instance
// it's field's text is assigned each time
cell.inputField.text = items[indexPath.row]
// Use the tag on UITextField
// to track the indexPath.row that
// it's current being presented for
cell.inputField.tag = indexPath.row
// become the field's delegate
cell.inputField.delegate = self
return cell
}
}
extension ViewController: UITextFieldDelegate {
// or whatever method(s) matches the app's
// input style for this view
func textFieldDidEndEditing(_ textField: UITextField) {
guard let text = textField.text else {
return // nothing to update
}
// use the field's tag
// to update the correct element
items[textField.tag] = text
}
}
I suggest to do the following
class Product_PriceTableViewCell: UITableViewCell {
var indexRow: Int = -1
func configureCell(index: Int) {
cell.dropDownViewProducts.clean()
...
cell.tfRemaingAfterDeposit_IDR.clean()
}
}
where clean is the function to empty de view (depend on the type)
Then in the delegate:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : Product_PriceTableViewCell = tableView.dequeueReusableCell(withIdentifier: "product_priceCell") as! Product_PriceTableViewCell
cell.configureCell(row: indexPath.row)
return cell
}
As #thefredelement pointed out when the cell is not in the view frame, it is not created. Only when the view is going to appear, it tries to reuse an instance of the cell and as the first is available, the table view uses it but does not reinitialize it. So you have to make sure to clean the data
The rest of the answer is for better coding.

UILabel Not Changing Text

I have created an array in a tableView to record how many cells have checkmarks or not, and tried to have the Label text change based on how many checkmarks there are.
My code:
#IBOutlet weak var myLabel: UILabel!
func setText() {
if checkmarks == [0: false] {
self.myLabel.text = "text"
}
if checkmarks == [1: false] {
self.myLabel.text = "text1"
}
}
I am not getting any errors, but the text is not changing. Any advice is appreciated.
UPDATE: The array I am trying to take values from is in another class.
Here is that code:
var checkmarks = [Int: Bool]()
the checkmarks get saved so I thought by writing the above code in a (public class??), my other class files could access it.
Perhaps I need to call checkmarks at the start of the other class file too? Edit: That is an invalid redeclaration my bad.
Thanks for the help
UPDATE 2: The problem was in the interpretation of the array system. I rewrote my code as a loop (which I will add on request) and that fixed it. Thank you all for helping!!
as of my understanding you are trying to show the selected row with tick mark in a tableview, so if it then there is another approach to show it.
consider the following approach.
declare your variables as global to class
var selected = [String]()
let colors = ["Apple","Pear", "Banana","Orange",]
and your datasource and delegate methods should like similar to this
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.selectionStyle = .none
if selected.contains(colors[indexPath.row]) {
cell.accessoryType = .checkmark
}else{
cell.accessoryType = .none
}
cell.textLabel?.text = colors[indexPath.row]
return cell;
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return colors.count
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if selected.contains(colors[indexPath.row]) {
selected.remove(at: selected.index(of: colors[indexPath.row])!)
}else{
selected.append(colors[indexPath.row])
}
tableView.reloadData()
}
}
and finally your output will be like this

Resources