UIStepper inside UITableViewCell Goes Problematic When Tapped - ios

I need to present a UIStepper in a row of a UITableView(only the second row - see the image below).
Therefore I implemented func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell like below:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("OptionCell")!
let debugModeOptionType = DebugModeOptionsType(rawValue: indexPath.row)
switch(debugModeOptionType!) {
case .DummyCurrentLocation:
cell.textLabel!.text = "Dummy current location"
case .StepLength:
cell.textLabel!.text = "Step Length: \(stepLength)"
// create a UIStepper
let stepper = UIStepper(frame: CGRectMake(220, 10, 100, 10))
// customize UIStepper
stepper.autorepeat = true
stepper.value = stepLength
stepper.minimumValue = 0.1
stepper.stepValue = 0.02
stepper.maximumValue = 1.5
stepper.addTarget(self, action: #selector(adjustStepLength(_:)), forControlEvents: UIControlEvents.AllEvents)
// add UIStepper into the cell
stepper.translatesAutoresizingMaskIntoConstraints = false
cell.contentView.addSubview(stepper)
case .TrueHeading:
cell.textLabel?.text = "True Heading: \(trueHeading)"
case .MagneticHeading:
cell.textLabel?.text = "Magnetic Heading: \(magneticHeading)"
case .HeadingAccuracy:
cell.textLabel?.text = "Heading Accuracy: \(headingAccuracy)"
case .CurrentDirection:
cell.textLabel?.text = "Current Direction: \(currentDirection)"
case .DrawWalking:
cell.textLabel?.text = "Draw walking while navigating"
}
if selectedDebugModeOptions.contains(debugModeOptionType!) {
cell.accessoryType = UITableViewCellAccessoryType.Checkmark
} else {
cell.accessoryType = UITableViewCellAccessoryType.None
}
return cell
}
However when I touch the UIStepper on a real device(this does not happen inside the simulator) the following happens:
When this happens, the other cells' UISteppers start flashing as well. Why does such a problem occurs?

