say me, please, how better compare two same (label has name "labelNumber") labels from different rows in Tableview
For example: I know, that in row №0 label is "06" (Int) and in next cell (row №1) this label is "07". So, "07" > "06". How compare it with swift language?
Thanks!
The wrong way
As stated by #vadian, you should NOT use the UI you populated to perform calculations on data.
However this is the code to compare the values inside 2 UITableViewCell(s)
class Controller: UITableViewController {
func equals(indexA: NSIndexPath, indexB: NSIndexPath) -> Bool? {
guard let
cellA = tableView.cellForRowAtIndexPath(indexA),
cellB = tableView.cellForRowAtIndexPath(indexB) else { return nil }
return cellA.textLabel?.text == cellB.textLabel?.text
}
}
The right way
Think about how you are populating the cells. I imagine you are using some Model Value. So just compare the Model Values you are using to populate the cells.
Probably something like this
class Controller: UITableViewController {
var data: [Int] = [] // ....
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCellWithIdentifier("YourCellID") else { fatalError("No cell found with this id: 'YourCellID'")}
cell.textLabel?.text = String(data[indexPath.row])
return cell
}
func equals(indexA: NSIndexPath, indexB: NSIndexPath) -> Bool? {
return data[indexA.row] == data[indexB.row]
}
}
Compare the values stored within your data array:
if dataArray[0].myIntegerValue > dataArray[1].myIntegerValue {
// Do your stuff
}
Edit: this assumes your data is stored as objects with that Int as an attribute.
As the others have said, don't do that. In the MVC design pattern, labels are views. They are for displaying data, not storing it.
Trying to read values from table view labels is especially bad, because as the table view scrolls, the cells that go off-screen will be recycled and the values in their views will be discarded. You need to save your data to a model object. (An array works just fine to save table view data, or an array of arrays for a sectioned table view.)
Related
I have a table view with custom cells. They are quite tall, so only one cell is completely visible on the screen and maybe, depending on the position of that cell, the top 25% of the second one. These cells represent dummy items, which have names. Inside of each cell there is a button. When tapped for the first time, it shows a small UIView inside the cell and adds the item to an array, and being tapped for the second time, hides it and removes the item. The part of adding and removing items works fine, however, there is a problem related to showing and hiding views because of the fact that cells are reused in a UITableView
When I add the view, for example, on the first cell, on the third or fourth cell (after the cell is reused) I can still see that view.
To prevent this I've tried to loop the array of items and check their names against each cell's name label's text. I know that this method is not very efficient (what if there are thousands of them?), but I've tried it anyway.
Here is the simple code for it (checkedItems is the array of items, for which the view should be visible):
if let cell = cell as? ItemTableViewCell {
if cell.itemNameLabel.text != nil {
for item in checkedItems {
if cell.itemNameLabel.text == item.name {
cell.checkedView.isHidden = false
} else {
cell.checkedView.isHidden = true
}
}
}
This code works fine at a first glance, but after digging a bit deeper some issues show up. When I tap on the first cell to show the view, and then I tap on the second one to show the view on it, too, it works fine. However, when I tap, for example, on the first one and the third one, the view on the first cell disappears, but the item is still in the array. I suspect, that the reason is still the fact of cells being reused because, again, cells are quite big in their height so the first cell is not visible when the third one is. I've tried to use the code above inside tableView(_:,cellForRow:) and tableView(_:,willDisplay:,forRowAt:) methods but the result is the same.
So, here is the problem: I need to find an EFFICIENT way to check cells and show the view ONLY inside of those which items are in the checkedItems array.
EDITED
Here is how the cell looks with and without the view (the purple circle is the button, and the view is the orange one)
And here is the code for the button:
protocol ItemTableViewCellDelegate: class {
func cellCheckButtonDidTapped(cell: ExampleTableViewCell)
}
Inside the cell:
#IBAction func checkButtonTapped(_ sender: UIButton) {
delegate?.cellCheckButtonDidTapped(cell: self)
}
Inside the view controller (NOTE: the code here just shows and hides the view. The purpose of the code is to show how the button interacts with the table view):
extension ItemCellsTableViewController: ItemTableViewCellDelegate {
func cellCheckButtonDidTapped(cell: ItemTableViewCell) {
UIView.transition(with: cell.checkedView, duration: 0.1, options: .transitionCrossDissolve, animations: {
cell.checkedView.isHidden = !cell.checkedView.isHidden
}, completion: nil)
}
EDITED 2
Here is the full code of tableView(_ cellForRowAt:) method (I've deleted the looping part from the question to make it clear what was the method initially doing). The item property on the cell just sets the name of the item (itemNameLabel's text).
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier:
ItemTableViewCell.identifier, for: indexPath) as? ItemTableViewCell{
cell.item = items[indexPath.row]
cell.delegate = self
cell.selectionStyle = .none
return cell
}
return UITableViewCell()
}
I've tried the solution, suggested here, but this doesn't work for me.
If you have faced with such a problem and know how to solve it, I would appreciate your help and suggestions very much.
Try this.
Define Globally : var arrIndexPaths = NSMutableArray()
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 30
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:TableViewCell = self.tblVW.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
cell.textLabel?.text = String.init(format: "Row %d", indexPath.row)
cell.btn.tag = indexPath.row
cell.btn.addTarget(self, action: #selector(btnTapped), for: .touchUpInside)
if arrIndexPaths.contains(indexPath) {
cell.backgroundColor = UIColor.red.withAlphaComponent(0.2)
}
else {
cell.backgroundColor = UIColor.white
}
return cell;
}
#IBAction func btnTapped(_ sender: UIButton) {
let selectedIndexPath = NSIndexPath.init(row: sender.tag, section: 0)
// IF YOU WANT TO SHOW SINGLE SELECTED VIEW AT A TIME THAN TRY THIS
arrIndexPaths.removeAllObjects()
arrIndexPaths.add(selectedIndexPath)
self.tblVW.reloadData()
}
I would keep the state of your individual cells as part of the modeldata that lies behind every cell.
I assume that you have an array of model objects that you use when populating you tableview in tableView(_:,cellForRow:). That model is populated from some backend service that gives you some JSON, which you then map to model objects once the view is loaded the first time.
If you add a property to your model objects indicating whether the cell has been pressed or not, you can use that when you populate your cell.
You should probably create a "wrapper object" containing your original JSON data and then a variable containing the state, lets call it isHidden. You can either use a Bool value or you can use an enum if you're up for it. Here is an example using just a Bool
struct MyWrappedModel {
var yourJSONDataHere: YourModelType
var isHidden = true
init(yourJSONModel: YourModelType) {
self.yourJSONDataHere = yourJSONModel
}
}
In any case, when your cell is tapped (in didSelectRow) you would:
find the right MyWrappedModel object in your array of wrapped modeldata objects based on the indexpath
toggle the isHidden value on that
reload your affected row in the table view with reloadRows(at:with:)
In tableView(_:,cellForRow:) you can now check if isHidden and do some rendering based on that:
...//fetch the modelObject for the current IndexPath
cell.checkedView.isHidden = modelObject.isHidden
Futhermore, know that the method prepareForReuse exists on a UITableViewCell. This method is called when ever a cell is just about to be recycled. That means that you can use that as a last resort to "initialize" your table view cells before they are rendered. So in your case you could hide the checkedView as a default.
If you do this, you no longer have to use an array to keep track of which cells have been tapped. The modeldata it self knows what state it holds and is completely independent of cell positions and recycling.
Hope this helps.
I have table contains custom cells contain uisegment
The problem is when select any item in uisegment and scroll down in the table view it change the selection in uisegment in the cells down on table
almost cell 1 like 11 and 2 like 12
It related to dequeueReusableCellWithIdentifier and my question is what is the best way to solve it ?
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let item = self.cells.items[indexPath.row]
if let cellHedaer: TcPationQuestionHeader = tableView.dequeueReusableCellWithIdentifier("HeaderItem") as? TcPationQuestionHeader {
cellHedaer.setCell(Results.Questions[indexPath.row/2 ])
if item as? SwiftyAccordionCells.HeaderItem != nil {
let cellItem: TcPationQuestionItem = tableView.dequeueReusableCellWithIdentifier("Item") as! TcPationQuestionItem
cellItem.setCell(Results.Questions[indexPath.row/2])
return cellItem
}
cellHedaer.selectionStyle = UITableViewCellSelectionStyle.None
return cellHedaer
}
I see 2 major problems in your code:
You shouldn't keep the state inside UI. Every accessing data from UI
is a big mistake. You should keep selection inside model. The easiest way is to keep array var inside controller.
In some cases you will call dequeueReusableCellWithIdentifier 2 times. That shouldn't happen too.
Don't forget the implementation of cellForRow is connected to numberOfRowsAtIndexPath and numberOfSections. If you want to more detailed help paste here these 2 functions.
I apologize for the poorly worded question but I simply cannot think of why/how to describe this bug in a short manner.
Basically I have a table view controller displaying two sections, each with a respective custom UITableViewCell subclass. They are displayed perfectly, with the correct model data.
The problem arises when I hit the "done" button, my program traverses through all the cells, gathering the data from each cell and updating the model. The first section goes off without a hitch... but the second section is where trouble hides. The first cell of this section can be casted into the proper subclass, yet any other additional cells don't pass this conditional test and their reuse identifier is nil. I tried checking the cells when they're dequeued but they are being created/reused properly. My code is below and any suggestions will aid my sanity.
Here is the part of my helper function that traverses the cells:
for i in 0..<tableView.numberOfSections {
for j in 0...tableView.numberOfRowsInSection(i) {
let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: j, inSection: i))
// This section seems to work fine
if(i == 0){
if let gradeCell: PercentTableViewCell = cell as? PercentTableViewCell {
print("retrieveCellInfo: gradeCell - \(gradeCell.getPercent())")
newPercentages.append(gradeCell.getPercent())
}
} else if (i == 1){
print("Else if i == 1 : j == \(j) : \(cell?.reuseIdentifier!)") // Prints "category" for the first cell and "nil" for any other
// This is the cast conditional that only lets one cell through
if let catCell = cell as? EditTableViewCell{
newCategories.append(Category(name: catCell.category!, weight: catCell.weight!, earned: catCell.earned!, total: catCell.total!))
}
}
}
}
Here is my delegate function which works perfectly as far as I can tell. As I said, the UI is displayed correctly:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if(indexPath.section == 0){
/* Grade Parameter Cells */
let cell: PercentTableViewCell = tableView.dequeueReusableCellWithIdentifier("gradeParam", forIndexPath: indexPath) as! PercentTableViewCell
var gradePercentages = course?.getGradePercentages()
// Make sure percentages array is sorted correctly
gradePercentages?.sortInPlace() {
return $0 > $1
}
// Update cell's member variables
cell.initialize(letters[indexPath.row], percent: gradePercentages![indexPath.row])
return cell
} else {
/* Category Cells */
let catcell: EditTableViewCell = tableView.dequeueReusableCellWithIdentifier("category", forIndexPath: indexPath) as! EditTableViewCell
let category = categories![indexPath.row]
catcell.setLabels(category.name, earned: category.earned, total: category.total, weight: category.weight)
return catcell
}
}
The problem arises when I hit the "done" button, my program traverses
through all the cells, gathering the data from each cell and updating
the model
This is your problem. Your cells should reflect your data model, they cannot be relied upon to hold data as once the cell is offscreen it may be re-used for a row that is onscreen.
If data is updated by the user interacting with the cells then you should update your data model as they do that. You can use a temporary store if you don't want to commit changes immediately.
What I want to do is be able to let the user re-order (not sort) the items in a UITableView. In other words, change the order in what each item was entered, for instance the most common behavior is to show the first item entered into a table at the top but the user may want to see the last item at the top instead to verify what he/she entered.
What is the most common logic to accomplish this behavior?
Here is the code I'm using to enter my data to the table.
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate{
#IBAction func enter(sender: AnyObject?) {
itemList.append(newItem)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reusableCell", forIndexPath: indexPath) as! CustomCell
let data = itemList[indexPath.row]
cell.displayPrice!.text = String(data.basePrice)
return cell
}
}
I know I would probably need to change the way I'm inserting my items if the user re-orders the table, something like...
var insertAtTop = false
#IBAction func enter(sender: AnyObject?) {
if insertAtTop{
itemList.insert(newItem, atIndex: 0)
}else{
itemList.append(newItem)
}
}
It now inserts the items at the top but, of course, this doesn't re-order the items in the array itemList.
Has anyone done something like this?
I guess I could just have an Insert At Top option and clear the table to start new but the user would loose any item already entered.
If you want to allow the user to sort chronologically the data shown in a table, it's probably best to save a timestamp (e.g. NSDate().timeIntervalSince1970) along with each piece of data that's in the table and then filter the data source array by ascending or descending timestamp value.
dataSource.sort() { previous, next in
previous < next // Ascending
}
dataSource.sort() { previous, next in
previous > next // Descending
}
I know how to send the user to a new cell after they select a cell but what if the order of my cells change because I am retrieving data from Parse so for each new cell, the row number changes.
How do I ensure the user is sent to the correct page when they select a certain cell? This is what I'm currently using but I know there's got to be a better solution than hardcoding every possible option..
Any advice?
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.section == 0 && indexPath.row == 1 {
self.performSegueWithIdentifier("toSettingsPage", sender: self)
}
}
For my understanding of your questions, I suggest you use a NSMutableDictionary to store all the user info data, and on the didSelectRowAtIndexPath function, you will use the indexPath to find the correct user info.
Your input:
A table view
An index path (section, row) ordered pair
Your output:
A string that identifies a segue
An ideal data structure for this is a dictionary.
First, notice that the table view input is always the same (you only seem to care about one table view - the protocol for data source is written to handle as many table views as you like, but most people use one for one).
Second, think about your keys and values: your key is the index path. And in fact, the index path breaks down into just an Integer because it is always the same section, which is analogous to the situation with table view described above.
So your dictionary is going to be of type: Dictionary<Integer, String>.
Now, instead of using the dictionary directly, let's make a function to wrap it and call the function segueForIndexPathInDefaultTableView:
private let seguesForIndexPaths:[Integer:String] = [0:"segue0",1:"segue1",2:"segue2"]
private func segueForIndexPathInDefaultTableView(indexPath: NSIndexPath) {
return self.seguesForIndexPaths[indexPath.row]
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier(self.segueForIndexPathInDefaultTableView(indexPath: indexPath), sender:self)
}