I have a UICollectionView with 4 views in it. Each of these views have a UITableView inside it with custom cells. Each cell of the UITableView has a UIButton inside it and I have 2 cells per UITableView.
Something strange is happening. I have an action function for each button so that when a button is clicked, it becomes purple. The strange thing is this: if I scroll to the 4th view of my collection view and click on a button, it becomes purple as expected but then when I scroll to the 1st view of my collection view, the same button that I clicked in the 4th view (either the first one or second one) is also purple as if the 4th view of my collection view was referencing the items of my 1st view of the collection view.
I don't know at which point the 1st view becomes the same as the 4th view but here is a sample of the code:
// this is the cellForItemAt of my UICollectionView, very basic
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cellView = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! PollCellView
return cellView
}
// THIS IS ANOTHER FILE HERE
// this is part of my view that populate the UICollectionViews
class PollCellView: UICollectionViewCell {
// the table view
let questAndAnswersTableView : UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.separatorStyle = .none
tableView.allowsSelection = false
return tableView
}()
// I add the tableview in the view here
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(questAndAnswersTableView)
// a classic cellForRowAt of my UITableView
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "AnswerCell2", for: indexPath) as! AnswerCell2
return cell
}
// THIS IS ANOTHER FILE HERE
// this part is my custom cell of the UITableView
class AnswerCell2: UITableViewCell {
let answerTextButton: UIButton = {
let answerButton = UIButton()
answerButton.setTitle("initial text", for: .normal)
return answerButton
}()
// I add the button to the cell here
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String!) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
addSubview(answerTextButton)
// I define the action function
answerTextButton.addTarget(self, action: #selector(answerClicked), for: .touchUpInside)
}
// the action to make the button purple
#objc func answerClicked(sender: UIButton) {
sender.backgroundColor = UIColor(displayP3Red: 0.6902, green: 0.7176, blue: 0.9922, alpha: 1.0)
}
[EDIT FOLLOWING THE ANSWERS RECEIVED]
Dequeuing is definitely not as simple as it first seems... You can't trust that the tableview dequeued in a given collection view is really the one you expect... You need to keep track of the content (model) yourself. One thing that made it easier to fix is to use closures between the cell of the TableView and the cell of the UICollectionViewCell... You can very easily pass data from one to the other (like what indexPath was clicked, etc.).
when you're managing multiple nested tableviews or collectionviews or even when you're managing this yourself, you have to set up a system of arrays that when the button is clicked, you add those indexpaths to an array called "indexPathsThatArePuple" then when you after each button click, inside the button click function, you pass the indexPath and add that to the array. If the button is already inside the array when the button click function is pressed, then you remove that indexPath. in the button click function, after you add or remove indexPaths, you reload the collection view which will reload the tableviews and then inside "cellForItemAtIndexPath" you check the indexPathsThatArePuple array and then write "if indexpath is contained in indexPathsThatArePuple" then set the cell color to purple.
I understand what you're trying to do, but you don't realize or understand just how complicated the cell reuse system is in iOS. The first thing you need to do is wrap your mind around the idea the that you have to manually manage cell states due to cell resuse. The purple cell showing up in another table is from cell reuse. Apple won't automatically manage this for you, you have to do it yourself. As i described above and even my description above is not that cut and dry since you'll likely struggle with this for weeks until you grasp the concept. Good luck, you'll eventually get it.
It seems to be caused by UICollectionView cell reuse mechanism.
When the cell is created and scroll out of the screen, it will be reused later if a new cell with the same identifier is required. In your case, when you scroll down, the 1st view is reused as the 4th view, And when you scroll to the top, The 4th view is reused as the first view. If the method prepareForReuse happens to be not implemented, the UI status of the cell will not be change, so you will see the button with purple color.
Related
Using Xcode 10+, Swift 4+, iOS 11+
I've created a simple Search View that has a SearchBar at the top,
with a UITableView beneath.
When the view is first displayed, the Table is empty, and as the user enters text into the search bar, I add results to the table. Functionally, everything works ok.
Next, I've made the main View background blue and want the entire TableView to be clear so I can see the background.
I've set the table view background in the "viewDidLoad" :
myTable.backgroundColor = UIColor.clear
In "cellForRowAt" I set the cell background to clear:
var cell:UITableViewCell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: "cell")
cell.backgroundColor = UIColor.clear
cell.contentView.backgroundColor = UIColor.clear
However, when the search returns values and cells are added to the table,
they always have a white background instead of clear.
If I pop and then push the Search View without clearing the table, the existing table cells show up (redraw) the way I want them - clear.
I've also tried adding this to "didFinishLaunchingWithOptions" but it does not help:
UITableViewCell.appearance().backgroundColor = UIColor.clear
How can I make the cells show clear when they are first created?
try to set cell or tableView backgroundColor when cell will dispaly.
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.backgroundColor = UIColor.clear
}
or you can override function setBackgroundView to debug problem.
I need to place a radio button in tableview custom cell. whenever user clicks the tableview cell or button then radio button needs to work. I tried by using below code but didn't execute well.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:TableViewCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! TableViewCell
cell.country.text = self.animals[indexPath.row]
cell.selectionStyle = UITableViewCellSelectionStyle.none;
if selectedRows.contains(indexPath)
{
cell.radioButton.setImage(UIImage(named:"check.png"), for: .normal)
}
else
{
cell.radioButton.setImage(UIImage(named:"uncheck.png"), for: .normal)
}
return cell
}
Here's a great solution for creating radio buttons in a UITableView using a storyboard that requires zero code - and has 2 great Cool Tips!!
Make sure your table view is set to Single Selection, and to use Static cells.
Add a Basic cell, set the image to be your unchecked button image, and make sure the selection style is Default
Cool Tip # 1: Click on and select the cell's image view, and then set it's highlighted image to be your checked state. When the cell is highlighted or selected, the image view within will change to show its highlighted state.
Cool Tip # 2: Next, drag a UIView into the cell's content view, behind the text label. As you're using a basic cell, you won't be able to drop it directly into the cell, you'll need to drag it into onto the Document Outline on the left instead. Then hook this up to the cell's selected background view outlet. When a cell is selected (or highlighted), this view will be displayed in the background. In this case, we're going to use it to prevent the grey background appearing, so set its colour to Clear. Note that it doesn't matter what size the view is, and there's no need to set any constraints - it's automatically sized to match the cell at runtime.
Finally, duplicate this cell and change the text for each of your radio button options. Build and run, and you have code-free radio buttons!
In your TableViewCell class why don't you create a data source element and override the didSet for it. also in your data source for the UITableView I would recommend an array of something more than just a String.
I haven't compiled the below so this is just an idea.
import UIKit
class TableViewCell : UITableViewCell {
var data: Animal? {
didSet {
self.country.text = data.description
if (data.isSelected) {
self.radioButton.setImage(UIImage(named:"check.png"), for: .normal)
} else {
self.radioButton.setImage(UIImage(named:"uncheck.png"), for: .normal)
}
}
}
}
in your view controller you will of course have to set the isSelected property whenever a row is tapped.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
var animal = self.animals[indexPath.row]
animal.isSelected = !animal.isSelected
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:TableViewCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! TableViewCell
cell.data = self.animals[indexPath.row]
}
and for your Animal maybe something like this:
struct Animal {
var description: String
var isSelected: Bool
}
In my tableview, every cell will be different and determined by a JSON response from server. And there will be infinite possibilities. So defining a prototype for each type of cell is not possible.
For example, one cell will have labels and buttons, another cell have images and buttons in different orders.
How to achieve this dynamic structure in tableview cells?
Currently what I am doing is: adding views as subview in cellForRowAtIndexPath but scrolling is very laggy this way.
How to achieve this without affecting performance this much
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! MyCell
for myview in data[indexPath.row].myviews{
cell.addSubview(myview)
}
return cell
}
If you're using a table view then your content is going to scroll vertically, right?
There is a physical limit to the amount of UI that you can put horizontally. Limited by the screen size.
So I'm guessing your UI parts are being laid out vertically in the cell?
So instead of laying out a button, label, image, another button, and a text field vertically in a cell...
Create a cell type called ButtonCell, LabelCell, ImageCell, MultiLineLabelCell, TextFieldCell, etc...
So now, instead of creating one cell with all these elements added. You instead create multiple cells each containing one type of UI. Now you can dequeue your cells in any particular order (driven by your JSON) and won't lose the performance.
The only solution I see is to have empty cell and add/remove subviews as needed. But you should add new subviews to a cell only if you did not add them before.
For example:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! MyCell
if cell.contentView.viewWithTag(1) == nil {
let label = UILabel()
label.tag = 1
cell.contentView.addSubview(label)
}
let label = cell.contentView.viewWithTag(1)
// label config there
return cell
}
Also don't forget to add subviews to cell's contentView not to cell itself.
I've created a custom UITableView Cell and everything seems to be working with one exception. I have a UISwitch that is inside the cell (hooked up to it's own UITableViewCell class that the tableView loads) but it only appears when you click on the cell or the cells background is clear/transparent. Ideally I have a white background for the cell and the switch on top of the background.
I've tried some hacky stuff like:
cell.bringSubview(toFront: cell.switch)
and
cell.switch.isHidden = false
But that obviously didn't work.
The switch is enabled and ON by default.
The tableview and switch is created from storyboards.
The hierarchy looks like this - TableView > Cell > Content View > Switch
Here's a video to see in detail - http://quick.as/rpyub8mv
Xcode Storyboard Screenshot
Custom TableViewCell Class
class SettingsBoolCell: UITableViewCell {
#IBAction func switchAction(_ sender: UISwitch) {
}
#IBOutlet weak var switchOutlet: UISwitch!
}
ViewController Implementation
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "settingsSectionOne", for: indexPath) as! SettingsBoolCell
switch indexPath.section {
case 0: cell.textLabel?.text = titles[0]
case 1: cell.textLabel?.text = titles[1]
case 2: cell.textLabel?.text = titles[2]
default: ()
}
return cell
}
If you're using storyboard, make sure that your UISwitch is inside the right view, and that it is under the other components in your document outline.
If you're generating the view inside the cell programmatically, make sure that you add the UISwitch to the right view with addSubView last. You can also set zPositions with view.layer.zPosition attribute.
So I was setting -
cell.textLabel.text?
to change the text of the cells inside the tableView. The problem is, apparently when you are using a custom cell, you can't access the default cell properties without some funky behavior.
Thanks everyone for your help!
I am trying to get a custom tableView cell with a textView inside working in my tableView. I have made a custom UITableViewCell with a textView inside it.
I can get the custom made UITableViewCell with the textView inside to appear in the UITableView.
I can click inside the textView to type something, but when I finish typing and click on another tableViewCell, the first tableViewCell with the textView inside disappears. After disappearing, it becomes an empty tableViewCell. XCode gives this message:
"no index path for table cell being reused"
However, when I scroll away in the tableView and scroll back to the empty tableViewCell, it reappears.
I don't know how to keep the tableViewCell from disappearing. It seems like the answer has something to do with using the restorationIdentifier inside of UITableView, but I'm not sure how to use it. In the docs, it says to use restorationIdentifier for state preservation.
Here is the relevant code I have:
inside ViewDidLoad():
tableView.registerClass(PhotoAndRateTableViewCell.classForCoder(), forCellReuseIdentifier: ReuseIds.reviewCell)
tableView.registerNib(UINib(nibName: "PhotoAndRateTableViewCell", bundle: NSBundle.mainBundle()), forCellReuseIdentifier: ReuseIds.reviewCell)
inside cellForRowAtIndexPath:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var section = indexPath.section
let cell = UITableViewCell()
var cell = tableView.dequeueReusableCellWithIdentifier(ReuseIds.reviewCell, forIndexPath: indexPath) as PhotoAndRateTableViewCell
cell.selectionStyle = UITableViewCellSelectionStyle.None
return cell
}
You're misusing the dequeue procedure:
var cell = tableView.dequeueReusableCellWithIdentifier(ReuseIds.reviewCell, forIndexPath: indexPath) as? PhotoAndRateTableViewCell
if (cell == nil) {
cell = PhotoAndRateTableviewCell();
}
You want to reuse a cell if available, or create a new one if not. In your case, you're creating a cell every time (of the generic class) and then attempting to dequeue a cell from your custom class (which has never been created)
As far as preserving the data, you need to implement the prepareForReuse method in the table cell which should clear whatever index specific data was contained in the cell. Then in cellForRow you can re-set the data for the cell for re-appearance