What's the purpose for making UIViewController a subclass of UIResponder? Was it done solely to pass the rotation events?
I could not find any definitive info on that in the docs.
Update
I understand that if something is made a UIResponder, this something is suppose to be included in the responder chain and process events. But I have two gnawing doubts.
As far as I know, UIViewController is put into the responder chain right after its view. Why do we need a view controller in the responder chain at all? Its view is already there, so why don't we let the view process the events that were not handled by its subviews?
OK, I'm ready to agree that we might need this. But I would like to see some real life examples, when processing events in a view controller is really needed and is the best/easiest/most appropriate way to do something.
I think your problem may simply be a failure of object oriented thinking.
Per the docs:
The responder chain is a linked series of responder objects to which
an event or action message is applied.
In UIKit the view controller sits in the responder chain between its view and the view to which the controller was pushed. So it is offered any event or action that its views don't handle.
The topmost view controller's next responder is the window, the window's next responder is the application, the application's next responder is the application delegate and the application delegate is where the buck stops.
Your question "Was it done solely to pass the rotation events?" applies the incorrect test; it implies that at some point the responder chain had otherwise been fully engineered and somebody thought 'oh, wait, what about rotation? Better chuck the view controllers into the chain'.
The original question will have been: is it helpful if events or actions can be handled by the view controller if none of the views handle them? The answer should obviously be 'yes' as — even on a touch-screen device — there will be events or actions that aren't inherently related to a view.
The most obvious examples are those related to physical inputs other than the screen. So device rotation is one. Key presses on a bluetooth keyboard are another. Remote controls are a third. The accelerometer is a fourth.
The next most obvious example is any system-generated events or actions that should go to the single most local actor rather than to everyone. In iOS that's generally requests for a more specific actor, like the most local undo manager or the identity of the input view to show if focus comes to you.
A slightly less obvious example is that exemplified by UIMenuController — a pop-up view that posts a user-input event that may need to traverse several view controllers to get to the one that should act on it. iOS 5's child view controllers increase the number of possibilities here enormously; quite often you're going to have one parent view controller with the logic to do a bunch of things and children that want to pass messages up to whomever knows how to handle them, without hard coding the hierarchy.
So, no, view controllers weren't added to to the responder chain just to handle rotation events. They were added because logically they belong to be there based on the initial definition of the responder chain.
This is going to sound like a glib answer, but it really isn't. UIViewController is a subclass of UIResponder so that is can respond to user actions (e.g. touches, motion, etc.).
If a view does not respond to an event it is passed up the responder chain giving higher level objects a chance to handle it. Hence, view controllers and the application class are all subclasses of UIResponder
You can find more detailed information about the responder chain in Cocoa Application Competencies for iOS: Responder Object on Apple's developer site.
UIViewController is in the responder chain to allow for it to process any event. There are more then just the events you think of (touches) that pass through this chain. Motion events get passed through the chain, touch events that a specific view doesn't handle, you can also force things through the responder chain using [UIApplication sendEvent:...] with a nil target.
The other thing you may notice is UIApplication is also a subclass of UIResponder. All events that aren't handled will end up there.
In MVC conception handling of event occurred in view should perform controller.
There is a feature, to set "nil" to "target" parameter of -[UIControl addTarget:action:forControlEvents:] in with case the responder chain is searched for an object willing to respond to the action.
That is, because of search option, UIViewController is subclass of UIResponder.
So, you can send your custom control events in same manner to get benefits of "search" option.
Related
I've been reading about Clean Architecture from Robert Martin and more specifically about VIPER.
Then I ran into this article/post Brigade’s Experience Using an MVC Alternative which describes pretty much what I'm currently doing.
After actually trying to implement VIPER on a new iOS project, I've ran into some questions:
Is it ok for the presenter to query information in the view or should the "information passing" always start from the view?
For example, if the view triggered some action in the presenter, but then, depending on the parameters passed through that action, the presenter might need more information.
What I mean is: the user tapped “doneWithState:”, if state == “something”, get information from the view to create an entity, if state == “something else”, animate something in the view. How should I handle this kind of scenario?
Lets say a "module" (group of VIPER components) decide to present another module modally. Who should be responsible for deciding if the second module will be presented modally, the first module's wireframe or the second module's wireframe?
Also, lets say the second module's view is pushed into a navigation controller, how should the "back" action be handled? Should I manually set a "back" button with an action in the second module's view controller, that calls the presenter, that calls the second module's wireframe that dismiss and tells the first module's wireframe that it was dismissed so that the first module's view controller might want to display something?
Should the different modules talk only through the wireframe or also via delegates between presenters? For example if the app navigated to a different module, but after that the user pressed "cancel" or "save" and that choice needs to go back and change something in the first module (maybe display an animation that it was saved or remove something).
Lets say a pin was selected on a map, than the PinEditViewController is displayed. When going back, the selected pin's color might need to change depending on use actions on the PinEditViewController. Who should keep the state of the current selected pin, the MapViewController, the MapPresenter or the MapWireframe in order for me to know, when going back, which pin should change color?
1. May the Presenter query information from the view
To answer this to your satisfaction, we need more details about the particular case. Why can't the view provide more context information directly upon callback?
I suggest you pass the Presenter a Command object so the Presenter doesn't have to know what to do in which case. The Presenter can execute the object's method, passing in some information on its own if needed, without knowing anything about the view's state (and thus introducing high coupling to it).
View is in a state you call x (opposed to y and z). It knows about its state anyway.
User finishes the action. View informs its delegate (Presenter) about being finished. Because it is so involved, it constructs a Data Transfer Object to hold all usual information. One of this DTO's attributes is a id<FollowUpCommand> followUpCommand. View creates a XFollowUpCommand (opposed to YFollowUpCommand and ZFollowUpCommand) and sets its parameters accordingly, then putting it into the DTO.
Presenter receives the method call. It does something with the data no matter what concrete FollowUpCommand is there. Then it executes the protocol's only method, followUpCommand.followUp. The concrete implementation will know what to do.
If you have to do a switch-case/if-else on some property, most of the time it'd help to model the options as objects inheriting from a common protocol and pass the objects instead of the state.
2. Modal Module
Should the presenting module or the presented module decide if it's modal? -- The presented module (the second one) should decide as long as it's designed to be used modally only. Put knowledge about a thing in the thing itself. If its presentation mode depends on the context, well, then the module itself can't decide.
The second module's wireframe will receive message like this:
[secondWireframe presentYourStuffIn:self.viewController]
The parameter is the object for which presentation should take place. You may pass along a asModal parameter, too, if the module is designed to be used in both ways. If there's only one way to do it, put this information into the affected module (the one presented) itself.
It will then do something like:
- (void)presentYourStuffIn:(UIViewController)viewController {
// set up module2ViewController
[self.presenter configureUserInterfaceForPresentation:module2ViewController];
// Assuming the modal transition is set up in your Storyboard
[viewController presentViewController:module2ViewController animated:YES completion:nil];
self.presentingViewController = viewController;
}
If you use Storyboard Segues, you'll have to do things a bit differently.
3. Navigation hierarchy
Also, lets say the second module's view is pushed into a navigation controller, how should the "back" action be handled?
If you go "all VIPER", yes, you have to get from the view to its wireframe and route to another wireframe.
To pass data back from the presented module ("Second") to the presenting module ("First"), add SecondDelegate and implement it in FirstPresenter. Before the presented module pops, it sends a message to SecondDelegate to notify about the outcome.
"Don't fight the framework", though. Maybe you can leverage some of the navigation controller niceties by sacrificing VIPER pure-ness. Segues are a step into the direction of a routing mechanism already. Look at VTDAddWireframe for UIViewControllerTransitioningDelegate methods in a wireframe which introduce custom animations. Maybe this is of help:
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
return [[VTDAddDismissalTransition alloc] init];
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented
presentingController:(UIViewController *)presenting
sourceController:(UIViewController *)source
{
return [[VTDAddPresentationTransition alloc] init];
}
I first thought that you'd need to keep a stack of wireframes similar to the navigation stack, and that all "active" module's wireframes are linked to one another. But this isn't the case. The wireframes manage the module's contents, but the navigation stack is the only stack in place representing which view controller is visible.
4. Message flows
Should the different modules talk only through the wireframe or also via delegates between presenters?
If you directly send another module B's object a message from Presenter A, what should happen then?
Since the receiver's view is not visible, an animation cannot start, for example. The Presenter still has to wait for the Wireframe/Router. So it has to enqueue the animation until it becomes active again. This makes the Presenter more stateful, which makes it harder to work with.
Architecture-wise, think about the role the modules play. In Ports/Adapters architecture, from which Clean Architecture burrows some concepts, the problem is more evident. As an analogy: a computer has many ports. The USB port cannot communicate with the LAN port. Every flow of information has to be routed through the core.
What's at the core of your app?
Do you have a Domain Model? Do you have a set of services which are queried from various modules? VIPER modules center around the view. The stuff modules share, like data access mechanisms, don't belong to a particular module. That's what you may call the core. There, you should perform data changes. If another module becomes visible, it pulls in the changed data.
For mere animation purposes, though, let the router know what to do and issue a command to the Presenter depending on the module change.
In VIPER Todo sample code:
The "List" is the root view.
An "Add" view is presented on top of the list view.
ListPresenter implements AddModuleDelegate. If the "Add" module is finished, ListPresenter will know, not its wireframe because the view is already in the navigation stack.
5. Keeping state
Who should keep the state of the current selected pin, the MapViewController, the MapPresenter or the MapWireframe in order for me to know, when going back, which pin should change color?
None. Avoid statefulness in your view module services to reduce cost of maintaining your code. Instead, try to figure out whether you could pass a representation of the pin changes around during changes.
Try to reach for the Entities to obtain state (through Presenter and Interactor and whatnot).
This doesn't mean that you create a Pin object in your view layer, pass it from view controller to view controller, change its properties, and then send it back to reflect changes. Would a NSDictionary with serialized changes do? You can put the new color in there and send it from the PinEditViewController back to its Presenter which issues a change in the MapViewController.
Now I cheated: MapViewController needs to have state. It needs to know all pins. Then I suggested you pass a change dictionary around so MapViewController knows what to do.
But how do you identify the affected pin?
Every pin might have its own ID. Maybe this ID is just its location on the map. Maybe it's its index in a pin array. You need some kind of identifier in any case. Or you create an identifiable wrapper object which holds on to a pin itself for the duration of the operation. (That sounds too ridiculous for the purpose of changing the color, though.)
Sending Events to Change State
VIPER is very Service-based. There are lots of mostly stateless objects tied together to pass messages along and transform data. In the post by Brigade Engineering, a data-centric approach is shown, too.
Entities are in a rather thin layer. On the opposite of the spectrum I have in mind lies a Domain Model. This pattern isn't necessary for every app. Modeling the core of your app in a similar fashion may be beneficial to answer some of your questions, though.
As opposed to Entities as data containers into which everyone might reach through "data managers", a Domain protects its Entities. A Domain will inform about changes proactively, too. (Through NSNotificationCenter, for starters. Less so through command-like direct message calls.)
Now this might be suitable for your Pin case, too:
PinEditViewController changes the pin color. This is a change in a UI component.
The UI component change corresponds to a change in your underlying model. You perform the changes through the VIPER module stack. (Do you persist the colors? If not, the Pin Entity is always short-lived, but it's still an Entity because its identity matters, not just its values.)
The corresponding Pin has changed color and publishes a notification through NSNotificationCenter.
By happenstance (that is, Pin doesn't know), some Interactor subscribes to these notifications and changes its view's appearance.
Although this might work for your case, too, I think tying the edit
This answer may be a bit unrelated, but I'm putting it here for reference. The site Clean Swift is an excellent implementation of Uncle Bob's "Clean Architecture" in swift. The owner calls it VIP (it still contains the "Entities" and the Router/wireframe though).
The site gives you XCode templates. So let's say you want to create a new scene (he calls a VIPER modules, "scenes"), All you do is File->new->sceneTemplate.
This template creates a batch of 7 files containing all the headache of the boilerplate code for your project. It also configures them so that they work out of the box. The site gives a pretty thorough explanation of how every thing fits together.
With all the boiler plate code out of the way, finding solutions the questions you asked above is a bit easier. Also, the templates allow for consistency across the board.
EDIT -> In regards to the comments below, here's an explanation as to why I support this approach -> http://stringerstheory.net/the-clean-er-architecture-for-ios-apps/
Also this one -> The Good, the bad, and the Ugly about VIPER in iOS
Most of your questions are answered on this post: https://www.ckl.io/blog/best-practices-viper-architecture (sample project included). I suggest you pay special attention to the tips for Modules initialization/presentation: it's up to the source Router to do it.
Regarding back buttons, you can use delegates to trigger this message to the desired module. This is how I do it and it works great (even after you insert push notifications).
And yes, modules can definitely talk to each other by using delegates as well. It's a must for more complex projects.
I have created a custom class for my UIBarButtonItem (refreshIndicator.m). This button will be on many different view controllers, all push-segued from my MainViewController/NavigationController.
Instead of dragging an outlet onto every single ViewController.m file for iPhone storyboard THEN iPad storyboard (ugh, still targeting iOS7), I want to know if there is a way to complete my task simply within my UIBarButtonItem custom class. I've looked around everywhere but I haven't quite found an answer to this,
All I need to do is check which UIViewController is present, check the last time the page was refreshed, and then based on that time, set an image for the UIBarButtonItem. (I've got this part figured out though, unless someone has a better suggestion). How can I check for the current UIViewController within a custom button class? Is this possible?
Does it need to know which view controller its on so it can tell that vc it was pressed? If that's the case, then use your button's inherited target and action properties. On every vc that contains an instance of the button, in view did load:
self.myRefreshIndicator.target = self;
self.myRefreshIndicator.action = #selector(myRefreshIndicatorTapped:);
- (void)myRefreshIndicatorTapped:(id)sender {
// do whatever
}
More generally, its better to have knowledge about the model flow to the views from the vc, and knowledge of user actions flow from the views. Under that principal, your custom button could have a method like:
- (void)timeIntervalSinceLastRefresh:(NSTimeInterval)seconds {
// change how I look based on how many seconds are passed
}
And your vcs:
NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:self.lastRefreshDate];
[self.myRefreshIndicator timeIntervalSinceLastRefresh:interval];
If you really must go from a subview to a view controller, you could follow the responder chain as suggested in a few of the answers here (but I would go to great lengths to avoid this sort of thing).
It is possible to achieve this, but the solution is everything but elegant. It is one way of getting around the basic principles of iOS and is strongly discouraged.
One of the ways is to walk through the responder chain, posted by Phil M.
Another way is to look through all subviews of view controllers until you find the button.
Both ways are considered a bad practice and should be avoided.
For your particular case, I would rethink the structure of having a separate instance of the bar button. For example, you could rework it into a single UIButton instance that gets displayed over every view controller and it can also act as a singleton.
I am building a music player app -- in this app, the music player view controller will always sit on top of any other sub-viewcontroller (navigation view, table views, etc) I need actions taken in any potential subview to be sent back up to the player view controller (for example, user selects "play" on a profile page, and I send that event back up) My question is what is the best way to do this? I apologize in advance for being a bit nebulous, but I already know of three ways I can implement it. I just want to know what the "right" way is.
These are the three ways I've thought of:
1.Delegate pattern -- pass the Music Player Controller off to it's children controllers and set itself as the delegate for whenever that event is passed (messy because the first view controller is a navigation view controller, so I think I'd have to pass it down several levels meaning several delegates (correct me if I'm wrong))
2.Notification Center -- register the player view controller for a particular notification, encapsulate the data that's sent from the other viewcontrollers so that I can perform my actions.
3.Singleton-like access of the player view controller - basically allow access to the player view controller from any view controller.
Any help is appreciate to steer me in the right direction. I can do it either of these ways, but as this is a "learning" app, would love to do it right.
IMHO there is no "right way". Frankly I was thinking of all three of them when I read the subject line only.
As you are asing for opinions... I would not recommend the singleton pattern here. This is just because view controllers could stack and by their nature be instanciated multiple times. In terms of maintainable code (readability by others) I'd say no to that approach.
The delegate pattern is fine. But as you say you would have to pass a reference to this view controller from one view controller to the next. Yes, a bit messy.
Well, you could store a reference to the delegate in some singleton. That is not the same as designing a view controller as singleton.
I'd say that the notification center is most appropriate for this type of data flow. Sender and receiver of the message are totally detatched and don't need to 'know each other'. It is a perfect pattern for all kinds of multiple senders and/or mulitple receipient type of messages.
Well, as I said, this is just an opinion.
I would not recommend broadcast notifications (via NSNotificationCenter), they are hard to debug, other developers will have problems to understand, whats going on in your application (application flow), every developer must maintain a global list with all notification names (notificationSender and observer must use the same notification name and they are usually constant string variables), observer can not send back any data after receiving a notification, etc. Broadcasting is really helpful, if all controllers should be notified of the same event (for example Login/Logout events).
If possible, i think one should allways try to use a Delegate Pattern (with clearly defined protocol). Maybe something like:
id <SubViewControllerEvents>musicPlayerVC= [MyMusicAppManager delegate];
if ( [musicPlayerVC respondsToSelector:#selector(userDidSelectPlay)] ) {
[musicPlayerVC userDidSelectPlay];
}
I have a viewController, in which I have a view in which I draw like in Paint by tapping. In that controller I have a method to clear screen -
- (void) clearS
{
[(TouchTrackerView *)self.view clear];
}
Now, as I don't want to occupy my screen with buttons, I have button in another screen that resets application to starting position, and I want it to clear screen. That button belongs to different view controller, and among other things I want it to call my drawing view controller and ask it to wipe screen clear. So I have setup notifications, like this in draw view contoller:
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(clearS) name:#"clearScreen" object: nil];
And like this in my view controller from where I click a button:
[[NSNotificationCenter defaultCenter] postNotificationName:#"clearScreen" object: nil];
As I understand, when the button gets clicked, notification gets posted, and method is called. And it crashes.
[(TouchTrackerView *)self.view clear]
As I understand the problem is that at the moment of method invocation the "self" variable is my non-drawing view controller, and it tries to perform clear method on view of itself, which it lacks and crashes.
How do I send the method to correct view controller?
Is there a flaw in my thinking? Maybe this can be approached in a better way?
EDIT:
I have found the problem,
[(TouchTrackerView *)self.view clear]
calls view and it is UIScrollView, and it does not support clear method. So I have made a property containing the correct view, and changed it to
[(TouchTrackerView *)self.correctView clear]
and it works like a charm.
I have chosen notification because it is only two lines of code, and I am beginner and it is hard for me to wrap my head around delegates, so I will leave this as it is, especially that it works.
At the philosophical level, while I'm sympathetic to the observations from others that you could replace the notification with a delegate pattern (or, better, delegate protocol pattern), it strikes me that this is not an appropriate example of having two controllers communicating directly to each other at all. You probably should be employing a MVC model, where the editing view controller, A, is designed for the editing of a drawing (and thus updates the model and coordinates the view) and the reset view controller, B, should just update/reset the model. If A needs to be informed of model changes, then apply a delegate-protocol pattern there, between the model and controller A. But I don't think B should be communicating with A at all.
At a practical level, there's absolutely no reason why notifications shouldn't work just fine. Unfortunately, you haven't shared enough for us to answer the question. You should share the specifics of the error message and we might be able to help you more.
I think that notifications, which are in nature a one-to-many method of communication are not good to use here. A better approach would be to hook one as the delegate of the other -> once the button is pressed and the corresponding IBAction is invoked, the delegate (in your case, the view controller you use for drawing) should get a message and perform whatever it is it needs to do.
I would avoid notifications unless you are trying to broadcast out some info that multiple objects may be interested in.
A better approach might be to create a delegate protocol for the painting view controller --
So the PaintingViewControllerDelegate Protocol may have methods like so
(void) paintingViewControllerWillClear:(PaintingViewController*)paintingViewController;
(void) paintingViewControllerDidClear:(PaintingViewController*)paintingViewController;
Now the controller with the buttons becomes the delegate to the PaintingViewController and that object provides the methods of the PaintingViewControllerDelegate protocol as needed.
Lets say I have view1 which is intercepting touch events and view2 which is not.
Can view1 pass those events to view2 and call [view2 touchesBegin] [view2 touchesMoved]...etc?
Yes, sometimes, maybe. The technique you're asking about is known as event forwarding. I'll refer you to the Forwarding Touch Events section of the Event Handling Guide for iOS, which says the following:
The classes of the UIKit framework are not designed to receive touches
that are not bound to them; in programmatic terms, this means that the
view property of the UITouch object must hold a reference to the
framework object in order for the touch to be handled. If you want to
conditionally forward touches to other responders in your application,
all of these responders should be instances of your own subclasses of
UIView.
So, if you're looking to forward events from one view to another and both views are instances of your own UIView subclass, event forwarding may work for you. If your view2 is an instance of a UIKit class, though -- say, UIScrollView or UITextView, you shouldn't be surprised to encounter problems. And even if it works now, it could easily break in the future. A little further on in the text, that section states this more simply:
Do not forward events to UIKit framework objects.
I'd urge you to read the entire section -- there's some good advice and an example that may help if you do decide to use event forwarding.