I have viewcontroller and it has split by two views. one is view which cotrolled by tableviewcontroller which is childcontroller of viewcontroller, and one is statusview which is show data directly when tableviewcontrollersdidselectrowatindexpathis called. but when i cant reload my data in statusview when i called didselectrowatindexpath. i can relaod my data in tableview but status view does not reflect data.
the darkgray area is statusview and middle view is tableview.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.tableView.reloadData()
}
by this code i can reload my data in tableview . but when i use setneedsDisplay or setneedslayout of parentController(viewcontroller) it crash. how can i solve this problem?
In this case you will need one of these two options:
Create a protocol that your main UIViewController will implement, so that UITableViewController can pass data via the delegate to its parent view controller
Post a UINotification in the UITableViewController and receive this notification in your status bar view and display the data.
Lets explore both options:
Define a protocol, in this example I am only sending a String of the cell that was tapped on:
#objc protocol YourDataProtocol {
func didSelectCell(withString string: String)
}
Next add a delegate property to your UITableViewController
class YourTableViewController: UIViewController {
weak var delegate: YourDataProtocol?
...
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
//call the delegate method with your text - in this case just text from textLabel
if let text = cell?.textLabel?.text {
delegate?.didSelectCell(withString: text)
}
}
}
Make your UIViewContoller be the delegate of the UITableViewController subclass:
class YourViewController: UIViewController, YourDataProtocol {
...
let yourTableVC = YourTableViewController(...
yourTableVC.delegate = self
func didSelectCell(withString string: String) {
statusBar.text = string//update the status bar
}
}
Second option is with using NotificationCenter
In your UITableViewController you post a notification
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
if let text = cell?.textLabel?.text {
let notificatioName = Notification.Name("DataFromTableViewCell")
NotificationCenter.default.post(name: notificatioName, object: nil, userInfo: ["YourData": text])
}
}
In status bar you start listening to this notification
NotificationCenter.default.addObserver(self, selector: #selector(didReceiveData(_:)), name: notificatioName, object: nil)
#objc func didReceiveData(_ notification: Notification) {
if let userData = notification.userInfo, let stringFromCell = userData["YourData"] {
print(stringFromCell)
}
}
Related
Hello the image above is the UI of my todo list app, now I just want to show the detail of item (First Item, second Item etc) when I click the detail button in the tableviewcell. So in order to get the property of the item, I need to know the indexPath of the row that I just clicked on the detail button.
I have tried some properties of the tableview like didSelectRowAt, or indexPathForSelectedRow, but both not work. For didSelectRowAt user need to click on the row first then click the detail button, and that's not what I want, and the indexPathForSelectedRow is not working for me.
A common, generalized solution for this type of problem is to connect the #IBAction of the button to a handler in the cell (not in the view controller), and then use a delegate-protocol pattern so the cell can tell the table when the button was tapped. The key is that when the cell does this, it will supply a reference to itself, which the view controller can then use to determine the appropriate indexPath (and thus the row).
For example:
Give your UITableViewCell subclass a protocol:
protocol CustomCellDelegate: class {
func cell(_ cell: CustomCell, didTap button: UIButton)
}
Hook up the #IBAction to the cell (not the view controller) and have that call the delegate method:
class CustomCell: UITableViewCell {
weak var delegate: CustomCellDelegate?
#IBOutlet weak var customLabel: UILabel!
func configure(text: String, delegate: CustomCellDelegate) {
customLabel.text = text
self.delegate = delegate
}
#IBAction func didTapButton(_ button: UIButton) {
delegate?.cell(self, didTap: button)
}
}
Obviously, when the cell is created, call the configure method, passing, amongst other things, a reference to itself as the delegate:
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { ... }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
let text = ...
cell.configure(text: text, delegate: self)
return cell
}
}
Finally, have the delegate method call indexPath(for:) to determine the index path for the cell in question:
extension ViewController: CustomCellDelegate {
func cell(_ cell: CustomCell, didTap button: UIButton) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
// use `indexPath.row` here
}
}
The other approach is to use closures, but again using the same general pattern of hooking the button #IBAction to the cell, but have it call a closure instead of the delegate method:
Define custom cell with closure that will be called when the button is tapped:
class CustomCell: UITableViewCell {
typealias ButtonHandler = (CustomCell) -> Void
var buttonHandler: ButtonHandler?
#IBOutlet weak var customLabel: UILabel!
func configure(text: String, buttonHandler: #escaping ButtonHandler) {
customLabel.text = text
self.buttonHandler = buttonHandler
}
#IBAction func didTapButton(_ button: UIButton) {
buttonHandler?(self)
}
}
When the table view data source creates the cell, supply a handler closure:
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { ... }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
let text = ...
cell.configure(text: text, buttonHandler: { [weak self] cell in // the `[weak self]` is only needed if this closure references `self` somewhere
guard let indexPath = tableView.indexPath(for: cell) else { return }
// use `indexPath` here
})
return cell
}
}
I personally prefer the delegate-protocol pattern, as it tends to scale more nicely, but both approaches work.
Note, in both examples, I studiously avoided saving the indexPath in the cell, itself (or worse, “tag” values). By doing this, it protects you from getting misaligned if rows are later inserted and deleted from the table.
By the way, I used fairly generic method/closure names. In a real app, you might give them more meaningful names, e.g., didTapInfoButton, didTapSaveButton, etc.) that clarifies the functional intent.
Implement the delegate method tableView(_:accessoryButtonTappedForRowWith:)
func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath)
However if you want to navigate to a different controller connect a segue to the accessory view button
If the button is a custom button see my answer in Issue Detecting Button cellForRowAt
I have an UITableView, in each cell it's have some label and a button. I want to get all label value when I click the button. How to do this? Thank you.
You can do it by closure or delegation
1: Closure
In your tableViewCell class create a variable like this
customObject is the object you passed the tableviewCell to load the data
var cellData: customObject? {
didSet {
// do your loding labels in here
}
}
var clickHandler: ((customObject) -> Void)!
and inside of you action button add this
#IBAction func replyAction(_ sender: Any) {
if let customObject = customObject {
clickHandler(customObject)
}
}
now go to where are you deque the table
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! YourCustomCell
// add this
cell.clickHandler = { customObject in
print("myCell.customObject = \(customObject)")
}
}
this will do the magic
2. Delegation
Create a delegate methode like this
protocol CustomCellDelegate {
func getCustomObject(in cell: CustomCell, withCustomObject object: CustomObject)
}
now in your cell class add delegate variable
var delegate: CustomCellDelegate?
and inside of you action button add this
#IBAction func replyAction(_ sender: Any) {
if let customObject = customObject {
delegate.getCustomObject(in: self, withCustomObject: customObject)
}
}
and now for the last part go to class you implemented the table view and this to where it shows
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! YourCustomCell
// add this
cell.delegate = self
}
and inside of class you should add you delegate method
extension YourClass: CustomCellDelegate {
getCustomObject(in cell: CustomCell, withCustomObject object: CustomObject) {
print("current cell data = \(CustomObject)"
}
}
this will do the job too
Hop this will Helps
Create IBAction method for the button inside the cell custom class and inside it print
print("label text : \(self.lbl.text)")
Or use delegate to send that value to the VC the contains the tableView
First of all. You should have a model object which you are using it to load the values of label. Use
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {}
to get the index then try getting the value from the model using the index
for example you have an array myArray then you could access the value using myArray[indexPath.row] to get the value. Then save, pass and use it where ever you want. Then implement a delegate method in your custom table cell class passing the indexPath. Then refresh the cell using tableView.reloadRows(at: [IndexPath(item:0,section:0)], with: .fade)
I would like to call reloadData method of TableView when i return in foreground. My TableView is built programmatically.
In FirstViewController i've the methods to populate the TableView and the function reloadListOfApps where i call reloadDatamethods.
class FirstViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, MFMailComposeViewControllerDelegate {
#IBOutlet weak var listOfApps: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
listOfApps.delegate = self
listOfApps.dataSource = self
}
func reloadListOfApps() {
listOfApps.reloadData()
}
// ================ TABLE DELEGATE/MANAGER ===================
let apps = ListAppClass.listApp()
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return apps!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let bundle = apps! [indexPath.row]
let bundle_temp = String(describing: bundle)
cell.textLabel?.text = bundle_temp
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Current Cell!")
}
}
In AppDelegate i've
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
FirstViewController().reloadListOfApps()
}
When i run the app i've this error
Fatal error: Unexpectedly found nil while unwrapping an Optional value
Reading some questions i've also check the Outlets in Storyboard, and seems all ok.
Hooks correspond to FirstViewController
Where is the error?
Try add oberver in your view controller
NotificationCenter.default.addObserver(self,
selector: #selector(applicationWillEnterForeground),
name: .UIApplicationWillEnterForeground,
object: nil)
callback
#objc func applicationWillEnterForeground() {
listOfApps.reloadData()
}
//// problem explaining
when you write this code in AppDelegate
FirstViewController().reloadListOfApps()
this creates an instance on the fly whose all properties are nil as you didn't load the storybaord object or Xib file associated with that is not presented away from the currently active one , control goes to reloadListOfApps function and find that listOfApps you want to reload is nil so crash happens , the solution above is one way , another way is to use delegate or make listOfApps a shared object that can be referenced anywhere
I'm using a custom cell of UITableViewCell in multiple tableview, and its working fine, but i want to know that, the custom cell is used by which tableview ?
Eg.
Suppose ViewControllerA has a tableView with custom cell, namely (ReuseIdentifier) cellA.
Also ViewControllerB has a tableView with custom cell, namely (ReuseIdentifier) cellB
but both cell has the same class.
Now, from a ViewController both the class has push by any button action.
now, how that custom cell will understand which tableView is used it ?
Is there any way ?
Any answer will be appreciated
Thanks
You could implement a delegate for each cell in tableView(_ tableView: UITableView, cellForRowAt indexPath: UIIndexPath). The same can be done in ViewController which will receive callbacks from all cells that you assigned instance of ViewController to delegate property.
See example below, and excuse my typos, written without IDE.
YourCell.swift
protocol YourCellDelegate {
func cellDidTapButton(_ cell: YourCell)
}
class YourCell {
var delegate: YourCellDelegate?
...
// Action linked to your cells button
#IBAction func didTapButton(_ sender: Any) {
delegate?.cellDidTapButton(_ cell: YourCell)
}
}
ViewControllerA.swift
class ViewControllerA: UITableViewDataSource {
var tableView: UITableView!
...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: UIIndexPath) {
if let cell = tableView.dequeueReusableCell(withIdentifier:"ReuseIdentifier", at: indexPath) as? YouCell {
// Do cell configuration
...
cell.delegate = self
}
}
}
extension UIViewControllerB: YourCellDelegate {
func cellDidTapButton(_ cell: YourCell) {
// Get indexPath of this cell
if let indexPath = self.tableView.indexPathForRow(at:cell.center) {
// Do action specific to this cell
}
}
}
I have a view controller containing a table view. In the table view are cells that have a button to delete the data the cell contains. When I press the delete button the cell information is removed from my database but the table view doesn't update until i segue back and forth.
I'm unsure how to access the TableView and force it to re-draw itself from a method inside my table cell.
class EditTaskTableViewCell: UITableViewCell {
//......//
#IBAction func deleteButtonPressed(_ sender: Any) {
let userDefaults = Foundation.UserDefaults.standard
if let userTasks = userDefaults.array(forKey: "userTasks"){
let newArray = removeTask(item: taskNameLabel.text!, from: userTasks as! [String])
userDefaults.set(newArray, forKey: "userTasks")
userDefaults.synchronize()
}
//update table view
}
}
class EditTaskTable_VC: UIViewController, UITableViewDelegate, UITableViewDataSource {
//......//
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = Bundle
.main
.loadNibNamed("EditTaskTableViewCell", owner: self, options: nil)?
.first as! EditTaskTableViewCell
cell.taskName.text = cellData[indexPath.row].title
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellData.count
}
Try to use delegates.
The example is in Objective C but the general idea is there.
sending reload data from custom tableview cell?
swift delegate example
https://gist.github.com/austinzheng/db58036c4eb825e63e88