How to hide content on specific cells only, despite using dequeueReusableCell? - ios

I would like to hide some elements in a custom cell when we overpass a specific number of row. I added more row than the ones visible, because I needed to scroll until the last row without the bouncing effect. But now I have more cells, and I don't need the cells after row > 13.
I tried to setNeedsDisplay the cell with a if else, but the dequeue... method has a bad effect on the cells, when I scroll up, back to the previous cells, they don't have the texts anymore, like the row > 13. Is there a way to use the dequeue method, and let the content for the rows < 13, and remove the content for the rows > 13 ?
Here is some code :
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var identifier = ""
if tableView == self.tableView{
identifier = "MyCell"
let cell = tableView.dequeueReusableCellWithIdentifier(identifier) as MyCell
if indexPath.row < 14 {
cell.showContent = true
cell.label.text = "test\(indexPath.row)"
}
else {
cell.showContent = false
cell.label.text = ""
cell.addItem.text = ""
}
cell.setNeedsDisplay()
return cell
}
//MyCell
override func drawRect(rect: CGRect) {
if !showContent {
label.text = ""
addItem.text = ""
}
else {
let path = UIBezierPath()//custom separator that should not be drawn for row > 13
Thanks

You shouldn't modify the text this way in drawRect. You already modified the labels in cellForRow. That's all you need.
That said, this isn't how I would do it. I'd probably create a different cell with its own identifier for empty cells. That way they can be really simple and you don't have to do things like cell.setNeedsDisplay() to get rid of the separator line. So in cellForRow, just return one kind of cell for data rows, and a different kind of cell for empty rows. There's no rule that says all the cells have to be the same class.

Related

UISegmentControl with UITableView - UITableViewCell layout remains same on changing the segment

In the scenario mentioned in the question title, on changing the segment, ideally the UITableView should reload and hence the UITableViewCell should also reload. The issue is, all the content gets updated like label texts. But if I have expanded a subview of cell in one segment, it remains still expanded after segment is changed.
Segment index change function :
#IBAction func segmentOnChange(sender: UISegmentControl)
{
// Few lines
// self.tableMenu.reloadData()
}
Screenshot 1 :
Screenshot 2 :
So ideally, in screenshot 2, the cart view should have been collapsed.
Update :
Show/Hide view :
func showHideCartView(sender: UIButton)
{
let cell = self.tableMenu.cellForRow(at: IndexPath(row: 0, section: Int(sender.accessibilityHint!)!)) as! RestaurantMenuItemCell
if sender.tag == 1
{
// Show cart view
cell.buttonArrow.tag = 2
cell.viewAddToCart.isHidden = false
cell.constraint_Height_viewAddToCart.constant = 50
cell.buttonArrow.setImage(UIImage(named: "arrowUp.png"), for: .normal)
}
else
{
// Show cart view
cell.buttonArrow.tag = 1
cell.viewAddToCart.isHidden = true
cell.constraint_Height_viewAddToCart.constant = 0
cell.buttonArrow.setImage(UIImage(named: "arrowDown.png"), for: .normal)
}
self.tableMenu.reloadData()
}
cellForRowAtIndexPath :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RestaurantMenuItemCell", for: indexPath) as! RestaurantMenuItemCell
cell.selectionStyle = .none
let menuItem = self.menuItems[indexPath.section]
cell.imageViewMenuItem.image = UIImage(named: "recommend0#2x.png")
cell.labelName.text = menuItem.name
cell.labelDescription.text = menuItem.description
cell.labelPrice.text = String(format: "$%i", menuItem.price!)
cell.buttonArrow.accessibilityHint = String(format: "%i", indexPath.section)
cell.buttonArrow.addTarget(self, action: #selector(self.showHideCartView(sender:)), for: .touchUpInside)
return cell
}
Since you rely on the sender button's tag to determine whether the cell should be shown in expanded or collapsed state, you need to make sure that when the segment changes, the tags for all the cells' buttonArrow also change to 1.
Unless that happens, the cells will be reused and since the buttonArrow's tag is set to 2, it will be shown as expanded.
You are reusing cells, see first line of your cellForRowAt implementation:
let cell = tableView.dequeueReusableCell(withIdentifier: "RestaurantMenuItemCell", for: indexPath) as! RestaurantMenuItemCell
That means that a tableView does not create a new cell, nor it redraws it unless necessary for the given indexPath. However, in the code you expand a cell by setting some height constraints and isHidden flags on subviews of the cell. Now once you reload table at a new segment, the tableView will use the already rendered cells to make the reload as efficient as possible. However, that means, that although you will change the data in the cell, unless you explicitly collapse it, it will stay expanded (because previously you have set the constraints to expanded height).
You need to reset the expanded state when you change the segment. Now let's elaborate, because I believe your current solution has a hidden bug in it:
First of all, since as I said you are dequeueing the cells, in case there are a lot of items in the table and you are scrolling through them, there is a high chance that the expanded cell will get reused even somewhere later in the table view. So it will seem like there is some random item expanded too.
Therefore I suggest you to provide some model that would remember which cells are supposed to be expanded and then use the information in this model to update state of each cell before you return them in your cellForRowAt. The model can be for example a simple array of integer values that would represent indexes of cells that are expanded - so for example if there is an index 3 in the array, that would mean that cell at row 3 should be expanded. Then when you dequeue a cell in cellForRowAt for indexPath with row = 3 you should set constraints on that cell before returning it. This way you can remove the bug I mentioned. Moreover, then when you change segments, you can just remove all the indexes from the array to signify that no cell should be expanded anymore (do it before calling tableView.reloadData()).
What happened here, When you click any row then It will be expanded based on make true first condition in showHideCartView method and after that when you are changing segment index that keep height of previous row as it is because you are reusing cell So you must set normal hight of each row when you change segment index.
For do that take object of indexPath like
var selectedIndexPath: NSIndexPath?
Then set selectedIndexPath in showHideCartView method
func showHideCartView(sender: UIButton) {
selectedIndexPath = NSIndexPath(row: 0, section: Int(sender.accessibilityHint!)!)) // you can change row and section as per your requirement.
//// Your other code
}
Then apply below logic whenever you are changing segment index.
#IBAction func segmentOnChange(sender: UISegmentControl)
{
if indexPath = selectedIndexPath { // check selectedIndexPath is not nil
let cell = self.tableMenu.cellForRow(at: indexPath) as! RestaurantMenuItemCell
cell.buttonArrow.tag = 1
cell.viewAddToCart.isHidden = true
cell.constraint_Height_viewAddToCart.constant = 0
cell.buttonArrow.setImage(UIImage(named: "arrowDown.png"), for: .normal)
}
selectedIndexPath = nil // assign nil to selectedIndexPath
// Few lines
self.tableMenu.reloadData() // Reload your tableview
}

Some unwanted UIImageViews disappear while trying to set them as borders of UITableView

What I am trying to do is to replace borders(solid lines by default, of course) by small dots. I want the size of the dots to be 6 by 6.
Making borders invisible is a simple thing to do; I just set separators as 'None' in the attribute inspector.
But there was no field to set images or other objects as separators. To solve this problem, I splitted the dot into up and down pieces, assigned them in UIImageViews, and located the upper one on the bottom and the lower one on the top.
Then in the function tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath), I got the lower image in the first row and the upper image in the last row invisible.
What make the problem is that some unwanted dots disappear, while the dots I intended to change are properly gone away. Furthermore they come back or go away when the tableView is scrolled. The codes and the screenshot are below.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "reuseIdentifier"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! MyListTableViewCell
//each element in datas[] has lowercase alphabets, a from q.
let array = datas[indexPath.row]
//data is a UIButton, which is the only component in each row besided those dots
cell.data.setTitle(array, forState: .Normal)
//this is where I typed to handle the unwanted dots on the very top and bottom
if indexPath.row == 0{
cell.lowerHalf.hidden = true
}
else if indexPath.row == datas.count - 1{
cell.upperHalf.hidden = true
}
///////////////////
return cell
}
When I scrolled tableView up and down side, it changed to this:
What makes the dots which shouldn't disappear go away? Or is their a better way to set images as borders?
+In the way iSashok suggested, things didn't change unfortunately. I applied this code right after the declaration; and then I moved to right before the return statement. Both did not work.
cell.clipsToBound = false
Your cell is getting reuse so you need to change your if condition of cellForRowAtIndexPath like this
if indexPath.row == 0 {
cell.lowerHalf.hidden = true
cell.upperHalf.hidden = false
}
else if indexPath.row == datas.count - 1{
cell.lowerHalf.hidden = false
cell.upperHalf.hidden = true
}
else {
cell.lowerHalf.hidden = false
cell.upperHalf.hidden = false
}
Try to modify for your cell clipsToBounds property in
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
like below
cell.clipsToBounds = false;
and for contentView the same
cell.contentView.clipsToBounds = false;
Why don't you use UITableViewHeaderFooterView? Simply create your view with that dot image in centre and return that view from viewForFooterInSection.

