Protocol/delegate in Swift3 doesn't work - ios

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.

Related

How does iOS call delegates and datasource methods?

For example, when we are creating tableview we need some datasource methods like
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messageArray.count
}
I don't call this anywhere. However, iOS does this instead of me and I wonder how iOS does this?
iOS search for a tableview, if it is available on the view then call delegates and datasource methods or it called when we declare uiTableView.delegate = self or uiTableView.datasource = self.
Another is these methods called before viewDidLoad?
Generally speaking, this is the setup for a class with a delegate:
class SimpleTableView {
var delegate: SimpleTableViewDelegate?
// ...
func renderCell(at row: Int) { // called whenever the table needs to render a cell
let cell = SimpleTableViewCell()
cell.frame.size.height = delegate?.tableView(self, cellHeightForRow: row)
// continue rendering cell
}
}
The protocol SimpleTableViewDelegate contains the delegate methods. It would look something like this:
protocol SimpleTableViewDelegate {
func tableView(_ tableView: SimpleTableView, cellHeightForRow: Int) -> CGFloat
}
So what we have here is a class, SimpleableView, that gets data from somewhere (the view controller). This is how the delegate comes into play:
class ViewController: UIViewController, SimpleTableViewDelegate {
var tableView = SimpleTableView()
override func viewDidLoad() {
tableView.delegate = self
}
func tableView(_ tableView: SimpleTableView, cellHeightForRow: Int) -> CGFloat {
return 44
}
}
This is essentially how a delegate works, and that is what the real tableView is doing. You set the tableView delegate and tableView calls the delegate methods to get information from you.
Hopefully this helps explain to you how the delegate works here, what calls it, and what's going on in general. If you need clarification, don't hesitate to ask!
So a quick way to look at this is separate these two things.
First lets look at delegate
https://developer.apple.com/documentation/uikit/uitableviewdelegate
The delegate provides a set of methods that you can include in your code that provides a callback for uitableview to execute certain protocol defined methods depending on whats happening within the tableView.
example func tableView(UITableView, heightForRowAt: IndexPath)
This example allows uitableview to ask you how should i display a certain cell at this current indexpath.
Next lets look at the datasource
https://developer.apple.com/documentation/uikit/uitableviewdatasource
Datasource works in a similar fashion as the delegate but provides a different set of methods that help you populate your table view.
example func numberOfSections(in: UITableView)
Apple's uitableview will call this method and ask the tableview how many sections should I display.
Ultimately, these are just protocols that allow the tableview to interact with your code and helping you display your table with your configuration!
tableView:numberOfRowsInSection is method of the UITableViewDatasource protocol. The methods of data source are called by method reloadData() of UITableView.
According to documentation of UITableView:
UITableView overrides the layoutSubviews() method of UIView so that it
calls reloadData() only when you create a new instance of UITableView
or when you assign a new data source. Reloading the table view clears
current state, including the current selection. However, if you
explicitly call reloadData(), it clears this state and any subsequent
direct or indirect call to layoutSubviews() does not trigger a reload.

Delegate Method to UItableViewCell Swift

