This question already has answers here:
Passing data between view controllers
(45 answers)
Closed 5 years ago.
I've got this problem, in the gif attached you can see it: if I tap on the row of UrgenzaViewController it gets back to Ho fumatoViewController, and what I need is that the Label in UITableViewCell "Urgenza" will be modified with the title of the row pressed in UrgenzaViewController. How to modify the label in the custom cell? Thanks everybody
In your Urgenza view controller create a delegate at the top of your file (above your class declaration, below the import statements) like this:
protocol UrgenzaDelegate: class {
func menuItemSelected(item: String)
}
Then inside your Urgenza class declaration create an instance of the delegate like this :
weak var delegate: UrgenzaDelegate?
Then inside didSelectRowAtIndexPath method I would call the delegate method like this:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let delegate = delegate {
delegate.menuItemSelected(item: dataSource[indexPath.row])
}
}
Replace 'dataSource' with whatever data source you are using to populate the cell labels.
Finally, in your initial view controller (Ho fumatoViewController) you need to conform to the delegate you just created. You can do this by making an extension like this :
extension fumatoViewController: UrgenzaDelegate {
func menuItemSelected(item: String) {
// Here is where you save the selected item to whatever data source you are using
tableView.reloadData()
}
}
And lastly, and very important!, wherever you are pushing the Urgenza view controller you must set yourself to its delegate property like so:
let vc = UrgenzaViewController()
vc.delegate = self // This is the important part!
self.present(vc, animated: true, completion: nil)
Related
This question already has answers here:
Passing data between view controllers
(45 answers)
Closed 2 years ago.
I have a UIViewController with a UIImageView and a UIButton. When a user presses the button, a new UITableViewController appears with different rows, like "Animals", "Nature" and others. Then the user selects a row in didSelectRowAt and I present another UIViewController with pictures of animals, nature etc. Then a user selects a picture and I want to dismiss my controllers to the first one and put selected image there.
I don't want to store the image in Core Data nor User Defaults or somewhere else. I want to pass this image back somehow. How can I do that?
Use the delegation approach to pass the selected image back.
protocol ImageInfoViewControllerDelegate: class {
func didSelectImage(_ image: UIImage)
}
final class ImageInfoViewController: UIViewController {
weak var delegate: ImageInfoViewControllerDelegate?
}
final class ListViewController: UIViewController, UITableViewDelegate, ImageInfoViewControllerDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let infoVC = ImageInfoViewController()
infoVC.delegate = self
present(infoVC, animated: true, completion: nil)
}
// MARK: - ImageInfoViewControllerDelegate
func didSelectImage(_ image: UIImage) {
// You have image here.
}
}
DeviceTableViewCell
I create a protocol on my custom DeviceTableViewCell
protocol DeviceTableViewCellDelegate : NSObjectProtocol {
func printMe(_ text : String)
}
I also declared my delegate in
weak var delegate: DeviceTableViewCellDelegate?
DevicesViewController
I had this
extension DevicesViewController: DeviceTableViewCellDelegate {
func printMe(_ text : String) {
let text = "Protocol & Delegate"
print("........")
print(text)
print("........")
}
}
I don't know how to trigger my print() statement.
How would one trigger it ?
Do I need to call my printMe() somewhere ?
Did I missing something here ?
First, do this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "YourCellIdentifier", for: indexPath) as! DeviceTableViewCell
cell.delegate = self
}
Then, you must call printMe from your cell to handle your action
You just declare how the delegate function working, you haven't called it yet.
Based on the context, you can decide when to call delegate.printMe(_ text : "Foo").
I suggest a simple example you could refer: passing data back to previous view controller from current view controller using delegate.
Let's take example to understand the concept. So as you already create delegate variable for your protocol.
weak var delegate: DeviceTableViewCellDelegate?
Now to call protocol method you need to assign your delegate to some viewController or class. Let's assign in same view controller in viewDidLoad method.
override func viewDidLoad(){
delegate = self
}
Now let's say need to call protocol method when some button pressed. So what you need to do is call this method like this in button press method.
delegate?.printMe("Button Pressed")
I have followed this tutorial to have delegate methods to update a value in my other class, but it does not even trigger it. Can you please tell me what i am doing wrong?
protocol myDelegate {
func report(info:String)
}
class TypeFilterViewController: UIViewController, XLFormRowDescriptorViewController,
UITableViewDataSource, UITableViewDelegate {
var delegate:myDelegate?
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.delegate?.report("testValue")
self.navigationController?.popViewControllerAnimated(true)
}
}
So, after i select the row item, i dismissed pushed view and display previous class.
class SearchRefinementsTypeCell: XLFormBaseCell, XLFormBaseCellSeparator, myDelegate {
// Delegate method
func report(info: String) {
print("delegate: \(info)")
}
override func update() {
super.update()
let mc = TypeFilterViewController()
mc.delegate = self
self.headerLabel.text = //Value from TypeFilterViewController didSelectRow
}
Thank you for all kind of helps.
You clearly misunderstood the tutorial.
Delegate pattern is useful when you want to delegate from a cell to view controller. You're doing the opposite: sending event from a viewController to a cell, which is pointless, since your viewController already has access to it's tableView, which in it's turn operates with it's cells.
Also you shouldn't use any ViewControllers inside cell class because it breaks MVC pattern. You should think of UITableViewCell and pretty much every UIView as of powerless objects which cannot decide anything by themselves, but can only delegate events to other smart guys, which do the logic by themselves (view controllers).
Now about your case:
You have vc A and vc B, pushed over it. When a cell in B is pressed, you should send a callback to A, right? What you should do:
B has a delegate which implements some protocol
When A pushes B, it set's itself as a protocol: b.delegate = self
When a cell is selected in B, you call delegate's method, which is implemented in A and passes a string into it.
UI in A is updated.
Once again, cells must not know anything about any of your view controllers, they are just pawns. All logic should be handled between view controllers themselves.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let storyboard = UIStoryboard(name: "FilteringPageSelection", bundle: nil)
controller = storyboard.instantiateViewControllerWithIdentifier("FilteringPageSelectionController") as! FilteringPageSelectionTableViewController
controller.filteringType = filterTitle[indexPath.row];
controller.selectedValue = selectedValue;
controller.title = "Selection"
self.navigationController?.pushViewController(controller, animated: true)
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
Above is my code, this is my table view controller code, i am intend to push to another view controller by clicking it cell. Inside the new controller, there is multiple value for me to choose. however , after i select the new value from the new controller, i back to the original table view scene and get the selected value from the new controller and assign into the variable selectedValue. Below is my code of the unwind segue.
#IBAction func unwindToFilteringVC(segue:UIStoryboardSegue) {
selectedValue = controller.selectedValue
}
However, when i click the cell again, since the tableview override function only initialize for the first time when the tableview is loaded, so when i change the value of the selectedValue, the controller.selectedValue still remain the old value and push into new controller.
How do i changes the value of controller.selectedValue?
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) - this function gets called every time you tap on a cell, not just once. So whenever you select a cell, your code instantiates the filter view controller and sets its selectedValue to the current selectedValue of the table view controller (unless that is a global variable). Maybe that is not what you want.
OR
You may have mixed which selectedValue is which. You should rename one.
I think what you need to use is a delegate pattern to inform the table view controller of the selected value in the selection view controller instead of holding a reference to it. First you define a protocol:
protocol SelectionViewControllerDelegate: class {
func selectionViewController(controller: SelectionViewController, didSelectValue value: ValueType)
}
You add a weak reference to an optional property that conforms to that protocol and you inform the delegate of changes in the selected value right where you handle the selection action in your selection view controller.
class SelectionViewController: UIViewController {
weak var delegate: SelectionViewControllerDelegate?
func handleChange() {
delegate?.selectionViewController(self, didSelectValue: selectedValue)
}
}
Then you make your table view controller conform to this protocol like so:
class TableViewController: UITableViewController, SelectionViewControllerDelegate {
func selectionViewController(controller: SelectionViewController, didSelectValue value: ValueType) {
self.selectedValue = value
}
}
Don't forget to set the table view controller as the delegate of the selection view controller in the didSelectRowAtIndexPath method by saying:
controller.delegate = self
Right before you call navigationController?.pushViewController method.
I have UITableView with two static cells. Each cell has custom class and independently validate account name, when I fill text field in the cell. (This part of code I got as is and I am not allowed to rewrite it). The cell delegates about changes if validation is correct to delegate (SocialFeedSelectCellDelegate). Originally, this tableView appeared in SignUpViewController: UITableViewController, UITableViewDataSource, UITableViewDelegate, SocialFeedSelectCellDelegate only.
Problem : The same UITableView should appear in two different places (SignUpViewController and SettingsViewController). Also SignUpViewController and SettingsViewController should know about success or fail of account validation.
What I tried : I created SocialFeedTableViewController: UITableViewController, SocialFeedSelectCellDelegate for the tableView with two cells. Set view in SocialFeedTableViewController as container view for SignUpViewController and SettingsViewController. I used second delegation (from SocialFeedTVC to SignUp and Settings) to notify SignUp and Settings about validation changes. I think it is bad idea, because of double delegation. Teammate said me that it is hard to understand.
Question: What is the best and simple design solution for the problem?
Why is the double delegation a problem? As far as I see it you have 2 table views, 1 for each controller. Then each controller sets the delegate to each of the table view as self. Even if not it is quite common to change the delegate of the object in runtime. It is also normal to have 2 delegate properties with the same protocol simply to be able to forward the message to 2 objects or more.
There are many alternatives as well. You may use the default notification center and be able to forward the messages this way. The only bad thing about it is you need to explicitly resign the notification listener from the notification center.
Another more interesting procedure in your case is creating a model (a class) that holds the data from the table view and also implements the protocol from the cells. The model should then be forwarded to the new view controller as a property. If the view controller still needs to refresh beyond the table view then the model should include another protocol for the view controller itself.
Take something like this for example:
protocol ModelProtocol: NSObjectProtocol {
func cellDidUpdateText(cell: DelegateSystem.Model.MyCell, text: String?)
}
class DelegateSystem {
class Model: NSObject, UITableViewDelegate, UITableViewDataSource, ModelProtocol {
// My custom cell class
class MyCell: UITableViewCell {
weak var modelDelegate: ModelProtocol?
var indexPath: NSIndexPath?
func onTextChanged(field: UITextField) { // just an example
modelDelegate?.cellDidUpdateText(self, text: field.text) // call the cell delegate
}
}
// some model values
var firstTextInput: String?
var secondTextInput: String?
// a delegate method from a custom protocol
func cellDidUpdateText(cell: DelegateSystem.Model.MyCell, text: String?) {
// update the appropriate text
if cell.indexPath?.row == 0 {
self.firstTextInput = text
} else {
self.secondTextInput = text
}
}
// table view data source
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = MyCell() // create custom cell
cell.indexPath = indexPath // We want to keep track of the cell index path
// assign from appropriate text
if cell.indexPath?.row == 0 {
cell.textLabel?.text = self.firstTextInput
} else {
cell.textLabel?.text = self.secondTextInput
}
cell.modelDelegate = self // set the delegate
return cell
}
}
// The first view controller class
class FirstViewController: UIViewController {
var tableView: UITableView? // most likely from storyboard
let model = Model() // generate the new model
override func viewDidLoad() {
super.viewDidLoad()
refresh() // refresh when first loaded
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
refresh() // Refresh each time the view appears. This will include when second view controller is popped
}
func refresh() {
if let tableView = self.tableView {
tableView.delegate = model // use the model as a delegate
tableView.dataSource = model // use the model as a data source
tableView.reloadData() // refresh the view
}
}
// probably from some button or keyboard done pressed
func presentSecondController() {
let controller = SecondViewController() // create the controller
controller.model = model // assign the same model
self.navigationController?.pushViewController(controller, animated: true) // push it
}
}
// The second view controller class
class SecondViewController: UIViewController {
var tableView: UITableView? // most likely from storyboard
var model: Model? // the model assigned from the previous view controller
override func viewDidLoad() {
super.viewDidLoad()
refresh() // refresh when first loaded
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
refresh() // Refresh each time the view appears. This will include when third view controller is popped
}
func refresh() {
if let tableView = self.tableView {
tableView.delegate = model // use the model as a delegate
tableView.dataSource = model // use the model as a data source
tableView.reloadData() // refresh the view
}
}
// from back button for instance
func goBack() {
self.navigationController?.popViewControllerAnimated(true)
}
}
}
Here the 2 view controllers will communicate with the same object which also implements the table view protocols. I do not suggest you to put all of this into a single file but as you can see both of the view controllers are extremely clean and the model takes over all the heavy work. The model may have another delegate which is then used by the view controllers themselves to forward additional info. The controllers should then "steal" the delegate slot from the model when view did appear.
I hope this helps you understand the delegates are not so one-dimensional and a lot can be done with them.