I had used NHRangeSlider for setting price range in my application placed in table view in which after setting the min and max ranges when i move to another view controller i was getting error and the last set values and normal values has been set in the slider how to clear this error ?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0{
let cell = tableView.dequeueReusableCell(withIdentifier: "sliderCell", for: indexPath) as! SliderCell
tableView.isHidden = false
activityIndicator.stopAnimating()
activityIndicator.hidesWhenStopped = true
let sliderView = NHRangeSliderView(frame: CGRect(x: 16, y: 28, width: self.view.bounds.width - 32, height: 80))
sliderView.sizeToFit()
cell.contentView.addSubview(sliderView)
return cell
}
else{
let cell = tableView.dequeueReusableCell(withIdentifier: "brandCell", for: indexPath) as! BrandCell
if selected == true{
let value = values.count
if (value == 1) {
cell.brandsLabel.text = values[indexPath.row]
}
else if (value == 0) {
let string = "no brand selected"
cell.brandsLabel.text = string
}
else {
let total = " &"+" \(value-1) more"
print(total)
cell.brandsLabel.text = "\(values[0])" + total
}
}
return cell
}
}
image is as shown here
It looks like you are always adding a new instance of NHRangeSliderView to your cell:
let cell = tableView.dequeueReusableCell(withIdentifier: "sliderCell", for: indexPath) as! SliderCell
tableView.isHidden = false
activityIndicator.stopAnimating()
activityIndicator.hidesWhenStopped = true
let sliderView = NHRangeSliderView(frame: CGRect(x: 16, y: 28, width: self.view.bounds.width - 32, height: 80))
sliderView.sizeToFit()
cell.contentView.addSubview(sliderView)
return cell
Assuming that you are reloading the cell/tableview somewhere else you will find that this piece of code is always adding a brand new slider view to the content view of your cell, since it is reusing the cell that already has an instance of the NHRangeSliderView in it.
In this situation I would recommend creating a custom table view cell that already has the slider view attached this way you will have a slightly simpler cellForRow(atIndexPath:) method, but also you won't need to worry about continually adding the view.
Related
What I want to ask you is "Can one UITableviewcell be used for multiple tableview like viewholder that can use anytime for recyclerview in android?" what I used to do is in one viewcontroller I have a tableview with a custom Cell and gave its identifier as normal but if I trying to use another uitableview in another Viewcontroller with that cell that inside the previous tableview, it always gives me a blank cell with white background. is there a way to use it like that?
EDIT: Here is what my tableview look like when i've already set cellforrow for it already.
Click to view and here what my cell look like Click to view cell and here are my code for different cell in a tableview, It'll work if i use use those 2 cell in current tableview
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0{
let cell = self.mytable.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! HistoryItemTableCell
let view = UIView(frame: CGRect(x: 0, y: cell.frame.maxY, width: cell.frame.size.width, height: cell.frame.size.height))
view.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.3)
cell.selectedBackgroundView = view
let order = OrderItemObj
cell.num_of_day.text = "\(order.ticket_type.name)"
cell.ref_num.text = order.order_tx_number
cell.quantity.text = order.number_tickets
cell.price.text = "$\(order.ticket_type.price).00 USD"
if order.status == "unpaid"{
cell.ic_status.image = UIImage(named: "ic_status_unpaid")
}else{
cell.ic_status.image = UIImage(named: "ic_status_paid")
}
cell.start_date.text = "\(order.start_date)"
cell.end_date.text = "\(order.expired_date)"
return cell
}else{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! OrderDetailTicketCell
let t = listTicket[indexPath.row]
cell.dob.text = t.dob
cell.gender.text = t.sex
cell.nation.text = t.nationality
let url = URL(string: t.photo)
cell.imageN.kf.setImage(with: url)
return cell
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 3
}else{
return self.listTicket.count
}
}
override func viewDidLoad(){
super.viewDidLoad()
mytable.register(HistoryItemTableCell.self, forCellReuseIdentifier: "cell")
ViewHistoryItem()
mytable.dataSource = self
mytable.delegate = self
}
Yes you can. You have to register it again for the new tableView. It is just like how you create variables using the same type. This is also a class which can be used to create objects. Doesn't matter where you want to use it.
On the other hand if you are asking if instances of the same cell which are present in a tableView can be reused in another tableView, then the answer is no, because they have only been registered for that particular tableView.
I have a collection view with multiple sections. Collection view data source is
dataSource = [Section]
Each section is having data members as below
Section = {id, [arr1], [arr2]}
Here, id is assigned as section header label. Currently, I am able to generate and insert cells for all the items in arr1 and arr2. But they are continuously inserted under the section. I want to be able to insert cells in arr2 starting on a new line even if there is space left after inserting the last item in arr1. My view controller subclasses UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout. What would be the most elegant and flexible way to achieve this? Am I missing anything?
Below is what I have in collectionview(cellForItemAt: ). Resources is arr1 and ResourceDesktops is arr2.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.item < sections[indexPath.section].resources.count {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: resourceReuseIdentifier, for: indexPath) as! FeedsViewCell
if #available(iOS 11.0, *) {
cell.imageView.accessibilityIgnoresInvertColors = true
}
cell.image = UIImage(named: sections[indexPath.section].resources[indexPath.item].imageName)
cell.resourceLabel.text = sections[indexPath.section].resources[indexPath.item].resourceName
cell.resourceLabel.sizeToFit()
var labelFrame = cell.resourceLabel.frame
labelFrame = CGRect(x: labelFrame.origin.x, y: labelFrame.origin.y, width: labelWidth, height: labelFrame.height)
cell.resourceLabel.frame = labelFrame
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: resourceDesktopReuseIdentifier, for: indexPath) as! ResourceDesktopViewCell
if #available(iOS 11.0, *) {
cell.imageView.accessibilityIgnoresInvertColors = true
}
cell.resourceLabel.text = sections[indexPath.section].resourceDesktops[indexPath.item - sections[indexPath.section].resources.count].resourceName
cell.resourceLabel.sizeToFit()
var labelFrame = cell.resourceLabel.frame
labelFrame = CGRect(x: labelFrame.origin.x, y: labelFrame.origin.y, width: labelWidth, height: labelFrame.height)
cell.resourceLabel.frame = labelFrame
return cell
}
}
You can use UITableView instead of UICollectionView for whole data.
In details you can use 2 UICollectionViews and one label in each cell of UITableView
I've made an UITableView and filled it with JSON data I get inside my API. I get and place all correctly but when I scroll or delete a row everything gets messed up!
Labels and images interfere; this is my code:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
var dict = productsArrayResult[indexPath.row]
let cellImage = UIImageView(frame: CGRect(x: 5, y: 5, width: view.frame.size.width / 3, height: 90))
cellImage.contentMode = .scaleAspectFit
let productMainImageString = dict["id"] as! Int
let url = "https://example.com/api/DigitalCatalog/v1/getImage?id=\(productMainImageString)&name=primary"
self.downloadImage(url, inView: cellImage)
cell.addSubview(cellImage)
let cellTitle = UILabel(frame: CGRect(x: view.frame.size.width / 3, y: 5, width: (view.frame.size.width / 3) * 1.9, height: 40))
cellTitle.textColor = UIColor.darkGray
cellTitle.textAlignment = .right
cellTitle.text = dict["title"] as? String
cellTitle.font = cellTitle.font.withSize(self.view.frame.height * self.relativeFontConstantT)
cell.addSubview(cellTitle)
let cellDescription = UILabel(frame: CGRect(x: view.frame.size.width / 3, y: 55, width: (view.frame.size.width / 3) * 1.9, height: 40))
cellDescription.textColor = UIColor.darkGray
cellDescription.textAlignment = .right
cellDescription.text = dict["description"] as? String
cellDescription.font = cellDescription.font.withSize(self.view.frame.height * self.relativeFontConstant)
cell.addSubview(cellDescription)
return cell
}
You are adding subviews multiple times while dequeuing reusable cells. What you can do is make a prototype cell either in storyboard or as xib file and then dequeue that cell at cellForRowAtIndexPath.
Your custom class for cell will look similar to this where outlets are drawn from prototype cell.
Note: You need to assign Reusable Identifier for that prototype cell.
class DemoProtoTypeCell: UITableViewCell {
#IBOutlet var titleLabel: UILabel!
#IBOutlet var descriptionLabel: UILabel!
#IBOutlet var titleImageView: UIImageView!
}
Now you can deque DemoProtoTypeCell and use accordingly.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: DemoProtoTypeCell.self), for: indexPath) as! DemoProtoTypeCell
cell.titleImageView.image = UIImage(named: "demoImage")
cell.titleLabel.text = "demoTitle"
cell.descriptionLabel.text = "Your description will go here."
return cell
}
That's because you are adding subviews to reused (so that it may already have subviews added previously) cells.
Try to check if the cell has subviews and fill in information you need, if there're no subviews then you add them to the cell.
Option 1
if let imageView = cell.viewWithTag(1) {
imageView.image = //your image
} else {
let imageView = UIImageView(//with your settings)
imageView.tag = 1
cell.addSubview(imageView)
}
Option 2
Crete UITableViewCell subclass that already has all the subviews you need.
I have used below method to remove all subviews from cell:
override func prepareForReuse() {
for views in self.subviews {
views.removeFromSuperview()
}
}
But I have created UITableViewCell subclass and declared this method in it.
you can also do one thing as #sCha has suggested. Add tags to the subviews and then use the same method to remove subview from cell:
override func prepareForReuse() {
for view in self.subviews {
if view.tag == 1 {
view.removeFromSuperview()
}
}
}
Hope this helps.
I think the other answers already mentioned a solution. You should subclass the tableview cell and just change the values of your layout elements for each row.
But I want to explain why you get this strange behaviour.
When you call
tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
it tries to reuse an already created cell with the passed identifier #"cell". This saves memory and optimises the performance. If not possible it creates a new one.
So now we got a cell with layout elements already in place and filled with your data. Your code then adds new elements on top of the old ones. Thats why your layout is messed up. And it only shows if you scroll, because the first cells got no previous cells to load.
When you subclass the cell try to create the layout only once on first initialisation. Now you can pass all values to the respective layout element and let the tableview do its thing.
Try this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell:UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: "cell")
if cell == nil
{
cell = UITableViewCell.init(style: UITableViewCellStyle.default, reuseIdentifier: "cell")
}
for subView in cell.subviews
{
subView.removeFromSuperview()
}
// Your Code here
return cell
}
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)!
All my cells have a switch that moves the cell to the bottom of the table. It works fine, however when there is enough cells to drop below the visible bounds, I receive the error that the cell doesn't exist. This doesn't work because I need to update ALL cells sender.tag.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: TaskTableViewCell = tableView.dequeueReusableCellWithIdentifier("cell") as! TaskTableViewCell
var task = tasks[indexPath.row]
let index = tasks.indexOf(task)!
cell.cellSwitch.tag = index
cell.addSubview(cell.cellSwitch)
cell.tag = index
cell.cellSwitch.addTarget(self, action: "switched:", forControlEvents: UIControlEvents.TouchUpInside)
return cell
}
func switched(sender: UIButton) {
let indexPath = NSIndexPath(forRow: sender.tag, inSection: 0)
let finalIndexPath = NSIndexPath(forRow: tasks.count - 1, inSection: 0)
let task = tasks[indexPath.row]
tasks.removeAtIndex(indexPath.row)
tasks.append(task)
self.taskTableView.moveRowAtIndexPath(indexPath, toIndexPath: finalIndexPath)
for var i = 0; i < tasks.count; i ++ {
let cellIndex = NSIndexPath(forRow: i, inSection: 0)
let cell: TaskTableViewCell = self.taskTableView.cellForRowAtIndexPath(cellIndex) as! TaskTableViewCell
cell.cellSwitch.tag = cellIndex.row
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cellToBeReturned = UITableViewCell()
if indexPath.row != tableView.numberOfRowsInSection(0) - 1 {
let cell: TaskTableViewCell = tableView.dequeueReusableCellWithIdentifier("cell") as! TaskTableViewCell
var task = tasks[indexPath.row]
cell.cellFlatSwitch.removeFromSuperview()
cell.currentStreak.removeFromSuperview()
switch(task.icon) {
case "heart"?:
task.color = colors[0]
case "meditate"?:
task.color = colors[1]
case "leaf"?:
task.color = colors[2]
case "run"?:
task.color = colors[3]
case "muscle"?:
task.color = colors[4]
default:
task.color = .blackColor()
}
switch(task.icon) {
case "heart"?:
cell.pretickButton.setImage(Icons.imageOfHeartIcon, forState: .Normal)
case "meditate"?:
cell.pretickButton.setImage(Icons.imageOfMeditationIcon, forState: .Normal)
case "run"?:
cell.pretickButton.setImage(Icons.imageOfRunIcon, forState: .Normal)
case "leaf"?:
cell.pretickButton.setImage(Icons.imageOfLeafIcon, forState: .Normal)
case "muscle"?:
cell.pretickButton.setImage(Icons.imageOfMuscleIcon, forState: .Normal)
default:
cell.pretickButton.setImage(Icons.imageOfLeafIcon, forState: .Normal)
}
if task.longestStreak >= 21 {
cell.starImage.alpha = 1
} else {
cell.starImage.alpha = 0
}
if localSettings.habitPreview == "dayCounter" {
cell.currentStreak = LTMorphingLabel(frame: CGRectMake(11, 29, 39, 31))
cell.currentStreak.morphingEffect = LTMorphingEffect.Anvil
cell.currentStreak.textAlignment = NSTextAlignment.Center
cell.currentStreak.text = String(task.consecutiveDays!)
cell.addSubview(cell.currentStreak)
cell.streakSublabel.textAlignment = .Center
cell.currentStreak.adjustsFontSizeToFitWidth = true
cell.streakSublabel.adjustsFontSizeToFitWidth = true
cell.growthPreviewImage.hidden = true
cell.streakSublabel.hidden = false
} else {
if task.growth > 0 && task.growth <= 21 {
cell.growthPreviewImage.image = UIImage(named: "growth\(task.growth!)")
} else if task.growth <= 0 {
cell.growthPreviewImage.image = UIImage(named: "growth1")
} else if task.growth > 21 {
cell.growthPreviewImage.image = UIImage(named: "growht21")
}
cell.growthPreviewImage.hidden = false
cell.streakSublabel.hidden = true
}
if task.consecutiveDays == 1 {
cell.streakSublabel.text = "day"
} else {
cell.streakSublabel.text = "days"
}
cell.taskLabel.adjustsFontSizeToFitWidth = true
cell.taskLabel.text = task.name
cell.taskLabel.textColor = task.color
cell.cellFlatSwitch = AIFlatSwitch(frame: CGRectMake(300, 15, 70, 70))
cell.cellFlatSwitch.lineWidth = 2.3
cell.cellFlatSwitch.strokeColor = task.color!
cell.cellFlatSwitch.trailStrokeColor = task.color!
let index = tasks.indexOf(task)!
cell.pretickButton.tag = index
cell.backgroundColor = UIColor.clearColor()
cell.addSubview(cell.cellFlatSwitch)
cell.tag = index
cell.bringSubviewToFront(cell.pretickButton)
cell.pretickButton.addTarget(self, action: "switched:", forControlEvents: UIControlEvents.TouchUpInside)
cell.sendSubviewToBack(cell.cellFlatSwitch)
if task.doneToday == true {
cell.cellFlatSwitch.selected = true
cell.pretickButton.alpha = 0.0101
} else {
cell.cellFlatSwitch.setSelected(false, animated: false)
cell.pretickButton.alpha = 1.0
}
cellToBeReturned = cell
} else {
let cell: AddNewTableViewCell = tableView.dequeueReusableCellWithIdentifier("add") as! AddNewTableViewCell
cell.tag = indexPath.row
cellToBeReturned = cell
}
return cellToBeReturned
}
Any tips or help is greatly appreciated.
You are doing it wrong.
Don't call methods with NSIndexPath object references to move cells around. The implementation of UITableView suggests you should use one data source (could be an array, a dictionary, a custom NSObject, NSFetchedResultsController, etc) to implement all the relevant UITableViewDataSource and UITableViewDelegate delegate methods and you make updates to your data source first then call either reloadData or beginUpdates and endUpdates on your UITableView whenever you need to refresh your view.
In your case, I would simply use 2 NSMutableArray's, one for items with, one for without tags. Your switch handler should retrieve the item from the cell index path and move it from one array to the other. Then you can call beginUpdates and move your cell appropriately.
They key is to make changes to your data source first and in a way that is consistent to your UITableViewDataSource methods, especially the cellForAtIndexPath method. Basically the error you get is most likely because your data source is out of sync with the visible cells.
Hope this helps.