Why are some cells being modified even if they don't fulfill a condition?

I have the following code in my table view controller:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("itemCell") as! ItemTableViewCell
cell.itemTitle.text = sortedItems[sortedArray[indexPath.section]]![indexPath.row].itemTitle
cell.itemType.backgroundColor = sortedItems[sortedArray[indexPath.section]]![indexPath.row].itemColor()
// Darkening cells
if /*certain condition is met*/ {
cell.backgroundColor = .redColor() //this colors other cells while scrolling which shouldn't happen
cell.itemTitle.text = "Hello" //this is applied correctly, but why?
}
return cell
}
As you can see in the 'comments', the code which changes the title is applied properly, while coloring the cell doesn't. Why is this? Does it have anything to do with cells being dequeued? How can I avoid this behavior in order to be able to color certain cells?
Table cells are reused when they move off screen. Therefore you must assume they have "left over" data from another cell. Consequently, you need to reset them to a known state. The easiest way to do this in your case, is to handle the else situation.
if /*certain condition is met*/ {
cell.backgroundColor = .redColor() //this colors other cells while scrolling which shouldn't happen
cell.itemTitle.text = "Hello" //this is applied correctly, but why?
} else {
cell.backgroundColor = .whiteColor() // whatever the default color is
cell.itemTitle.text = ""
}

