There is already a question like this that was answered, however, I was not satisfied with the answer: Why does AppDelegate inherit from UIResponder?.
I'd like more specifics, as I am trying to understand the architecture of an iOS app.
As far as I know, UIResponder is for things that "respond" to external forces (i.e. touch events, motion events, etc.).
How come the initialization of an application needs to be a UIResponder? Maybe I'm not understanding what the instance of AppDelegate does.
The application delegate class inherits from UIResponder.
This is so that the delegate instance can participate in the responder chain and so handle application-level actions.
Edit:
As of iOS 5 there is a step 6: The app delegate gets the final word on
events. Beginning with iOS 5 the app delegate inherits from
UIResponder and no longer from NSObject as it did before.
In short: first the view, if the view has a view controller then that,
next the superview until the top of the hierarchy, the window. From
there to the application and finally to the app delegate.
The addition of the app delegate as potential responder is a welcome
addition since we rarely – if ever – subclass UIApplication or
UIWindow. But we always have our own app delegate if only to create
the window and add the rootViewController in the
application:didFinishLaunching… delegate method. So this happens to be
the de facto best place for a responder to fall back to if there is
none in the entire view hierarchy.
Taken from:
https://www.cocoanetics.com/2012/09/the-amazing-responder-chain/
Related
In iOS 13, the UIWindowSceneDelegate object is not in the responder chain (I verified this by printing the responder chain). But the template code Xcode provides make the scene delegate class inherits from UIResponder. If I make the scene delegate class inherits from NSObject, the code still compiles and runs without problem.
So what's the point of making scene delegate class conforms to UIResponder?
I noticed this too while having trouble with NSToolbarItem actions and after investigating with Hopper I figured out the reason is although UIWindow's nextResponder is UIWindowScene, it does not have a nextResponder override that forwards to its delegate (our SceneDelegate class that conforms to UIWindowSceneDelegate), it's superclass - UIScene's next is the UIApplication which has next the app delegate.
My guess is requiring a scene delegate conforming class to have as next responder the application syntactically is tricky, and even if it is achieved in ObjC it is likely even harder or impossible in Swift. Maybe Apple just decided it wasn't worth the hassle. With Catalyst, any NSToolbarItem for the window scene's toolbar can just have their target set to self - the scene delegate, rather than search the responder chain, even the system items can have their target changed during toolbarWillAddItem like in the sample. It would have been nice if they had at least documented somewhere a warning that the window scene delegate isn't in the responder chain, especially because as you say it is a subclass of UIResponder.
If you would like it to be in the chain then I created a workaround (see code below). First create a subclass of UIWindowScene with a nextResponder method returning self.delegate. Second, in the scene delegate add a nextResponder that returns UIApplication.sharedApplication (which will forward to the app delegate). Finally in the Scene Manifest (in Info.plist) add a row under the default configuration and choose Class Name from the drop down and enter your subclass's name.
I suppose this could be useful for an action that needs access to the window, because once an action reaches the app delegate it is harder to figure out which scene window it came from. However as I said, in this case what is the point in searching the chain at all.
MyWindowScene.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface MyWindowScene : UIWindowScene
#end
NS_ASSUME_NONNULL_END
MyWindowScene.m
#import "MyWindowScene.h"
#implementation MyWindowScene
- (UIResponder *)nextResponder{
return self.delegate;
}
#end
SceneDelegate.m
#implementation SceneDelegate
...
- (UIResponder *)nextResponder{
return UIApplication.sharedApplication;
}
I have a new iMessage Extension project where I tried 2 ways of structuring the navigation stack:
In my storyboard I set the entry point to a UINavigationController that has my MSMessagesAppViewController as the root controller.
Or I set my MSMessagesAppViewController directly as the Entry point in my storyboard. (No UINavigationController that owns it).
For scenario #1 above, the navigation controller works fine, and I can push new screens on the stack. (with the exception of the whole nav bar being hidden in Expanded view, which is a separate issue that I still have to figure out). However, NONE of the delegate methods of my MSMessagesAppViewController get called with this configuration. Such as:
willTransitionToPresentationStyle
didTransitionToPresentationStyle,
willBecomeActiveWithConversation,
didSelectMessage
(none of these get called)
For scenario #2 above, the MSMessagesAppViewController methods DO get called. (because the UINavigationController is not the entry point in the Storyboard).
So my question is: How can I have a UINavigationController be at the root of my iMessage Extension application, so I can perform Push navigation, but at the same time have the methods of MSMessagesAppViewController get called, as described by the Apple API?
Although it doesn't seem to be documented, it looks like message extensions expect the entry point to be a subclass of MSMessagesAppViewController. Those methods aren't delegate methods, they're superclass overrides, so there's no way to arrange for them to go anywhere else. The message extension system could handle the case you describe but-- aparently-- does not.
What I'd try is:
Make the entry point a subclass of MSMessagesAppViewController.
Early in that object's life cycle (maybe in viewDidLoad) create a UINavigationController and add it as a child view controller of your MSMessagesAppViewController subclass. Make it fill the entire screen.
Now-- in effect-- your navigation controller is the root of the extension. It's not really the root since message events like willTransitionToPresentationStyle will still go through the MSMessagesAppViewController subclass. But everything else starts there. It's the root of the UI and the navigation.
In the meantime it might be good to file an enhancement request with Apple. It's reasonable to think that the message extension system would check the root VC of a navigation controller to see if it's the right class, and maybe they'll add that in the future.
I have an Mvvmcross app that has a TableViewController in root. Each row opens a DetailViewController. And inside each one of it, you can edit it in a EditViewController.
So I have a ViewModel for each view. I'm dealing with two problems here:
1 ) In DetailViewController i subscribe it to a database message. When i close it, i gotta dispose this subscribeToken. So i would need to call this when DetailViewController got destroyed. But cant call it when it disappears, because when I open editViewController it will send a message that DetailViewController gotta listen to.
So I cannot dispose it in ViewDidDisappear method. But the other option would be in ViewDidUnload. But this method is only called in MemoryWarnings. So it is not disposing the token. That is not good .
2) The other problem is: for each DetailsViewcontroller that i open, i have to save in Settings what is the current id. and then when I leave, i have to remove it from Settings. So the same problem here. If i remove it in ViewDidDisappear it will remove when i'm in EditViewController, and i cant, it gotta be set there. But then if i remove only in ViewDidUnload it will not be called, and this variable must be removed.
When should I call the OnDestroy method to both cases?
In Android i'm calling in OnDestroy. Where should i call it in iOS?
Thanks in regards,
ViewDidUnload is not an option - it's deprecated and won't be called (since a long time ago - e.g. maybe since iOS5?).
iOS doesn't really provide a general ViewController override for when the ViewController is "no longer used". However, if you have control of the ViewControllers in your app - e.g. if you are using a NavigationController which never reuses ViewControllers after they have been popped - then it should be relatively straight-forward to provide your own "cleanup" method and to call it from your own navigation control logic - e.g. from a custom presenter using events generated by a NavigationController.
I am working on an app that has UISplitViewControllers inside a UITabBarController under iOS 6.
This used to be a "no no," but apparently it's okay now?
Regardless, in order to make rotations work correctly, I have to forward events from the root view controller to my child split view controllers.
I am currently doing this for several known events (willRotate..., etc). However, there is one delegate method of the split view controller that is still not firing.
My question is this: Is there a way to watch (like in the Xcode console) the events that are being sent to the app delegate's root view controller? I want to see what if any events I am not forwarding correctly. Does this require me to subclass UIWindow and implement -(void)sendEvent:(UIEvent *)event where I do something like NSLog(event)?
Edit: I eventually solved my problem by subclassing UISplitViewController and forcing the delegate method to be called. I wrote a blog article detailing my solution. However, I still haven't found a good way to determine all the events getting sent to the root view controller.
I've seen NSNotification addObserver/removeObserver placed in viewDidLoad/viewDidUnload, viewDidAppear/viewDidDisappear,dealloc`....
What are the proper methods to use here so things are neat and tidy?
That depends. Do you only want to receive notifications when your view controller is on screen (then viewWillAppear/Disappear is probably a good choice) or also when the view is not currently active. In the latter case, the init method might be even better suited than viewDidLoad (or does it matter for the notification that the view is currently in memory?).
Also note that viewDidUnload is not called in all cases (only when the view gets unloaded but the view controller remains in memory – if the view controller is deallocated while the view is loaded, only dealloc is called and is the correct place to unregister.
For viewControllers:
I would say in viewWillAppear: and viewDidDisappear:.
The reason is that you care for these notifications as long as your view is "on screen".
Since a view does not need to be unloaded when your view is offscreen viewDidLoad and viewDidUnload are the wrong locations for (de)registering notifications.
For registering for notification for non views:
In the designated initializer and dealloc.