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:
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
How to Fix Uilabel Text Spacing
Schedule Local Notification Every N Days (Timezone Safe)
Change Selected Row Label Color in Picker View Swift 1.2
Using Huffman Coding to Compress Images Taken by the iPhone Camera
Reversing a Range Results in Mismatching Types
Fbsdk (New Facebook Sdk 4.0) Implementation Is Not Working for Login with Facebook
How to Make Watchkit Extension App and My iPhone App Share the Same Icloud Databases
Swift How to Add Background Color Just Around Text in a Label
How to Position UI Elements on Background Image Relative to the Image
How to Make Playground Execution Time Is as Fast as If We Run in iOS Application
Getting Uitableview Error "Unable to Dequeue a Cell with Identifier Cell"
Add View as a Parameter to a Custom Viewmodifier
How to Load Thousands Records to Realm Correctly
How to Send Emails in Swift Using Mailgun
How to Change How a Remote Notification Is Presented Before Presentation
Didoutputsamplebuffer Delegate Not Called
Random Number from an Array Without Repeating the Same Number Twice in a Row