I can't say why this happening only on a real device, but because table view cells are reused, you have to be careful when adding elements to the cells programmatically, because those elements (such as your stepper) will be spread to other cells as the cells are reused.
There are (at least) two ways you can deal with this:
Check for the presence of a stepper after you dequeue a reusable cell and remove it if it is on a row that doesn't need a stepper. You could do this by giving the stepper a unique tag number (such as 123) and then search for subviews with that tag and remove them.
let stepperTagNumber = 123
let cell = tableView.dequeueReusableCellWithIdentifier("OptionCell")!
let debugModeOptionType = DebugModeOptionsType(rawValue: indexPath.row)
if let stepper = cell.contentView.viewWithTag(stepperTagNumber) {
// We have a stepper but don't need it, so remove it.
if debugModeOptionType != .StepLength {
stepper.removeFromSuperview()
}
} else {
// We don't have a stepper, but need one.
if debugModeOptionType == .StepLength {
// create a UIStepper
let stepper = UIStepper(frame: CGRectMake(220, 10, 100, 10))
stepper.tag = stepperTagNumber // This is key, don't forget to set the tag
// customize UIStepper
stepper.autorepeat = true
stepper.value = stepLength
stepper.minimumValue = 0.1
stepper.stepValue = 0.02
stepper.maximumValue = 1.5
stepper.addTarget(self, action: #selector(adjustStepLength(_:)), forControlEvents: UIControlEvents.AllEvents)
// add UIStepper into the cell
stepper.translatesAutoresizingMaskIntoConstraints = false
cell.contentView.addSubview(stepper)
}
}
OR:
Create a second prototype cell for your tableview (called "OptionCellWithStepper"). Add the stepper to that cell in your storyboard. Then, when you call dequeueReusableCellWithIdentifier, use "OptionCellWithStepper" for case .StepLength and use identifier "OptionCell" for all the other cases. Doing it this way, you don't have to programmatically add the stepper, and you don't have to remember to remove it for the other cells.
let debugModeOptionType = DebugModeOptionsType(rawValue: indexPath.row)
let cellID = (debugModeOptionType == .StepLength) ? "OptionCellWithStepper" : "OptionCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellID)!

Related

How does one prevent labels of different Swift cells displaying on top of each other?

Screenshot of the double text label
Current constraints of the label
Complete Hirearchy
What is my relevant code currently
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? ViewControllerTableViewCell {
let immy = cell.viewWithTag(1) as? UIImageView
let person: Userx = people[indexPath.row]
let text = person.Education
cell.lblName.text = person.Education. ///key line
cell.lblName.text = text?.uppercased()
cell.lblName?.layer.masksToBounds = true
let person5 = colorArray[indexPath.row]
let person6 = colorArray1[indexPath.row]
let person7 = colorArray2[indexPath.row]
let like = cell.viewWithTag(3) as? UIButton
cell.backgroundColor = person5
like?.backgroundColor = person6
immy?.backgroundColor = person7
cell.lblName.baselineAdjustment = .alignCenters
cell.postID = self.people[indexPath.row].postID
cell.row = indexPath.row
cell.delegate = self
cell.delegate2 = self
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
like?.isUserInteractionEnabled = true
}
return cell
}
return UITableViewCell()
}
What have I tried:
In prepare for reuse, I tried lblName.text = "" and lblName.text = nil Didn't work.
I also tried in cellForRowAt:
cell.lblName.text = nil
if cell.lblName.text == nil{
cell.lblName.text = person.Education
}
I also tried to get it done by constraints, no luck.
What I think might be cause
This didn't happen before I changed from TableViewController scene to ViewController(with an output table) scene.
It also didn't happen before I set the cells to have different colors.
I got it to work by checking clear graphics context on label2.
i think your problem would solve if you check cell heights ,
check heightForCellAtRow Func
I havent seen at your cell class. That issue happens when you have constraints issues. Since the autolayout system is unable to calculate the height of the row, the height becomes 0, any change from that point forward might be wrong.
I would suggest you check the cell code, add the labels to the contentView (if they are not) and add constraints between them:
eg:
bottomLabel.topAnchor.constraint(equalTo: topLabel.bottomAnchor, constant: 8).isActive = true
also set the compression resistance for both labels to be .defaultHigh and if you have the height of the row hardcoded remove that.

TableView didSelect row Issue

So the issue is when a cell is tapped, desired data is shown and when again tapped on same cell ( again desired data is shown.)
But when one cell is selected and we again select other cell (then the data is been shown of second tapped cell but the first one is not deselected).
How can I take care of this issue?
var selectedIndex = -1
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
UIView.animate(withDuration: 1) {
self.labelViewHeightConstraint.constant = 60
self.labelLeadingConstraint.constant = 136
self.view.layoutIfNeeded()
}
let cell = tableView.cellForRow(at: indexPath) as! CustomCell
if(selectedIndex == indexPath.row) {
selectedIndex = -1
print("deselect")
UIView.animate(withDuration: 0.4) {
cell.secondView.isHidden = true
cell.firstView.backgroundColor = UIColor(red: 0.8588, green: 0.84705, blue: 0.8745, alpha: 1.0)
}
} else {
cell.secondView.isHidden = false
}
self.expandTableView.beginUpdates()
//self.expandTableView.reloadRows(at: [indexPath], with: UITableViewRowAnimation.automatic )
self.expandTableView.endUpdates()
}
You can archive single selection by setting tableView property like belwo
tableView.allowsMultipleSelection = false
This can also be done from Attributes Inspector
Hope this helps
you must disable multiple selection by,
self.tbl.allowsMultipleSelection = false
and enable single selection by,
self.tbl.allowsSelection = true
EDIT:-
if you want to access your old (selected cells), you should make a call like this,
//first assign tag or indexPath in Cell,
cell.tag = indexPath.row
// or
cell.indexPath = indexPath
//then fetch like bellow,
let visibleCell = tableView.visibleCells.filter({$0.tag == self.selectedIndex})
//or
let visibleCell = tableView.visibleCells.filter({$0.indexPath.row == self.selectedIndex})
//if you use ,
let cell = tableView.cellForRow(at: indexPath) as! CustomCell
//then it will get you new cell object.

