Delegate Method to Uitableviewcell Swift

Delegate Method to UItableViewCell Swift

If I correctly understood your question, maybe this could help:

class ViewController: UIViewController, YourCustomTableDelegate {

@IBOutlet weak var tableView: YourCustomTableView!

override func viewDidLoad() {
super.viewDidLoad()
self.tableView.customTableDelegate = self
}

// table delegate method
func shouldAnimateCell(at indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
cell.animate(...)
}
}
}

Correct way to use UITableViewDataSource and Custom UITableViewCell Delegate

One option is to have your MyTableViewController conform to your MyTableViewCellDelegate and then set the controller as the delegate in cellForRowAt in your dataSource class.

However, you may be much better off using a closure.

Get rid of your delegate and indexPath properties in your cell, and add a closure property:

final class MyTableViewCell: UITableViewCell, UITextFieldDelegate {

@IBOutlet weak var packageSizeTextField: UITextField!

override func awakeFromNib() {
super.awakeFromNib()
configureCell()
}
func configureCell() {
// configureCell...
packageSizeTextField.delegate = self
}

var changeClosure: ((String, UITableViewCell)->())?

func textFieldDidChangeSelection(_ textField: UITextField) {
print(#function)
changeClosure?(textField.text ?? "", self)
// delegate?.doSomething(self, indexPath: indexPath, text: textField.text ?? "")
}
}

Now, in your dataSource class, set the closure:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCell(withIdentifier: "mtvc", for: indexPath) as! MyTableViewCell
c.packageSizeTextField.text = myData[indexPath.row]
c.changeClosure = { [weak self, weak tableView] str, c in
guard let self = self,
let tableView = tableView,
let pth = tableView.indexPath(for: c)
else {
return
}
// update our data
self.myData[pth.row] = str
// do something with the tableView
//tableView.reloadData()
}
return c
}

Note that as you have your code written, the first tap in the textField will not appear to do anything, because textFieldDidChangeSelection will be called immediately.


Edit

Here's a complete example that can be run without any Storyboard connections.

The cell creates a label and a text field, arranging them in a vertical stack view.

Row Zero will have the text field hidden and its label text will be set to the concatenated strings from myData.

The rest of the rows will have the label hidden.

The closure will be called on .editingChanged (instead of textFieldDidChangeSelection) so it is not called when editing begins.

Also implements row deletion for demonstration purposes.

The first row will be reloaded when text is changed in any row's text field, and when a row is deleted.

Cell Class

final class MyTableViewCell: UITableViewCell, UITextFieldDelegate {

var testLabel = UILabel()
var packageSizeTextField = UITextField()

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
configureCell()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configureCell()
}
func configureCell() {
// configureCell...
let stack = UIStackView()
stack.axis = .vertical
stack.translatesAutoresizingMaskIntoConstraints = false

testLabel.numberOfLines = 0
testLabel.backgroundColor = .yellow

packageSizeTextField.borderStyle = .roundedRect

stack.addArrangedSubview(testLabel)
stack.addArrangedSubview(packageSizeTextField)

contentView.addSubview(stack)

let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
stack.topAnchor.constraint(equalTo: g.topAnchor),
stack.leadingAnchor.constraint(equalTo: g.leadingAnchor),
stack.trailingAnchor.constraint(equalTo: g.trailingAnchor),
stack.bottomAnchor.constraint(equalTo: g.bottomAnchor),
])

packageSizeTextField.addTarget(self, action: #selector(textChanged(_:)), for: .editingChanged)
}

var changeClosure: ((String, UITableViewCell)->())?

@objc func textChanged(_ v: UITextField) -> Void {
print(#function)
changeClosure?(v.text ?? "", self)
}
}

TableView Controller Class

class MyTableViewController: UITableViewController {

lazy var myTableViewDataSource: MyTableViewDataSource = {
MyTableViewDataSource()
}()

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
tableView.register(MyTableViewCell.self, forCellReuseIdentifier: "mtvc")
tableView.dataSource = myTableViewDataSource
}

}

TableView DataSource Class

final class MyTableViewDataSource: NSObject, UITableViewDataSource {

var myData: [String] = [
" ",
"One",
"Two",
"Three",
"Four",
"Five",
"Six",
"Seven",
"Eight",
]

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
myData.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
tableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
} else if editingStyle == .insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return indexPath.row != 0
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCell(withIdentifier: "mtvc", for: indexPath) as! MyTableViewCell
c.testLabel.isHidden = indexPath.row != 0
c.packageSizeTextField.isHidden = indexPath.row == 0

if indexPath.row == 0 {
myData[0] = myData.dropFirst().joined(separator: " : ")
c.testLabel.text = myData[indexPath.row]
} else {
c.packageSizeTextField.text = myData[indexPath.row]
}

c.changeClosure = { [weak self, weak tableView] str, c in
guard let self = self,
let tableView = tableView,
let pth = tableView.indexPath(for: c)
else {
return
}
// update our data
self.myData[pth.row] = str
// do something with the tableView
// such as reload the first row (row Zero)
tableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
}
return c
}
}

Edit 2

There is a lot to discuss which goes beyond the scope of your question, but briefly...

First, as a general rule Classes should be as independent as possible.

  • your Cell should only handle its elements
  • your Data Source should only manage the data (and, of course, the necessary funds like returning cells, handling Edit commits, etc)
  • your TableViewController should, as might be expected, control the tableView

If you are only manipulating the data and wanting to reload specific rows, it's not that big of a deal for your DataSource class to get a reference to the tableView.