I have a Social Network Feed in form UItableView which has a cell. Now each cell has an image that animates when an even is triggered. Now, This event is in form of a string, will be triggered at every cell. the options for the event are defined in another class(of type NSObject).
My issue:
I constructed a protocol delegate method in table view, which will be called whenever the event is triggered for each cell. Then, I define this function in UITableViewCell Class, since my the image will be animating on that.
All is working well but I am unable to figure out how to assign the delegate of TableView class to cell class. What I mean is, how can I use UITableView.delegate = self in cellView class. I have tried using a static variable, but it doesn't work.
I have been playing around the protocols for a while now but really unable to figure out a solution to this.
I hope I am clear. If not, I will provide with an example in the comments. I am sorry, This is a confidential project and I cant reveal all details.
If I understand you correctly, you are trying to make each of your cells conform to a protocol that belongs to their UITableView? If this is the case then this cannot be done. The Delegation design pattern is a one to one relationship, i.e only one of your UITableViewCells would be able to conform to the UITableView's delegate.
Delegation is a simple and powerful pattern in which one object in a program acts on behalf of, or in coordination with, another object. The delegating object keeps a reference to the other object—the delegate—and at the appropriate time sends a message to it. The message informs the delegate of an event that the delegating object is about to handle or has just handled. The delegate may respond to the message by updating the appearance or state of itself or other objects in the application, and in some cases it can return a value that affects how an impending event is handled. The main value of delegation is that it allows you to easily customize the behavior of several objects in one central object.
Quote from the Apple Docs
I would suggest that your UITableViewCell should call a block (Objective-C) or a closure (Swift) whenever your specified event is triggered to achieve what you are looking for. Set up this closure in your tableView:cellForRowAtIndexPath function.
EXAMPLE
TableViewController
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "MyTableViewCellID", for: indexPath) as! MyTableViewCell
cell.eventClosure = {
//Do something once the event has been triggered.
}
return cell
}
TableViewCell
func eventTriggered()
{
//Call the closure now we have a triggered event.
eventClosure()
}
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(...)
}
}
}
Try something like this:
Define your delegate protocol:
protocol CustomCellDelegate: class {
func animationStarted()
func animationFinished()
}
Define your CustomCell. Extremely important to define a weak delegate reference, so your classes won't retain each other.
class CustomCell: UITableViewCell {
// Don't unwrap in case the cell is enqueued!
weak var delegate: CustomCellDelegate?
/* Some initialization of the cell */
func performAnimation() {
delegate?.animationStarted()
UIView.animate(withDuration: 0.5, animations: {
/* Do some cool animation */
}) { finished in
self.delegate?.animationFinished()
}
}
}
Define your view controller. assign delegate inside tableView:cellForRowAt.
class ViewController: UITableViewDelegate, UITableViewDataSource {
/* Some view controller customization */
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: CustomCell.self)) as? CustomCell
cell.delegate = self
cell.performAnimation()
return cell
}
}

Passing data in reverse from viewcontroller to uitableviewcell [duplicate]

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)

What is the best design solution for this situation in iOS?

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.

didSelectViewController not getting called when in "More" section

I have a UITabBarController and I have set up its delegate method didSelectViewController, as I am interested in the index of the tab that is being selected.
However, I noticed that the didSelectViewController method doesn't get called when the user is in the "More" section (when there are more tabs than can be shown in the tabbar):
Is there a way for me to get notified of the items the user selects from the table that is being automatically created?
I found what I needed in this question.
Basically you set up a UITabBarControllerDelegate and a UINavigationControllerDelegate for the navigation controller that is displayed inside the More tab. After that you detect if the user touched one of the visible tabs, or the "More" tab.
EDIT
Also, to directly manipulate the table that is visible within the "More" navigation controller, you can set up a "man-in-the-middle" table view delegate, that intercepts the calls to the original delegate. See code from inside didSelectViewController below:
if (viewController == tabBarController.moreNavigationController && tabBarController.moreNavigationController.delegate == nil) {
// here we replace the "More" tab table delegate with our own implementation
// this allows us to replace viewControllers seamlessly
UITableView *view = (UITableView *)self.tabBarController.moreNavigationController.topViewController.view;
self.originalDelegate = view.delegate;
view.delegate = self;
}
After that, you are free to do whatever you like inside the delegate methods, as long as you call the same methods in the other delegate (I actually checked to which methods the original delegate responds, and the only delegate method that is implemented is the didSelectRow:forIndexPath:). See an example below:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// this is the delegate for the "More" tab table
// it intercepts any touches and replaces the selected view controller if needed
// then, it calls the original delegate to preserve the behavior of the "More" tab
// do whatever here
// and call the original delegate afterwards
[self.originalDelegate tableView: tableView didSelectRowAtIndexPath: indexPath];
}
Previous answer is almost correct because it misses one method to work properly.
class MyClass: ... {
var originalTableDelegate: UITableViewDelegate?
}
extension MyClass: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
if viewController == tabBarController.moreNavigationController && originalTableDelegate == nil {
if let moreTableView = tabBarController.moreNavigationController.topViewController?.view as? UITableView {
originalTableDelegate = moreTableView.delegate
moreTableView.delegate = self
}
}
}
}
extension MyClass: UITableViewDelegate {
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
originalTableDelegate!.tableView!(tableView, willDisplay: cell, forRowAt: indexPath)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("intercepted")
originalTableDelegate?.tableView!(tableView, didSelectRowAt: indexPath)
}
}
The original table delegate on more controller is actually system hidden class UIMoreListController. If we take a look into its implementation we will notice these two overrided functions: didSelect and willDisplay.
NOTE:
There could be a potential problem with this delegate interception if Apple decide to implement some other delegate method in its own UIMoreListController in future iOS versions.

Resources