self.tableView.indexPathForCell(cell) returning wrong cell?

I have a UITableView (multiple sections) with custom dynamic cells. Each cell has a UIStepper representing selected quantity.
In order to send the selected quantity (UIStepper.value) from the cell back to my UITableViewController, I have implemented the following protocol:
protocol UITableViewCellUpdateDelegate {
func cellDidChangeValue(cell: MenuItemTableViewCell)
}
And this is the IBAction in my custom cell where the UIStepper is hooked:
#IBAction func PressStepper(sender: UIStepper) {
quantity = Int(cellQuantityStepper.value)
cellQuantity.text = "\(quantity)"
self.delegate?.cellDidChangeValue(self)
}
And from within my UITableViewController I capture it via:
func cellDidChangeValue(cell: MenuItemTableViewCell) {
guard let indexPath = self.tableView.indexPathForCell(cell) else {
return
}
// Update data source - we have cell and its indexPath
let itemsPerSection = items.filter({ $0.category == self.categories[indexPath.section] })
let item = itemsPerSection[indexPath.row]
// get quantity from cell
item.quantity = cell.quantity
}
The above setup works well, for most cases. I can't figure out how to solve the following problem. Here's an example to illustrate:
I set a UIStepper.value of 3 for cell 1 - section 1.
I scroll down to the bottom of the table view so that cell 1 - section 1 is well out of view.
I set a UIStepper.value of 5 for cell 1 - section 4.
I scroll back up to the top of so that cell 1 - section 1 is back into view.
I increase UIStepper by 1. So quantity should have been 4. Instead, it's 6.
Debugging the whole thing shows that this line (in the delegate implementation of UITableViewController) gets the wrong quantity. It seems indexPathForCell is getting a wrong cell thus returning a wrong quantity?
// cell.quantity is wrong
item.quantity = cell.quantity
For completeness sake, here's cellForRowAtIndexPath implementation where the cells are being dequeued as they come into view:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "MenuItemTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! MenuItemTableViewCell
cell.delegate = self
// Match category (section) with items from data source
let itemsPerSection = items.filter({ $0.category == self.categories[indexPath.section] })
let item = itemsPerSection[indexPath.row]
// cell data
cell.cellTitle.text = item.name + " " + formatter.stringFromNumber(item.price)!
cell.cellDescription.text = item.description
cell.cellPrice.text = String(item.price)
if item.setZeroQuantity == true {
item.quantity = 0
cell.cellQuantityStepper.value = 0
cell.cellQuantity.text = String(item.quantity)
// reset for next time
item.setZeroQuantity = false
}
else {
cell.cellQuantity.text = String(item.quantity)
}
.....
....
..
.
}
You need to update the stepper value in the else clause in cellForRowAtIndexPath:
else {
cell.cellQuantity.text = String(item.quantity)
cell.cellQuantityStepper.value = Double(item.quantity)
}
Otherwise the stepper retains the value from the previous time the cell was used.

PrepareForReuse in CollectionViewCell