But, what if you need to do more than that? For example:

Sample Image

You don't want your Cell or DataSource class to act on the button tap and do something like pushing a new controller onto a nav stack.

To use the protocol / delegate pattern, you can "pass a delegate reference" through the classes.

Here's an example (with just minimum code)...

Two protocols - one for text change, one for button tap:

protocol MyTextChangeDelegate: AnyObject {
func cellTextChanged(_ cell: UITableViewCell)
}

protocol MyButtonTapDelegate: AnyObject {
func cellButtonTapped(_ cell: UITableViewCell)
}

The controller class, which conforms to MyButtonTapDelegate:

class TheTableViewController: UITableViewController, MyButtonTapDelegate {

lazy var myTableViewDataSource: TheTableViewDataSource = {
TheTableViewDataSource()
}()

override func viewDidLoad() {
super.viewDidLoad()
// assign custom delegate to dataSource instance
myTableViewDataSource.theButtonTapDelegate = self
tableView.dataSource = myTableViewDataSource
}

// delegate func
func cellButtonTapped(_ cell: UITableViewCell) {
// do something
}

}

The data source class, which conforms to MyTextChangeDelegate and has a reference to MyButtonTapDelegate to "pass to the cell":

final class TheTableViewDataSource: NSObject, UITableViewDataSource, MyTextChangeDelegate {

weak var theButtonTapDelegate: MyButtonTapDelegate?

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! theCell
// assign custom delegate to cell instance
c.theTextChangeDelegate = self
c.theButtonTapDelegate = self.theButtonTapDelegate
return c
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}

func cellTextChanged(_ cell: UITableViewCell) {
// update the data
}

}

and the Cell class, which will call the MyTextChangeDelegate (the data source class) on text change, and the MyButtonTapDelegate (the controller class) when the button is tapped:

final class theCell: UITableViewCell, UITextFieldDelegate {

weak var theTextChangeDelegate: MyTextChangeDelegate?
weak var theButtonTapDelegate: MyButtonTapDelegate?

func textFieldDidChangeSelection(_ textField: UITextField) {
theTextChangeDelegate?.cellTextChanged(self)
}
func buttonTapped() {
theButtonTapDelegate?.cellButtonTapped(self)
}

}

So, having said all that...

Speaking in the abstract can be difficult. For your specific implementation, you may be digging yourself into a hole.

You mention "how to use a containerView / segmented control to switch between controllers" ... It might be better to create a "data manager" class, rather than a "Data Source" class.

Also, with a little searching for Swift Closure vs Delegate you can find a lot of discussion stating that Closures are the preferred approach these days.

I put a project up on GitHub showing the two methods. The functionality is identical --- one approach uses Closures and the other uses Protocol/Delegate pattern. You can take a look and dig through the code (tried to keep it straight-forward) to see which would work better for you.

https://github.com/DonMag/DelegatesAndClosures

How to edit and pass back cell data with delegate and protocol in swift

You're setting delegate for case that segue's identifier is addNewThing, but what about case that identifier is showDetail?

Set delegate of segue's destination for case that segue's identifier is showDetail

if segue.identifier == "addNewThing" {
...
} else if segue.identifier == "showDetail" {
...
thingDetailViewController.delegate = self
...
}

Then when you need to dismiss ViewController embed in navigation controller, just dismiss it and then dismiss navigation controller

Passing data from Table view cell using button delegate

The problem is you don't update you're global properties when selecting each of you're row,

If you pass data over cell delegate and pass you're cell through delegate, you can pass data from cell like:

    customViewController?.titlemovie = cell.movieTitle.text ?? ""
customViewController?.imagemovie = cell.movieImage.image
customViewController?.overview = cell.movieOverview.text ?? ""

of course it would be better to pass you're data model to you're cell. and then share that through you're delegate not share you're cell, like:

protocol CellSubclassDelegate: AnyObject {
func buttonTapped(cell: MovieModel)
}

Access selected cell section in delegate function in swift

You need to send indexpath , the crash because you access the array model section with a row value that exceeds it

func addOnBtnTapped(tappedIndex : IndexPath)

//

extension RestaurantMenuDetailVC : ResMenuDetailDelegate{
func addOnBtnTapped(tappedIndex: IndexPath) {

print(tappedIndex)

let addonCategory = subCategoryModel![tappedIndex.section].items[tappedIndex.row].addonCategory
print(addonCategory as Any)
}

//

 @IBAction func addBtnTapped(_ sender: Any) {
delegate?.addOnBtnTapped(tappedIndex:self.myIndexPath)
}

//

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

if tableView == resMenuTableView{

let cell = resMenuTableView.dequeueReusableCell(withIdentifier: "detailMenuCell", for: indexPath) as! RestaurantMenuDetailTVC

cell.myIndexPath = indexPath
}

//

and declare that var in cell

var myIndexPath:IndexPath!

Delegate method not called from TableViewCell

As @vadian has commented you should assign target class to myDelegate

in cellForRowAt function assign target class to my delegate

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! CalenderTimeTableViewCell
cell.myDelegate = self
//Other
}

control the content of UITableView cell from another cell with 2 classes / delegates Swift

You can access the table view with this extension:

extension UITableViewCell {
var tableView: UITableView? {
return next as? UITableView
}
}

and you can access cell's indexPath with this extension:

extension UITableViewCell {
var indexPath: IndexPath? {
return tableView?.indexPath(for: self)
}
}

So you can calculate the player cell index and access it with tableView.cellForRow(at: indexPath).



Related Topics



Leave a reply



Submit