I have a UITableView and I'm displaying UITableViewCells.
When I click on a cell, I want to add a label to that cell, on a new row within the cell.
I'm updating my data structure correctly, I just don't know how to add the label to the subview.
This is where I add the row to my data structure:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
itemdata[indexPath.section].items[indexPath.row - 1].rows.append(row(size: 0))
// need to call below method somehow to update views
}
This is where I think I need to add the label to the cell:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "itemcell") as! itemcell
cell.lblItemName?.text = itemdata[indexPath.section].items[indexPath.row - 1].name
if(itemdata[indexPath.section].items[indexPath.row - 1].rows.count > 0){
// dynamically add new rows to the cell in a loop
// each row is a label on a new line
}
return cell
}
Here is what the cell looks like before click:
After touching, it should look like this:
After touching again, it should look something like this:
Dynamically adding new rows is what I don't know how to do.
If possible, I'd like to be able to visually design a row (with a label, buttons, etc) in a similar way that cells can be prototyped, and then add such a row to the cell. Perhaps by having an empty array of such rows within the cell which can then by increased on touch.
I don't want to do it by putting another UITableView inside the cell with more UITableViewCells inside that because I don't need the view to be scrollable and I don't want the lines between cells.
Related
I have a UITableView that uses a cell that has 3 expandable and collapsable subviews in them. I would prefer to maintain the state of these views in my UITableViewCell class itself (states as in collapsed or expanded)
Since they are reusable cells, currently, if I expand view 1 in cell A, and then scroll down to cell B, it's view 1 will be expanded. I don't want this. I want it collapsed. But, if I scroll back up to cell A, I want it to still be expanded.
Other than storing all of these states in an array or dictionary
var expandedViewOneCells: [Int] = []
var expandedViewTwoCells: [Int] = []
etc.
I would prefer to have the cells essentially of act individually and maintain their own state... But how would I do this when cells are reused? Keep in mind, I will always only have at most 3 of these kinds of cells, so can I set something like only reuse after 3 cells.
Would it be wise to keep an array of the cells I load, and then on cellForRowAt load the cell from that array based on the index and return it?
In your func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell function try not to deque a cell but create a new instance of your cell
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = MyCustomCell()
return cell
}
If you are loading your cell from a xib file you need a way to create your custom cell from that nib. Add the following method to your CustomCell class
static func loadFromNib() -> RequestTableViewCell {
let nib = UINib(nibName: "\(MyCustomCell.self)", bundle: Bundle.main)
let cell = nib.instantiate(withOwner: self, options: nil)[0] as! MyCustomCell
return cell
}
Then in your func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell this will create a new cell for every row and not reuse a cell when scrolling
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = MyCustomCell.loadFromNib()
return cell
}
A solution like this may not be optimal if your table view has a lot of rows but for a SMALL amount of rows this should be okay
I see 2 solutions to your problem:
Use 3 View Controllers. They should never get destroyed, and add / remove the corresponding VC's view on top of the .contentView of the cell as it appears or goes off-screen. See the solution here http://khanlou.com/2015/04/view-controllers-in-cells/ The Custom Cell itself is just a view, shouldn't really be concerned with the state, but if we move that logic to a View Controller - we should be fine, an we are not violating MVC. Plus, the View Controller can keep track of the height of the view, based on the state, and heightForRow(at:) can ask it for that
I'd use a Stack View as this is a perfect scenario for it. I'd probably represent the Cell itself as another stack view. Not sure exactly what the views look like and how they change, but it may end up as simple as hiding / unhiding the second view from the Stack View that represent a "cell".
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 currently have a TableView in my project, which is set up to turn a cell green when it is pressed, and back to clear if it is pressed a second time. However, if I scroll down to the bottom of the table view, and scroll back up, all my cells have been reset to their default clear colour.
I'm not sure how to go about fixing this issue, as anything I can find referring to it is in Objective-C rather than Swift. Any help and advice as to how to go about this would be great, thanks.
Everytime a UITableViewCell goes out of the screen, any function that you've written in the tableViewController/ViewController runs again.
for example in cellForRowAtIndexPath if you have a cell.setUpCell() or something similar, it will rerun and reset your values to the original values.
if you have a
var name = testName in your MainVC
and you update something in your cell, you should change the name in your mainVc too.
Every time you scroll or call tableView.reloadData() UITableView cells will reload. So, every time you select UITableViewCell, add selected index (indexPath.row) to an array(ex: selectedIndexArray) in your didSelectRowAt indexPath: delegate. If the cell you selected is already selected one, then remove the cell from selectedIndexArray.
And in your cellForRowAt indexPath: manage the cells using selectedIndexArray.
var selectedIndexArray:[Int] = [] //to save selected tableViewCells
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let isSelected = false
for each in selectedIndexArray
{
if each == indexPath.row
{
isSelected = true
}
}
if isSelected == true
{
//set selected cell color
}
else
{
//set default cell color
}
}
You need to write the logic of adding and removing cell indexes in your didSelectRowAt indexPath:.
I have a tableview which is inside the a tableview cell.
After click the "JAVA" inside "test1", I click the "About" Row which don't have the tableview inside:
But you can see that, the "About" and "JAVA" have the background colour at the same time. This is the automatic feature of tableview in iOS, I don't know how to code to prevent this happen. What I want is that when I click the "Java" inside "test1", the background color of "About" would disappear.
You are going to need to keep track of which cell was selected if you only want one cell selected at a time from both table views.
The best way to do that is to create a delegate for the nested tableview. So when that cell is selected it can call back to the parent tableview and let it know to unselect anything that was selected. The main class will need to keep track of the index that was selected as if another one is chosen then it can reach into the cell with selected cell and deselect that.
A little tricky, are you sure you can't get the same results from just using one tableview and multiple sections?
Use this code it helps you
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var selectedCell:UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
selectedCell.contentView.backgroundColor = UIColor.greyColor()
}
To deselect the selected row use this code
override func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
var cellToDeSelect:UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
cellToDeSelect.contentView.backgroundColor = colorForCellUnselected
}
So I have a UITableView which I plan to use 2 prototype cells inside of it. Let's call them Cell A and Cell B. Cell A has it's own layout and Cell B has it's own layout.
Here's the thing, in a typical UITableView implementation with only 1 prototype cell, after setting up all the cell and it's properties, cellForRowAtIndexPath takes care of populating all the rows based on the (x) number of items from numberOfRowsInSection.
Here is where I am having a problem. I've implemented both my prototype cells in my UITableView and when I run it, I notice cellForRowAtIndexPath is only being called twice, even though I have a value in (x) number of items which is greater than 2. Doesn't matter what I set it to, it only gets called twice. I already have the necessary if statements to pick a cell prototype based on the cell index etc...so that's not the issue. The issue is cellForRowAtIndexPath just gets called twice instead of looping thru all the items.
Why is this and how can I fix it?
This is my code for the DataSource methods:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 8
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
Scripts.log("Data Count = \(indexPath.row)")
if indexPath.row == 0{
var cell: ContactDetailImageCell = tableView.dequeueReusableCellWithIdentifier(NAME_OF_CUSTOM_IMAGE_CELL) as ContactDetailImageCell
cell.cardPhoto.image = aContact.profilePicture
cell.fullName.text = aContact.getDisplayName()
cell.workplace.text = aContact.workplace
return cell
}
else{
var cell: ContactDetailPhoneNumCell = tableView.dequeueReusableCellWithIdentifier(NAME_OF_CUSTOM_PHONE_CELL) as ContactDetailPhoneNumCell
return cell
}
return UITableViewCell()
}
What height are your cells? cellForRowAtIndexPath will only get called if the table thinks it needs to display the cell. Hence the whole reuse mechanism. So if you have 8 cells and it thinks 2 fill the screen it will not ask for any more until you scroll up/down.
What are you returning for heightForRowAtIndexPath.