My tableview looks like this
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! Cell
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 200
}
And my tableviewcell looks like this
class Cell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
When I change switch state on any cell and scroll. The switch for another cell periodically have state changed on scroll for other cells. Please help
You need to have data array with state for every switch and in your didSelect method change data model for switch then in cellForRow at get switch state from array and your viewController need to be SwitchControlDelegate
class UIViewController:SwitchControlDelegate{
var array = [Bool](count: 200, repeatedValue:false)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! Cell
cell.switchControl.isOn = array[indexPath.row]
cell.switchDelegate = self
cell.index = indexPath.row
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 200
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
array[indexPath.row] = !array[indexPath.row]
}
func switchChangeStateWithIndex(index:Int){
array[index] = !array[index]
}
This will change switch state when you click on cell , if you need to change state when change switch control you will need delegate to update data model in viewController
In your cell file:
protocol SwitchControlDelegate: class {
func switchChangeStateWithIndex(index:Int)
}
class Cell: UITableViewCell {
var index:Int = 0
weak var switchDelegate: SwitchControlDelegate?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBAction func valueChanged(_ sender: AnyObject) {
switchDelegate?.switchChangeStateWithIndex(index:index)
}
}
You need to match action value change from your switch control with this function valueChanged , so every time when you change switch state cell need to call func value Changeed
Handle your switch control properly for each cell in,
tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
so that when cell is dequeue, it handles each cell's switchControl properly.
Solution:
When you change state of switch control for cell, save that indexPath somewhere so that when you iterate through this indexPath in tableview cellForRowAt indexPath you set that state of switch again else set default state
Related
Referance Link that I have used
Here is my code
class ViewController: UITableViewController{
override func viewDidLoad() {
super.viewDidLoad()
tableView.allowsMultipleSelectionDuringEditing = true
tableView.setEditing(true, animated: false)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "\(indexPath.row)"
return cell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
}
It was Work fine for the default UITableViewCell. But if I have do the same thing with Custom UITableViewCell then selection is not worling
Code with Custom Cell
class ViewController: UITableViewController{
override func viewDidLoad() {
super.viewDidLoad()
tableView.allowsMultipleSelectionDuringEditing = true
tableView.setEditing(true, animated: false)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customcell", for: indexPath) as! ProductTblCell
cell.lblProductTitle?.text = "\(indexPath.row)"
return cell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
}
ProductTblCell Calss
class ProductTblCell: UITableViewCell {
#IBOutlet weak var lblProductTitle: UILabel!
}
Can you please someone tell me what's going Wrong? Thanks in advance
Output
It was, as tends to be the case, my mistake
After searching a lot I realized the issue.
The problem was mostly we all set the cell's selectionStyle property to .none from Storyboard or programmatically to remove the background color of the cell during the selection.
Keep in mind that if you want to use the default selection or
multiple selections feature during the editing must you need to follow this
Set the Selection Style from Storyboard
You can also Set in table views cellForRowAt method
cell.selectionStyle = .none
To remove or change the background color of the cell for the selection you need to use this override method in UITableViewCell
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
self.backgroundColor = selected ? .gray : .white
}
I'm trying to create an App but I'm struggling with a simple problem: How can I add a delete button to my custom cells?
I've created a NewCocoaTouchClass
I've created my custom design (added also a button for every cell) and provided the Outlets in the TableViewCellController
I managed to implement my custom cell to another viewController
I created a button on my viewController
I'd like that when the user taps on my new button, cell buttons will appear, and then I can delete cells singularly. When I create the IbAction I can't retrieve the cell code because the cell is defined only in my tableViewCode.
Thank in advance
Ps: I also implemented deleting cells by swipe but my custom cell design is incompatible with the standard rectangular delete option (this is awful).
TableViewCellController
import UIKit
class CustomTableTableViewCell: UITableViewCell {
static let identificatore = "CustomTableTableViewCell"
static func nib() -> UINib {
return UINib(nibName: "CustomTableTableViewCell", bundle: nil)
}
#IBOutlet weak var ImmagineCella: UIImageView!
#IBOutlet weak var TestoCella: UILabel!
#IBOutlet weak var Button: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
ViewController
import UIKit
class MaterieViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var materie : [String] = ["zoifvdhfdv", "szvzv", "zdvzfv", "zfdvbfdb", "bfzdfb"]
#IBOutlet weak var TableViewMaterie: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
TableViewMaterie.register(CustomTableTableViewCell.nib(), forCellReuseIdentifier: CustomTableTableViewCell.identificatore)
TableViewMaterie.delegate = self
TableViewMaterie.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return materie.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CustomTableTableViewCell.identificatore, for: indexPath) as! CustomTableTableViewCell
cell.TestoCella.text = materie[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 67.83
}
#IBAction func EditButton(_ sender: UIButton) {
}
}
This is the code I use in my project
func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String?
{
return "Delete"
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == .delete) {
//Add your code
}
}
You can write with add target
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CustomTableTableViewCell.identificatore, for: indexPath) as! CustomTableTableViewCell
cell.TestoCella.text = materie[indexPath.row]
cell. Button.tag = indexPath.row
cell. Button.addTarget(self, action: #selector(buttonClicked(sender:)), for: .touchUpInside)
return cell
}
func buttonClicked(sender: UIButton) {
let buttonRow = sender.tag
print(buttonRow)
}
UITableViewCell is crashing with "Unexpectedly found nil while implicitly unwrapping an Optional value" when i try to modify cell subviews.
Registering tableView cell
self.tableView.register(TravelTypeTableViewCell.self, forCellReuseIdentifier: "TravelCell")
tablView methods
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 224
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TravelCell", for: indexPath) as! TravelTypeTableViewCell
cell.travelTypeSegment.selectedSegmentIndex = 0
return cell
}
TableViewcell class
class TravelTypeTableViewCell: UITableViewCell {
#IBOutlet var travelTypeSegment: UISegmentedControl!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Storyboard images
Comment this
self.tableView.register(TravelTypeTableViewCell.self, forCellReuseIdentifier: "TravelCell")
As for prototype cells you shouldn't register them as this done automatically for them and make sure travelTypeSegment is connected in IB
My tableView cellForRowAtIndexPath looks like so:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CheckoutAppointmentCell.reuseIdentifier) as! CheckoutAppointmentCell
cell.appointment = appointments[indexPath.row]
cell.checkoutButton.tag = indexPath.row
cell.checkoutButton.addTarget(self, action: #selector(checkoutButtonTapped), for: .touchUpInside)
return cell
}
And then I remove the appointment from the tableView and dataSource like so:
func checkoutButtonTapped(sender: UIButton) {
appointments.remove(at: sender.tag)
print(sender.tag)
//self.tableView.beginUpdates()
self.tableView.deleteRows(at: [IndexPath(row:sender.tag, section: 0)], with: .automatic)
//self.tableView.endUpdates()
}
The first time I remove an appointment, it works fine. The sender.tag value is what it should be and the correct row is removed from the tableView.
After removing the first row, it seems to remove the incorrect row afterwards.
I have tried calling reloadData() after calling deleteRows but the animation no longer occurs. beginUpdates() and endUpdates() seems to make no difference neither.
Using tags to track index paths is a common but very poor practice. It fails in any table view that allows rows to be deleted, inserted, or moved because remaining cells now have invalid tags unless the table view is fully reloaded using reloadData.
The better solution that doesn't require the use of reloadData to keep tags up-to-date, is to determine the indexPath of the cell's button based on the button's location.
func checkoutButtonTapped(sender: UIButton) {
let hitPoint = sender.convert(CGPoint.zero, to: tableView)
if let indexPath = tableView.indexPathForRow(at: hitPoint) {
// use indexPath to get needed data
}
}
Well i don't know it is good idea or not but it also seems to work fine using CallBack Closures, as using tags are not good idea.
As some points suggested by #rmaddy I updated answer accordingly.
CustomCell Class-:
import UIKit
class testingCell: UITableViewCell {
var deleteCallBack : ((testingCell)->())?// CallBack function
#IBOutlet weak var parentlabel: UILabel!
#IBAction func deleteButton(_ sender: UIButton) {
// Call your closure
if let callBack = deleteCallBack{
callBack(self)
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Controller class-:
extension ViewController : UITableViewDelegate,UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}// Default is 1 if not implemented
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
// return number of rows in section
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! testingCell
cell.textLabel?.text = data[indexPath.row]
cell.deleteCallBack = { [weak self] tableCell in
//Print indexPath for selected Cell
print(self?.dataTableView.indexPath(for: tableCell) as Any )
if let selectedIndex = self?.dataTableView.indexPath(for: tableCell) {
// Print selected row
print(selectedIndex.row)
// delete row from array
self?.data.remove(at: selectedIndex.row)
// Get index row to be deleted from table
let indePath = NSIndexPath(item: selectedIndex.row, section: selectedIndex.section)
// delete row from table
self?.dataTableView.deleteRows(at: [indePath as IndexPath], with: UITableViewRowAnimation.automatic)
}
}
return cell
}
public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat{
// Return cell height
return 100
}
It is indeed printing out correct index after deletion.
So here is the structure of the TableView:
There is a main UITableView, and inside each UITableViewCell there is another UITableview
Screenshot:
Each of the UITableViewCells have been designed as Custom Views and have been added by loading it from the Nib in the cellForRowAtIndexPath function.
What I want to do is for any option selected in the inner Table Views I want to get the index path of the cell that the Table View is embeded in.
Document Layout:
I tried to follow the delegate approach mentioned by Paulw11 here:
swift: how to get the indexpath.row when a button in a cell is tapped?: StackOverflow
Note: The method suggested by Paulw11 works perfectly
Code(TableVC):
class TableVC: UITableViewController, QuestionCellDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 5
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 220.0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = Bundle.main.loadNibNamed("QuestionCell", owner: self, options: nil)?.first as! QuestionCell
cell.delegate = self
return cell
}
func sendCellInfo(cell: UITableViewCell) {
print(cell)
let indexPathForQuestion = tableView.indexPath(for: cell)
print(indexPathForQuestion)
}}
Code(QuestionCell):
protocol QuestionCellDelegate: class {
func sendCellInfo(cell: UITableViewCell)
}
class QuestionCell: UITableViewCell, UITableViewDelegate, UITableViewDataSource{
#IBOutlet weak var optionsTableview: UITableView!
var delegate: QuestionCellDelegate?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
self.optionsTableview.delegate = self
self.optionsTableview.dataSource = self
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = Bundle.main.loadNibNamed("OptionsCell", owner: self, options: nil)?.first as! OptionsCell
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 4
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedCell = optionsTableview.cellForRow(at: indexPath)
print("selectedCell")
self.delegate?.sendCellInfo(cell: selectedCell!)
}}
Code(OptionsCell):
class OptionsCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}}
Note: The current O/P is nil
Note: Code changed as per commented by pbasdf, realised the mistake
Found the solution due to pbasdf comment:
Delegate Function in TableVC:
func sendCellInfo(cell: UITableViewCell) {
/*** Take the cell passed and convert to a CGPoint wrt to TableView ***/
let cellPosition: CGPoint = cell.convert(cell.bounds.origin, to: self.tableView)
/*** wrt to CGPoint find index on current TableView ***/
/*** Returns as Section,Cell ***/
let indexPathForSelectedCell = (tableView.indexPathForRow(at: cellPosition)?.row)
print(indexPathForSelectedCell)
}
The following answer is added #Supratik Majumdar request for the logic which I said.
Supratik try using the following code, I hope you will get your need done.
//Initialize your question or answer in viewDidLoad or where ever you like to as shown below
self.questionArray = ["Question1", "Question2"]
self.optionArray = [["Option 1", "Option 2", "Option 3", "Option 4"], ["Option 1", "Option 2", "Option 3", "Option 4"]]
//Make us of the following tableview delegate & datasource code
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.questionArray.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.OptionArray[section].count
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String {
return self.questionArray[section]
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
let currentOptionArray = self.questionArray[section]
let currentOption = currentOptionArray[indexPath.row]
cell.textLabel.text = currentOption
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedIndex = indexPath
let selectedQuestionIndex = indexPath.section
let selectedOptionIndex = indexPath.row
//Make use of the data you need in the above values
}
Use this:
tableView.cellForRowAtIndexPath(YourIndexPath) as! OptionCell
You can do your own indexPath as global variable and filing it on didSelectRow method
YourIndexPath = indexPath