Using Swift4, iOS11.2.1, Xcode9.2,
I successfully added a custom button to the last cell of a tableView. (the button is used to add cells in the tableView - this also works fine...) - see Screenshot-1.
But now the issue: When adding more cells (i.e. more than fit in the tableView-height), and if you scroll to the top, then there is a second cell that shows this button - see Screenshot-2.
(see code below....)
Here are the two screenshots:
How can I get rid of the extra button ????
(it seems that a cell is reused and the button is partly drawn. The cut-off can be explained since the height of the last cell is made bigger than the other cells. But still, there shouldn't be parts of buttons in any other cell than the very last one...even when scrolling up).
Any help appreciated !
Here is my code (or excerts of it...):
override func viewDidLoad() {
super.viewDidLoad()
// define delegates
self.fromToTableView.dataSource = self
self.fromToTableView.delegate = self
// define cell look and feel
self.addButton.setImage(UIImage(named: "addButton"), for: .normal)
self.addButton.addTarget(self, action: #selector(plusButtonAction), for: .touchUpInside)
//...
self.fromToTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let resItems = resItems else {
self.rowCount = 0
return 0
}
self.rowCount = resItems.count - 1
return resItems.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if(indexPath.row == (self.rowCount)) {
return 160
} else {
return 120
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = fromToTableView.dequeueReusableCell(withIdentifier: "SettingsCell") as? SettingsCustomTableViewCell
// configure the cell
cell?.configureCell(tag: indexPath.row)
// fill cell-data
if let resItem = self.resItems?[indexPath.row] {
cell?.connectionItem = resItem
}
// add addButton to last cell
if(indexPath.row == (self.rowCount)) {
// add addButton
cell?.contentView.addSubview(self.addButton)
// scroll to last cell (that was just added)
// First figure out how many sections there are
let lastSectionIndex = self.fromToTableView.numberOfSections - 1
// Then grab the number of rows in the last section
let lastRowIndex = self.fromToTableView.numberOfRows(inSection: lastSectionIndex) - 1
// Now just construct the index path
let pathToLastRow = IndexPath(row: lastRowIndex, section: lastSectionIndex)
// Scroll to last cell
self.fromToTableView.scrollToRow(at: pathToLastRow as IndexPath, at: UITableViewScrollPosition.none, animated: true)
}
return cell!
}
You already figured out what the problem is :)
As you say here:
it seems that a cell is reused and the button is partly drawn
And that is exactly what happens here. You have a pool of UITableViewCell elements, or in your case SettingsCustomTableViewCell. So whenever you say:
let cell = fromToTableView.dequeueReusableCell(withIdentifier: "SettingsCell") as? SettingsCustomTableViewCell
Then, as the name implies, you dequeue a reusable cell from your UITableView.
That also means that whatever you may have set on the cell previously (like your button for instance) stays there whenever you reuse that specific instance of a cell.
You can fix this in (at least) three ways.
The Hack
In your tableView(_:cellForRowAt:indexPath:) you have this:
if(indexPath.row == (self.rowCount)) {
//last row, add plus button
}
You can add an else part and remove the plus button again:
if(indexPath.row == (self.rowCount)) {
//last row, add plus button
} else {
//not last row, remove button
}
The Right Way
UITableViewCell has the function prepareForReuse and as the documentation says:
If a UITableViewCell object is reusable—that is, it has a reuse identifier—this method is invoked just before the object is returned from the UITableView method dequeueReusableCell(withIdentifier:)
So, this is where you "reset" your custom table view cell. In your case you remove the plus button.
You already have a custom UITableViewCell it seems, so this should be a small change.
The Footer Way
As #paragon suggests in his comment, you can create a UIView and add that as a tableFooterView to your UITableView.
let footerView = YourFooterViewHere()
tableView.tableFooterView = footerView
Hope that helps
Related
First i hide all images in cell. After i click on did select row, in that cell will show image and click again it will hide image. That working. But the problem is when i click on that cell (without click again) and then click other cell, (other cell will show image) but the previous cell will not hide image.
This code for cell and didselectrow
take one variable who contains current show image's indexPath.row value
var showImageIndex : Int?
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let tableCell = tableView.dequeueReusableCellWithIdentifier("cellName") as? CustomCell
if showImageIndex == indexPath.row{
tableCell.IBimageView.isHidden = false
}else{
tableCell.IBimageView.isHidden = true
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){
showImageIndex = indexPath.row
self.tableViewName.reload()
}
In your didSelect method before make anything with your index you need to hidden image in old cell.
this will hide image in your old cell
if(index != nil){
tableView.cellForRow(at:index!) as! FailOrderNoteTableViewCell
cell.rightViewCell.hidden = true
}
And other problem in your code is that you need to set nil for index if you haven't selected cell not row 99 and section 99 that will work fine
Sorry I really cant find this anywhere.
I need to set my selection style to none so that the rows dont highlight when i click on it. Also, i need rows to be selectable as I have some rows which needs expanding and collapsing. I know the piece of code is UITableViewCellSelectionStyle.None but I have no idea where I can implement it. Thanks!
EDIT ADDED IN CODES
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 7
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
// Set height for date picker
if indexPath.section == 0 && indexPath.row == 2 {
let height:CGFloat = datePicker.hidden ? 0.0 : 216.0
return height
}
return super.tableView(tableView, heightForRowAtIndexPath: indexPath)
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// Expanding and collapsing date picker
UITableViewCellSelectionStyle.None
let datePickerIndexPath = NSIndexPath(forRow: 1, inSection: 0)
if datePickerIndexPath == indexPath {
datePicker.hidden = !datePicker.hidden
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.tableView.beginUpdates()
// apple bug fix - some TV lines hide after animation
self.tableView.deselectRowAtIndexPath(indexPath, animated: true)
self.tableView.endUpdates()
})
}
}
The codes are mainly for the datepicker that i have implemented. everything works fine but clicking on the cell highlights the whole row in the default selection color.
Hard to know where to best implement it without seeing your code, but you can definitely put it in your cellForRowAtIndexPath when you dequeue/initialize your cell. Just call yourCell.selectionStyle = .None before return yourCell
cell.selectionStyle = .None
When you write the code after equal(=) , just press dot(.) so that many type of functionality will be pop up like this-
And for your second issue just put some value in array to check that is working correctly or not.
I have had the same issue, the solution for me was:
self.tableView.allowsSelection = true
in storyboard, manually select all the static cells and change selection from "default" to "none"
for those cells are allowed to be selected, change the selection to "Default".
It would appear as if only some cells can be selected.
You can still handle selection for those exclusive cells in didSelectRowAt by checking indexPath.
Hope that helps.
Just found a way to programmatically insert the selection style setting for static cells, instead of using storyboard:
In viewWillAppear, use:
tableView.cellForRow(at: yourIndexPath1)?.selectionStyle = .none
tableView.cellForRow(at: yourIndexPath1)?.selectionStyle = .none
...
Nearly during a week i'm trying to figure out how i can append a static-/custom tableviewcell to a dynamically generated tableview. I'm populating the cells based on the data i'm getting from the database. Basically what i'm trying to accomplish is like the following picture from the app ClassDojo:
As you may know and see, you can add add as many groups as you want with the ClassDojo app, but the latest cell, in this case Voeg een nieuwe klas toe, will always stay at the bottom of the tableview. That's exactly what i'm trying to do.
What i tried to do till this moment is trying to calculate the latest cell in the tableview and trying to append my custom cell, but unfortunately i couldn't get my head around it.
I would really appreciate if someone can help me out with this.
Thanks in advance.
Please let me know if you guys need any code.
---------EDITED POST---------
I did accomplish to assign my custom cell thanks to #Slayter, but now i'm facing with the problem that my custom cell is immediately overwritten by my dynamically created cells (with Alamofire).
Any help would be appreciated.
ClassDojo iOS Engineer here! More than happy to share how we do this.
Like Slayter mentioned, we do
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count + 1 // Add one for your custom cell
}
But in addition we also do the following:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = nil;
if self.indexIsForCustomCell(indexPath) {
cell = tableView.dequeueReusableCellWithIdentifier(CustomCell.reuseIdentifier)
// Additional configuration
} else {
cell = tableView.dequeueReusableCellWithIdentifier(RegularCell.reuseIdentifier)
// Additional configuration
}
return cell
}
Where CustomCell looks something like this (not exact):
class CustomCell: UITableViewCell {
static let reuseIdentifier = "CustomCell"
// More code
}
And regular cell looks like:
class RegularCell: UITableViewCell {
static let reuseIdentifier = "RegularCell"
// More code
}
The reason your dynamic cells are getting overwritten is because of the line
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as UITableViewCell
Notice that you are using the same cellIdentifier regardless of whether or not it is the custom cell or the regular cells. This means that most likely you are doing the following:
self.tableView.registerClass(RegularCell.class, forIdentifier: "new group")
self.tableView.registerClass(CustomCell.class, forIdentifier: "new group")
When you should be doing:
self.tableView.registerClass(RegularCell.class, forIdentifier: RegularCell.reuseIdentifier)
self.tableView.registerClass(CustomCell.class, forIdentifier: CustomCell.reuseIdentifier)
OR
self.tableView.registerClass(RegularCell.class, forIdentifier: "regularCellReuseIdentifier")
self.tableView.registerClass(CustomCell.class, forIdentifier: "customCellReuseIdentifier")
By using the same identifier key, you are telling the UITableView to treat both cells as the same type. So when it needs to reclaim memory for a new cell being drawn on screen, it's going to use RegularCell and CustomCell interchangeably.
Hope this helped and thanks for checking out our App!
================= EDIT =================
Realized that I forgot to add the indexIsForCustomCell method. Here it is:
func indexIsForCustomCell(indexPath : NSIndexPath) -> Bool {
if self.myData.count > 0 {
return indexPath.section == 0 && indexPath.row == (self.myData.count+1)
}
return indexPath.section == 0 && indexPath.row == 0
}
It's pretty simple. You just need to tell the tableView to expect one more cell than what is in your data source.
Example
In your numberOfRowsInSection method, you will have something like this:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count + 1 // Add one for your custom cell
}
Then in your cellForRowAtIndexPath method you just need to add some custom logic for that indexPath
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as UITableViewCell
if indexPath.row < myData.count {
// configure cell as normal
} else {
// Add your custom cell logic here
}
return cell
}
I am aware of adding the activityIndicator to a certain place in the view, starting and stopping the animation when required. But in certain instances in my app, where the user clicks on a cell(with the title and subtitle), I fetch some data in the background and then show an alert to the user to get some inputs. In all these cases, I just display an activity indicator in the middle, which doesn't look so good. So, I would like to display the activity indicator only at the end of the cell, on which the user clicked. Is there a way to do that?
One thing I could think of is using a custom table View Cell and then explicitly adding an activity indicator there. But is it possible to do the same with a standard title and subtitle cell, and may be dynamically re-positioning the activity indicator based on the position of the cell the user has clicked?
I think you can do it using UITableViewCell's accessoryView
var selectedIndexPath : NSIndexPath?
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
// ...
if (indexPath == selectedIndexPath)
{
cell.accessoryView = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
(cell.accessoryView as! UIActivityIndicatorView).startAnimating()
}
else
{
cell.accessoryView = nil
}
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
selectedIndexPath = indexPath
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
}
I have a tableview, and each tableviewcell has a button.
I want to change current cell height when cell's button clicked.
How can I do this? Thank you!
Create an array that holds your selected cells index's
var selectedCellIndexs : NSMutableArray = []
In didSelectRowAtIndexPath indexPath: NSIndexPath) function add:
self.selectedCellIndexs.addObject(indexPath)
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
Now in the tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath), all you need to do is check if the cell is part of the selected array and if it is return a different value.
if (self.selectedCellIndexs.containsObject(indexPath)) {
return selectedHeight
}
return notSelectedHeight
Remember to that if you want to deselect a cell you will need to remove the index path from your selectedCellIndexs when the cell is clicked.
NOTE: This is the basic work flow for when the user selects the cell to change the height. A bit more work is needed to get the cell from the button action.
You should store all tableviewcell's height in an NSMutableArray.
When user clicks on tableviewcell's button, update height in NSMutableArray.
After that reload your UITableView.
Hope this helps.
Globally declare a mutableArray.
var buttonPressedIndexPaths : NSMutableArray = []
in cellForRowAtIndexPath() method
cell?.button.tag = indexPath.row
cell?.button.addTarget(self, action: "onButtonAction:", forControlEvents: UIControlEvents.TouchUpInside)
copy paste these methods
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
if(buttonPressedIndexPaths.containsObject(indexPath))
{
return 90;//button pressed cell height
}
else
{
return 44;//normal
}
}
func onButtonAction(sender:UIButton)
{
var indexPath : NSIndexPath = NSIndexPath(forRow: sender.tag, inSection: 0)//section may differ based on ur requirement
if(buttonPressedIndexPaths.containsObject(indexPath))
{
buttonPressedIndexPaths.removeObject(indexPath)
}
else
{
buttonPressedIndexPaths.addObject(indexPath)
}
tableView.reloadData();
}