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
Related
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
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"]
(Before you mark as duplicate you have to read the whole question and I am posting this cause I din't found the relevant and proper solution also need the solution in swift)
I have created one demo project and load and displayed name and area from array on custom cell.
I have noticed that after every 5th cell means 6th row is repeating with contents of 0th cell
for e.g.
the demo code is given below
class demoTableCell: UITableViewCell {
#IBOutlet var name : UILabel!
#IBOutlet var area : UILabel!
}
extension ViewController:UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.arrDemo.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "Cell"
var cell : demoTableCell = demoTable.dequeueReusableCell(withIdentifier: cellIdentifier)! as! demoTableCell
cell.name.text = (arrDemo.object(at: indexPath.row) as! NSDictionary).value(forKey: "Name") as? String
cell.area.text = (arrDemo.object(at: indexPath.row) as! NSDictionary).value(forKey: "Area") as? String
if indexPath.row == 0{
cell.name.isHidden = true
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
}
As I hide the first label on 0th cell so I found that 6th row is also effected with implemented functionality of 0th cell. It means that also hide label1 of every 6th cell as I have attached the screenshot below so you can get the exact issue (This issue happened only if table view is scrollable)
As I have try to solve this issue from this link also but facing the same issue and cannot find the proper solution, and I am stuck here.
Cells are reused, you have to make sure that every UI element is set to a defined state.
You are using an if clause but there is no else case or a default value.
Simple solution:
Just replace
if indexPath.row == 0 {
cell.name.isHidden = true
}
with
cell.name.isHidden = indexPath.row == 0
this sets the hidden property always to a defined state.
And the usual dont's
Do not use NSDictionary in Swift.
Do not valueForKey unless you really need KVC (actually here you don't).
Remember - the cells are being reused.
You hide the cell, but you never explicitly unhide the cell
When you come to row 6, you are re-using the cell that was at row 0, and isHidden = true
All you need to do is extend your check, and hide the rows that you need to be hidden, and explicitly show the cells that you need to see. If you also have a moving banner that you add - you will also need to check to see if it's been loaded, and remove it if not required. Remember - it may not be row 6 - that's just how it works out with the current screensize
If you do have significant differences between the cells you want to use, you might be better using two different classes - and then you don't have to think about hiding labels
class demoTableCell: DemoTableCellNormalRow {
#IBOutlet var name : UILabel!
#IBOutlet var area : UILabel!
}
class demoTableCell: DemoTableCellFirstRow {
#IBOutlet var area : UILabel!
#IBOutlet var movingBannerView : LCBannerView!
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "Cell"
if row == 0 {
var cell : demoTableCell = demoTable.dequeueReusableCell(withIdentifier: cellIdentifier)! as! DemoTableCellFirstRow
cell.area.text = (arrDemo.object(at: indexPath.row) as! NSDictionary).value(forKey: "Area") as? String
// populate the bannerview which already exists, or create a new one
return cell
} else {
var cell : demoTableCell = demoTable.dequeueReusableCell(withIdentifier: cellIdentifier)! as! DemoTableCellNormalRow
cell.name.text = (arrDemo.object(at: indexPath.row) as! NSDictionary).value(forKey: "Name") as? String
cell.area.text = (arrDemo.object(at: indexPath.row) as! NSDictionary).value(forKey: "Area") as? String
return cell
}
}
Implement prepareForReuse in your cell class
override func prepareForReuse() {
name.isHidden = false
}
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.
I am having a section with custom tableViewCell and this cell has two text fields.
This cell is used to display phone number of a user - country code in one text field and the number in other text field.
I fetch data from a service and if the user has more than one phone, based on the count of the phones, I am updating numberOfRowsInSection and cellForRowAtIndexPath. I do not have any issue till here.
For example, if user has two phones, I return 2 in numberOfRowsInSection, and shows two cells (same custom cell with two textFields).
The Problem:
I get the same data - the second phone number details - in both the cells. But I need to have list of phone numbers displayed one after the other in my custom cell textfields.
Here is my code:
numberOfRowsInSection
public override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 3:
return (user.phones?.count)!
default:
return 1
}
}
cellForRowAtIndexPath
public override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
case .ContactPhones:
if contactCellExpanded == true {
cell = tableView.dequeueReusableCellWithIdentifier("PhoneCell") as? PhoneCell
}
case .ContactEmail:
if contactCellExpanded == true {
cell = tableView.dequeueReusableCellWithIdentifier("EmailCell") as? EmailCell
}
default:
break
}
PhoneCell
import UIKit
class PhoneCell: ProfileCell {
#IBOutlet weak var countryCode: UITextField!
#IBOutlet weak var addPhoneButton: UIButton!
#IBOutlet weak var teleNumber: UITextField!
#IBAction func addPhoneButtonAction(sender: AnyObject) {
}
override func configure(withUser user: XtraUser, language: String, indexPath: NSIndexPath) {
super.configure(withUser: user, language: language, indexPath: indexPath)
self.countryCode.borderStyle = .RoundedRect
self.teleNumber.borderStyle = .RoundedRect
if let userPhoneInfo = user.phones {
for i in 0..<userPhoneInfo.count {
print ("numbers \(userPhoneInfo[i].number)")
self.countryCode.text = userPhoneInfo[i].country
self.teleNumber.text = userPhoneInfo[i].number
}
}
}
}
I understand that I am using a for loop to get the numbers list when available, but how do I assign each value to the cell's textField?
Please Help!
Thanks in Advance
Since in configure method you know the index path of the cell why dont you just use
override func configure(withUser user: XtraUser, language: String, indexPath: NSIndexPath) {
super.configure(withUser: user, language: language, indexPath: indexPath)
self.countryCode.borderStyle = .RoundedRect
self.teleNumber.borderStyle = .RoundedRect
print("Enumerated \(user.phones?.enumerate())")
if let userPhoneInfo = user.phones {
self.countryCode.text = userPhoneInfo[indexPath.row].country
self.teleNumber.text = userPhoneInfo[indexPath.row].number
}
}
But anyway the best solution is to have model for each cell and pass it to this configure method.
Wrong logic implementation in numberOfRows method.
In that method set number of rows according to your data in response.