I'm trying to learn Swift and I'm trying to develop the famous note application.
There is an array bound to a tableview and another view for adding notes.
At second view textfieldshouldreturn event triggers a segue and goes back to tableview.
I wanted to learn if this is the right way. Because by doing this way I'm manipulating a variable in another view controller. I'm not a MVC master but I felt like it is wrong. Here is my code snippet:
func textFieldShouldReturn(textField: UITextField) -> Bool {
self.performSegueWithIdentifier("backSegue", sender: self)
return true
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "backSegue"){
let navController = segue.destinationViewController as UINavigationController;
let myController = navController.topViewController as NotesTableViewController;
if(self.ourTextField?.text != nil || self.ourTextField?.text != ""){
myController.notes.append(self.ourTextField?.text ?? "");
}
}
}
Thank you.
Your question is not really about prepareForSegue but the relationship between view controllers. The reason that your design "feels wrong" is that it is. The problem is that your note writing view controller knows too much about the view controller that is using it because it is directly manipulating a variable from the calling view controller. In order to directly manipulate the variable, it must know the class of the caller.
Why is this a problem? It makes your note writing view controller less reusable. If you write the note writing view controller correctly, then you could reuse it in other apps. To make it reusable, you need to decouple the note writing view controller from the caller - it must not know who exactly is calling it.
So the question becomes, how do I pass data back to the caller if I don't know who called me? The answer is delegation.
Delegation works like this:
You create a protocol which describes a method or methods that the implementor of that protocol will implement. In your case, you could use a protocol like NoteWriterDelegate that implements the method takeNote(note: String).
protocol NoteWriterDelegate {
func takeNote(note: String)
}
Define this in the file along with your note writing view controller.
Your note writer will have an optional pointer to the delegate:
weak var delegate: NoteWriterDelegate?
You need to declare your first view controller as a NoteWriterDelegate:
class ViewController: UITableViewController, NoteWriterDelegate
And then implement the required method in your first view controller:
func takeNote(note: String) {
notes.append(note)
}
When you call prepareForSegue in preparation for moving to the note writing view controller, you pass yourself as the delegate:
destinationViewController.delegate = self
In the note writing view controller, when you have a note to pass back to the caller, you call takeNote on the delegate:
delegate?.takeNote(self.ourTextField?.text ?? "")
By doing it this way, your note writer only knows that it is talking to a NoteWriterDelegate. If you want to reuse this in the future, you just drop your note writer class into another project, implement the delegate, and it works without you having to touch the code in the note writer class.
I would recommend passing data via prepareForSegue in most cases. It's pretty simple to set up and easy to understand.
However, I would recommend never updating UI elements (labels, text fields, etc.) on the destination view directly. In my opinion, this is bad coupling that creates a lot of problems.
Instead, create a property or properties on the destination view controller that the caller can set in prepareForSegue to pass data to it. These should be special purpose properties used exclusively for passing data. The destination view controller is then in charge of using the data in these properties to update its UI or internal state.
Delegation is a valid approach, but I find it to be overkill for most situations. It requires more setup and is more abstract. This abstraction isn't needed in a lot of view controller relationships. If you discover you need to reuse a view controller, you can always refactor to use delegation later.
I do not believe that the prepareSegue is the ideal way for passing data between view controllers...at least not directly.
I share your concerns about using prepareForSegue to pass values between view controllers. The source view controller shouldn’t know anything about the destination view controller (and the other way around, for that matter). Ideally view controllers should be separate islands with no visibility into one another.
To address the coupling that storyboards seem to encourage, I’ve often used some form of the mediator pattern to pass data between view controllers. Here is a pretty good blog post on how to implement a version of this pattern around storyboards: http://coding.tabasoft.it/ios/mediator-pattern-in-swift/ . As always, this pattern may not be the best fit for all situations, but I feel it has been a good solution in a lot of my past projects.
Basically, how the mediator pattern would work within the storyboard paradigm is that in each view controller’s prepareForSegue method, the the segue object is passed to the mediator object. The view controller doesn’t care what’s inside or where the navigation is going next; it just knows it’s about to not be visible. The mediator, which has just been passed the segue object (containing the source and destination view controllers), is then responsible for passing data between the source and destination view controllers.
Using this pattern, each view controller is blissfully unaware of the existence of the other. The mediator class, on the other hand, must know about the relationships between the view controllers (and the view controllers' interfaces) in the navigation path. Obviously if the navigation changes, or the view controllers themselves change, the mediator class will need to adjust. Each view controller, however, need not have any dependence on each other, and therefore need not be updated to to accommodate changes in the navigation path or changes to the other view controllers along that navigation path.
It is not 'the' right way, but it is a right way. Especially in storyboard applications.
Here is an alternative way of passing value and calling the view.
var myNewVC = NewViewController()
myNewVC.data = self
navigationController?.presentViewController(myNewVC, animated: true, completion: nil)
Related
THere are 2 view controllers: master and detail view. They both have a presenter as I'm implementing the MVP pattern.
I need to update the data in the detail view controller.
I'm using this code in the master view controller.
detailVC.presenter?.set(data: presenter?.data[row])
I'm getting the data from the presenter of the master view controller and passing it to the presenter of the detailVC.
Is this good design?
If you create DetailVC before, you can use like that. Also u can use delegate patterns for that. MasterVC must have a delegate for DetailVC. Whenever you need to set your data you can use delegate.set(data: presenter?.data[row]) in your MasterVC class. But don't forget to set MasterVC delegate.
But if u want to create DetailVC and set parameters, u can use init method. Create an init function for your detailVC with the required parameters like that.. Write that function into your DetailVC class or create a DetailViewControllerInit class for your custom init functions with different parameters..
static func initDetailVC(data: DataType?) -> UIViewController {
let vc = UIStoryboard.... // create DetailVC here..
let presenter = viewController.presenter
presenter.set(data) // or presenter.data = data
return vc
}
After that u can create DetailVC in MasterVC like that
let detailVC = DetailVC.initDetailVC(data: presenter.data[row])
Though your implementation would certainly work, here are a couple of things to consider in order to improve it:
1) It hinges upon the master view controller knowing about a lot of different things (the detail view controller, the presenter, and the presenter's API). This can create coupling, which can make it more difficult to later refactor your code. Instead of calling detailVC.presenter?.someMethod(), I would consider adding a pass through method to detailVC that handles the calling of the presenter method (as well as anything else it needs to do at the same time) so that your architecture will be more modular and it will be easier to later swap out components.
2) I would consider decoupling the view and the model. Since MVP is really M<->P<->V in practice, ideally your model and view would not really communicate with or know about each other. Here, it seems like the view, or at least the object in which this line of code lives, knows about model when you call detailVC.presenter?.set(data: presenter?.data[row]). In order to do this decoupling, you could just have the view send an event / message prompting the presenter to do its thing, rather than manipulate the data directly. For example: detailVC.presenter?.newInputReceived(input: "hey!")
Hopefully that helps!
I pretty doubt that what is different between value by using prepare func
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "MainToTimer") {
let vc = segue.destination as! YourViewController
vc.var_name = "Your Data"
}
}
or declare global variable for example
in VC1
var justsimpleint:Int! = 0 //out side class
viewdidload(){
justsimpleint = justsimpleint + 2
}
in VC2
viewdidload(){
print(justsimpleint) // it will be 2
}
Using global variables to pass data between VCs is not a good idea. Global variables are things that should be used carefully. If you want to pass data from a View Controller A to View Controller B, which A presents, do it in the prepareForSegue method. If you want to pass data from View Controller B to View Controller A, which presents B, use the delegate pattern.
Global variables are bad for this purpose because:
They can be accessed from anywhere. This makes it easy for you to change them accidentally.
It is easier to spot errors. Say you have a global variable val that VC A and B will use. You pass some data from A to B. The first time A presented B, the data was passed successfully. The second time, the data was not sent due to some error in your logic. This would mean that B will receive the data you sent last time and everything will seem OK from the outside. If you pass the data in prepareForSegue, VC B will not get the data if no data was passed. A likely unexpectedly found nil while unwrapping optional error will occur. This makes it easy for you to tell what went wrong.
Global variables will hold references to objects, even when the VC that needs it is deallocated. This means that there will be some useless objects floating around in memory after the VC is dismissed if you don't clean it properly.
By default, assuming that you are working with storyboards, you should use the segues for passing data to the next view controller.
Global variable would be useful in case of you want it to be shared in the whole application, you could create a Singleton class for setting global properties.
Also, for passing back between view controllers, you could create a delegate to achieve it, you might want to check Passing data back from view controllers Xcode.
If you are unfamiliar with work with delegates, you might also want to check this answer.
Evening, my question is full about theory.
I understood reading from Apple developer documentation that is better to use the Delegates Pattern to keep track of some object attributes. In this way we can access the delegate without access to the object. (I really didn't get the reason of this choice)
I also understood that is better to define: protocolDelegate: class
and when we are declaring the delegate inside the class it's better to use the weak word to prevent some "kind of problem cycle". (??)
So, while I was playing a bit with code, I've discovered that you can't pass a weak delegate between two view controllers, because of course, when you change the controller, the weak delegate is going to be deleted because is a weak thing (or at least this is what I understood).
So, I have to choose between 2 options:
make the delegate "strong" deleting the weak key.
or pass the object in the segue and keep the delegate as weak.
I have a lot of confusion, can you clear my mind? :D
The cycle you're referring to is called a retain cycle.
Let's use a concrete example to clear this up: say you've got a UIViewController which has a UITableView. The view controller has a strong reference to the table view. The view controller now wants to act as the delegate to the table view.
Now, if the table view would have a strong reference to its delegate, we would have the following situation: the view controller has a strong reference to the table view, and the table view in turn would have a strong reference back to the view controller. Thus neither can ever get deallocated.
To break this cycle, references to delegates are usually weak. This allows the retain count of the view controller to drop to 0 eventually, which can in turn release the table view.
Your classes that want to use delegates should also follow this pattern and use weak references to their delegates. You should thus pass the required references via your segue.
I will concentrate on the first part of your question, since the previous answers have covered the rest pretty well.
Consider the following situation: you have a class that handles some kind of network connection - it sends a request to a server and gets a response. Outside of this class there is a viewController that has a button that triggers the request and a view which presents the response to the user.
Basically, the network handling class should be able to get some message from the viewController (button pressed) on one hand and pass the viewController the response on the other. So there should be bidirectional communication between the two classes. While the passing of the buttonPressed message to the network handling class is pretty obvious, the reverse part (passing the response) is a bit more tricky, because the network handling class should not be aware of who created it and who calls it (good OO practices and memory leaks prevention).
That's the point where the delegate pattern comes in. It allows an object to pass data to whoever is interested in it without knowing anything about the recipient. The class that passes the response only knows some 'delegate' and not another class. In addition you can take out the network handling class as is and put it in another project. Because it isn't supposed to know any other class from its original project, only some 'delegate', it can be put into another project without any modifications.
I hope it can help you to get the reason of the choice.
I think pass the object with segue, Segues are a very important part of using Storyboards in Xcode. We can go over the different types of seguesanother time, but this one shows you how to use the “Show” segue, as well as how to pass data between the two view controllers to customize the second one with whatever custom data is required.
You can easily use segues example; Under below you can send currentstring to destinationViewController inside sentstring , also ShowSegue is your segue identifier
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ShowSegue" {
if let destinationVC = segue.destinationViewController as? OtherViewController {
destinationVC.sentstring = currentstring
}
}
}
Navigation between viewcontrollers maintain stack of viewcontrollers.
For example aVC is firstviewcontroller then top of stack will be aVC,
now when you push or show another viewcontroller say bVC then now top of statck is bVC. So stack looks like,
aVC -> bVC(top)
now you push another cVC then,
aVC -> bVC -> cVC(top).
So top of stack is always visible to user.
at current situation, aVC and bVC and cVC are not deallocate. they are in memory. But if you pop or dismiss cVC, then it will deallocate from memory and now your top of stack looka like,
aVC -> bVC(top).
So viewcontrollers live in stack till they are not popped or removed. So, they are strog reference by default.
Segue is nothing but you can say that they are graphical representation of push or pop operation.
another thing is that delegate should be weak that because it can create retain cycle if they are strong.
you can called delegate as representative in general sense.
Now, if you are using segue, send your object in prepareForsegue and it will manage everything else.
Since iOS 6, unwind segues have been available to navigate up the scene hierarchy. I am trying to decide on the cleaner/better/preferred/more maintainable method for passing data to a parent view controller. There are some questions that address this from the technical perspective (e.g. "if I have an unwind do I still need a delegate") but I can't find much that addresses the questions of pros/cons.
Option 1: use a delegate.
Done by passing in the parent view controller as a delegate adhering to a protocol.
Child calls the protocol method to return the data.
If data validation is required by the Parent, return value/dict required to allow child to handle error.
Overhead: Protocol definition and one method in the parent (for data validation and receipt).
Option 2: use an unwind segue
Done by calling the unwind segue from the child.
Child adds a segue on its scene by dragging a button or the storyboard itself to Exit and naming the segue so it can be with performSegueWithIdentifier:sender
Parent implements returnFromSegueName (user named method linked to that segue) to grab the data from the child.
Data validation though can only be implemented by also implementing canPerformUnwindSegueAction:fromViewController:withSender
Data validation failure will require another property on the Child as this method only accepts a BOOL for return value.
Overhead: Two methods, an extra property, plus Storyboard shenanigans.
Overall, delegates are feeling like the cleaner way to go, but perhaps also antiquated. Am I wrong to be leaning in that direction?
I realize now that this isn't truly an answerable question other than to say that neither approach is wrong - they both have their pros and cons. After having tackled both for a week and done more reading on the subject I can at least quantify why you might want to use either an unwind segue or delegates for working between view controllers.
Coupling
Both models are roughly equally (loosely) coupled. Under the hood, an unwind segue is just a delegate where iOS has done the work of wiring it up for you. For delegates, the parent knows about and conforms to the child protocol. For unwind segues, the parent has to be wired to the child on the storyboard for unwind and needs to know the properties of the child to extract the return data. However, if you're new to delegates and just want to get some data out of a child view, unwind segues are probably less intimidating than using protocols with delegates.
Flexibility
Unwind segues are only a good choice if the sole purpose of the child-to-parent interaction is to return data. There does not appear to be a way to cancel an unwind segue in progress. So if the parent has to do any data validation, or if the child needs more than one interaction with the parent, the only way to do this is to have a delegate where multiple methods can be called back to the parent.
Maintainability
If the type or other aspects of the data being returned changes, it will be easier to update the unwind segue as all you have to do is to update the code in your unwind segue to look at the new properties. For the protocol/delegate approach, you will have to update the protocol in the child and the implementation in the parent. However, the simplicity of the unwind segue comes at the cost that you may easily miss places in parent view controllers that require updating because you don't have the compiler checking your contract (the protocol).
The Winner
There isn't one. Which way you go depends on your data needs, comfort level with protocols (they look more intimidating on first glance than they should), complexity of your application, and long term maintenance needs.
For my purposes, I wound up using delegates because my child had to make more than one call back to the parent in some cases. However, in a few instances where I had many pieces of data to pass back, I adopted what I learned from the unwind segue and simply used properties in the child from which the parent could extract the needed information. I also used this as a convenient path for the parent to provide error information to the child. I don't mix and match unwind segues with delegates in the program for consistency with a programming partner, but there's no reason you couldn't do that if you wanted to.
I was very skeptical of storyboards, but I decided to dive in and use them on a new project. I was amazed at the ease with which you can communicate between the two view controllers. When you perform a performSegueWithIdentifier you get a handle to the new ViewController. You can set any exposed properties you want in that new viewController very cleanly and nicely.
Here is an example:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"showDetail"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
Student *student = [self.students objectAtIndex:indexPath.row + [self rowAdjuster]];
[[segue destinationViewController] setStudent:student];
}
}
It is very nice and neat. No special protocol that you need to track or maintain.
And Then coming back (I have an IBAction connected to a button in my detail view) You can once again get a nice clean reference to the viewController to which you are returning, and act upon that viewController.
- (IBAction)returnWithStudent:(UIStoryboardSegue *)segue {
UIViewController *vc = [segue sourceViewController];
if ([vc isKindOfClass:[ AddStudentViewController class]]) {
AddStudentViewController *addViewController = (AddStudentViewController *)vc;
if (addViewController.student != nil) {
if ([addViewController hasTakenPhoto]) {
[PhotoHelpers saveImageForStudent:addViewController.student];
}
[StudentController updateStudent:addViewController.student];
}
}
}
Also the segue logic control is nice. One can perform logic checks in shouldPerformSegue which are quite handy.
I've seen lots of junky code that uses protocols of the "send something back to caller" that are really poor at coupling classes. It makes a three-way arrangement-- viewController1 -> protocol -> viewController2, whereas segues make a nice arrangement of viewController1->viewController2.
The segue is a nice way to cleanly and uniquely couple the two classes. I'd strongly recommend it.
I'm currently refactoring my app to be sure the it's MVC compliant.
I would like to split the controller (MyController which extends UIController) and the view (HomeView which extends UIView) I set the view in myController using
self.view = [[HomeView alloc] init];
When I push an UIButton, a method is called in the view, and in this method I would like to call a method from the controller.
In my view
[zenModeBtn addTarget:self action:#selector(touchZenMode:) forControlEvents:UIControlEventTouchDown];
...
- (void) touchZenMode:(id) sender {
[myController playZenMode];
}
But having a reference to the controller in the view is really a bad practice isn't it ?
EDIT :
So in my UIViewController I've made this :
- (id) init {
HomeView* myHomeView = [[HomeView alloc] init];
[myHomeView.arcadeModeBtn addTarget:self action:#selector(touchArcadeMode) forControlEvents:UIControlEventTouchUpInside];
self.view = myHomeView;
return self;
}
is that correct ?
The view talking to your controller is no problem, as outlined by some answers here. E.g. a text field can notify its controller via the defined delegate methods.
However, your design is still seriously flawed. Your view has absolutely no business handling a button press itself. Your intuition that the view should not know about its controller is correct.
Your controller should know about the button and how to react to it being tapped. That's why a controller has button IBOutlets to tell the button to e.g. change its title or enabled state. And it has button handlers to react to UI events. It is the controller's job to handle this logic. It is the view's job to display the title, gray out or send a tap event back to the controller.
The only code you should put into a view is basically how to draw itself. Everything that cannot be handled by a controller.
The basic idea of the MVC pattern, as used in Cocoa Touch:
As described here: The Model-View-Controller Design Pattern
What you want to achieve, is a form of loose-, even blind maybe, coupling. By using protocols (for delegation mechanism), a View only knows that there is an object that adopts a specific protocol, it can 'talk' to.
Take the UITableView for instance. It does not need to know that there is a certain type of UIViewController that helps it gather data, but only that there is an object that adopts the UITableViewDatasourceDelegate and/or UITableViewDelegate; that object can be of any type.
In your edit, you use the target-action mechanism, which is another way of achieving loose-coupling. You set up the connection at runtime; your View does not know your Controller. Therefor: correct, apart from the comment #Mundi made about your init implementation being incomplete.
The view needs some way to communicate things back to the controller, ask it questions about what to do next, etc. So it's perfectly fine for the view to know something about the controller.
Some of the built-in views, like UITextField, define protocols they use to tell their delegate about what's going on, or ask it to do something. You typically implement the protocol in your controller. That way the view doesn't really know much about the controller, just enough to communicate. That makes your view more generic and reusable.
What you want to avoid is for your view to have direct links to your model. The role of the controller is to mediate between the view and the model. You should be able to completely change how the view is implemented without touching the model, and vice-versa.
You can put that method in a protocol in your view's interface:
#protocol MyViewDelegateProtocol <NSObject>
-(void)myMethod;
#end
you put a new property of NSObject type called delegate in your view.
you make your view controller comply to that protocol and when it inits the view assign the delegate property to self.
you implement myMethod in your view controller implementation.
and now you just call [delegate myMethod] from your view whenever you need it.