Adjust SubView constraints on a UITableView Cell

I have an UITableView with 2 section. The first section (VoucherCell) will be filled with data from a database. The second section (KodeVoucherCell) will be filled with data inputted by the user using a simple form. I create that simple form as a SubView (see picture below).
Then I set the second section to load the SubView using the code below :
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = UITableViewCell.init()
if(indexPath == NSIndexPath(forRow: 0, inSection: 1)) {
cell = tableView.dequeueReusableCellWithIdentifier("KodeVoucherCell")!
cell.addSubview(inputKodeVoucherSubView)
} else {
cell = tableView.dequeueReusableCellWithIdentifier("VoucherCell")!
cell.indentationLevel = indexPath.length - 1
cell.accessoryType = .DisclosureIndicator
cell.textLabel!.text = "Voucher A"
}
return cell
}
Now the problem is, although I have set the constraints of the SubView, while I run the app, it looks like this :
How to fix this? I tried to modify the constraint but still get the same look. Thanks.
You shouldn't be adding subviews directly to a UITableViewCell. Add them to the cell's contentView. Replace:
cell.addSubview(inputKodeVoucherSubView)
with:
cell.contentView.addSubview(inputKodeVoucherSubView)

Stop the reuse of custom cells Swift

I have an uitableview with a custom cell which gets data from the array.
Custom cell has an uilabel and an uibutton (which is not visible until the uilabel text or the array object which loads for the text - is nil).
On launch everything is fine. When i press the uibutton the array is being appended, the new cells are being inserted below the cell.
But when i scroll - all of a sudden the uibutton appears on other cells where this conditional uilabel text isEmpty is not implied.
Here is how the whole process looks like
Here is my code for cellForRowAtIndexPath
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:TblCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! TblCell
cell.lblCarName.text = someTagsArray[indexPath.row]
if let text = cell.lblCarName.text where text.isEmpty {
cell.act1.hidden = false
} else {
println("Failed")
}
cell.act1.setTitle(answersdict[answersdict.endIndex - 2], forState:UIControlState.Normal)
cell.act2.setTitle(answersdict.last, forState:UIControlState.Normal)
return cell
}
So my general question is how do i stop the reuse of those custom cells?
As far as i'm aware there is no direct way of doing this on reusablecellswithidentifier in swift, but maybe there are some workarounds on that issue?
When a cell is reused, it still has the old values from its previous use.
You have to prepare it for reuse by resetting that flag which showed your hidden control.
You can do this either in tableView:cellForRowAtIndexPath: or the cell's prepareForReuse method.
Update:
Here's an example you can add for TblCell:
override func prepareForReuse()
{
super.prepareForReuse()
// Reset the cell for new row's data
self.act1.hidden = true
}

Resources