I want to hide the label in a cell that was tapped and instead show an image. But I want to do this only if a cell with a certain index has already been set to the imageView.
What is the best way to address the cells and store if they are set to imageView or not? How do I use the prepareForReuse method?
This is the way I do it until now, but as the cells are reused. The image is shown in other cells at scrolling.
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
println("user tapped on door number \(indexPath.row)")
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! MyCollectionViewCell
if (cell.myLabel.text == "1") {
one = true
if(seven = true) {
if (cell.myLabel.hidden) {
cell.myLabel.hidden = false
cell.MyImageView.image = nil
}
else {
cell.myLabel.hidden = true
cell.MyImageView.image = UIImage(named:"1")!
}
}
}
You didn't say if your collection view has exactly 7 cells or if it can have "N" (e.g. 100) cells in the collection, so if this were my problem and I had to solve it, I would make the state of your "seven" cell a property of the class (e.g. "var sevenState : Bool") and then I could display the button or image of other cells depending on what sevenState is.
In my app I have to configure a UICollectionReusableView based on the index path, if the indexPath has a particular value then I send an array which is used to set labels and images.
I use a function in the custom UICollectionReusableView, if I call it with an array it populates the labels and images and if I call it with nil it resets these.
func collectionView(collectionView: UICollectionView!, viewForSupplementaryElementOfKind kind: String!, atIndexPath indexPath: NSIndexPath!) -> UICollectionReusableView! {
.... [logic around selecting index path based on data returned]
....
if filteredEvents != nil{
reusableView.populateCalendarDayDates(sortedEvents)
}else{
reusableView.populateCalendarDayDates(nil)
}
In the function in the custom UICollectionReusableView I reset labels back to default values before possibly updating them :
func populateCalendarDayDates(arrayEvents: NSArray?){
let firstDayTag = tagStartDay()
var dayDate = 1
for var y = 1; y < 43; y++ {
let label = self.viewWithTag(y) as! BGSCalendarMonthLabel
label.delegate = callingCVC
label.backgroundColor = UIColor.clearColor()
label.textColor = UIColor.whiteColor()
label.text = ""
You can get the same effect, and it is probably a bit more readable, by moving this code to prepareForReuse in the custom UICollectionReusableView :
override func prepareForReuse() {
super.prepareForReuse()
for var y = 1; y < 43; y++ {
let label = self.viewWithTag(y) as! BGSCalendarMonthLabel
label.backgroundColor = UIColor.clearColor()
label.textColor = UIColor.whiteColor()
label.text = ""
}
}
Hope that helps.

Button in cell is being added when it is not supposed to be

So I have a settings button and it is only supposed to be added to the cell with the current users name. However, it seems to being randomly added to a cell. In the statement in which I create the button it is only being created once however it is being added to multiple cells. I have attached images of the problem and ps the current username is "test", so the settings button should not be in the same cell as the matt short user. Thanks and below is the attached code of the function in which i am creating and adding the button subview.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell : UITableViewCell? = tableView.dequeueReusableCellWithIdentifier("chatCell") as? UITableViewCell
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "chatCell")
}
cell!.selectionStyle = UITableViewCellSelectionStyle.None
let screenSize: CGRect = UIScreen.mainScreen().bounds
let screenWidth = screenSize.width
let screenHeight = screenSize.height
var sectionTitle = self.friendSectionTitles[indexPath.section]
var friendArray: [String]! = friendDict[sectionTitle]
var friend = friendArray[indexPath.row]
if sectionTitle == "me" && friend == PFUser.currentUser().username {
var settingsButton: UIButton = UIButton.buttonWithType(UIButtonType.System) as UIButton
settingsButton.frame = CGRectMake(screenWidth - 100 , 5, 50, 30)
settingsButton.addTarget(self, action: "settingsButtonTapped:", forControlEvents: UIControlEvents.TouchUpInside)
settingsButton.setTitle("Settings", forState: UIControlState.Normal)
cell!.addSubview(settingsButton)
}
cell!.textLabel!.text = friend
return cell!
}
In the statement in which I create the button it is only being created once however it is being added to multiple cells
Because cells, once created, are reused for other rows of the table. If you don't want the button to appear in a reused cell, you will need (in your implementation of cellForRowAtIndexPath:) to detect its presence and remove it (or at least hide it) for every row that is not supposed to have it.

Resources