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;
}
Related
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/
So, I am looking at the code from Parse Anypic tutorial here
and my question is this :
There are these 2 view controllers :
#interface PAPHomeViewController : PAPPhotoTimelineViewController
#end
and this one :
#interface PAPPhotoTimelineViewController : PFQueryTableViewController <PAPPhotoHeaderViewDelegate>
- (PAPPhotoHeaderView *)dequeueReusableSectionHeaderView;
#end
I have a segue let's say that opens the homeViewController.
What is the relationship between these two? Both of the viewcontrollers have implemented the viewDidLoad function - and they are both called. In what order are they called? Is any of them having priority over the other? I do not understand the idea of a viewcontroller that extends another viewcontroller.
Can I have my HomeViewController extends the UIViewController and the PhotoTimeline to be initiated inside the HomeViewController, given a specific frame?
Presumably PAPHomeViewController is the one you are actually instantiating. If that's the case, then its viewDidLoad gets called, if it has one. If PAPHomeViewController calls [super viewDidLoad] within its viewDidLoad method then PAPPhotoTimelineViewController will be called at that point. In turn, if PAPPhotoTimelineViewController calls [super viewDidLoad] then PFQueryTableViewControllers viewDidLoad will be called.
This process will continue all the way up to UIViewControllers viewDidLoad method.
You asked
What is the relationship between these two?
I strongly suggest you stop programming for a bit and read up on basic programming principles. Especially read anything you can find on "implementation inheritance".
Is there a way to accomplish this? Like let's say a framework perhaps?
All I need is something like:
aViewController appeared
bTableViewController appeared
cViewController appeared
Like I added a code in each viewDidLoad, but without adding that code. I know that there is a visibleViewController if I used navigation embed but I don't and I can't.
The framework I'm working on is based on no baseline, so I can not assume anything, like I'll past a code in a project and when the user compiles the code, I will get the views.
The purpose is I'm creating an analytic tool without being have to add individual code in each view controllers's viewDidLoad method.
First of all, there's no such thing as a "visible view controller" technically speaking, despite the annoyingly named method of navigation controllers. Instead, we should be talking about visible views. There's nothing visible about the controller.
But what you really want to know is what view controller is active and controlling visible views. With a navigation controller, it's the top of the nav-stack, with a tab bar controller, it's the selected view controller, etc.
By implementing ONLY code in app-delegate, I don't know of any way to accomplish what you're trying to accomplish.
Instead, what would be your best option would be to provide a framework that included a subclass of UIViewController and UIAppDelegate.
Set the code in these to do all the analytical work necessary. Provide this framework as a whole and inform your users that if they want to make use of the analytics you've provide, they'll need to subclass your view controller instead of UIViewController, etc.
In your custom classes, just override viewDidAppear: to send a notification and viewDidDisappear: to send a notification. And the appdelegate just manages an array that keeps track of which is on top, adding when the viewDidAppear: notification fires, and removing when the viewDidDisappear: notification fires.
And to be sure your viewDidAppear:/viewDidDisappear: isn't overridden, be sure to make use of this: When a subclass overrides a method, how can we ensure at compile time that the superclass's method implementation is called?
So in your view controller base class, it'd be as simple as posting a notification:
// AnalyticViewController.h
#interface AnalyticViewController : UIViewController
- (void)viewDidAppear NS_REQUIRES_SUPER;
- (void)viewDidDisappear NS_REQUIRES_SUPER;
#end
// AnalyticViewController.m
#implementation AnalyticViewController
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[[NSNotificationCenter defaultCenter] postNotificationName:#"AnalyticVCAppeared"
object:self
userInfo:nil];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidAppear:animated];
[[NSNotificationCenter defaultCenter] postNotificationName:#"AnalyticVCDisappeared"
object:self
userInfo:nil];
}
#end
And register for these in the App delegate in application:didFinishLaunchingWithOptions:. You'll want an mutable array to keep track of the stack of view controllers. And you'll want separate methods for each notification.
Given a property #property (nonatomic,strong) NSMutableArray *analyzedViewControllers;, the methods that respond to the notifications should look like this:
- (void)handleViewAppear:(NSNotification *)aNotification {
if (!self.analyzedViewControllers) {
self.analyzedViewControllers = [NSMutableArray array];
}
[self.analyzedViewControllers addObject:[aNotification object]];
}
- (void)handleViewDisappeared:(NSNotification *)aNotification {
[self.analyzedViewControllers removeObject:[aNotification object]];
}
And the top-most view controller will always be at [self.analyzedViewControllers lastObject].
If you need to send more information about the view controller, you can using the userInfo argument when posting the notification. You could even put the entire notification into the array, so you have the view controller reference and the user info reference.
What can also be important to note however is that just because one view controller has appeared doesn't inherently mean another has disappeared. View controllers don't necessarily take up the entire screen, and although there's eventually a practical limit to how many view controllers have visible views on screen, it's a quite high limit.
ADDENDUM: To add some clarity... in the AnalyticViewController class I'm recommending, we have this line:
#interface AnalyticViewController : UIViewController
A line similar to this exists in every one of your view controller's header files. What I'm recommending is that all your other view controllers subclass this one, so instead of:
#interface FooBarViewController : UIViewController
You'd instead have this:
#import AnalyticViewController.h
#interface FooBarViewController : AnalyticViewController
And the code in the viewDidAppear: and viewDidDisappear: of AnalyticViewController is now automatically in FooBarViewController unless you ignore the NS_REQUIRES_SUPER warning and override the viewDidAppear: or viewDidDisappear: methods.
It will be a little bit of effort to change over existing view controllers, but for any future view controllers that are not yet created, it's as simple as choosing to subclass AnalyticViewController rather than UIViewController when you create the file.
For creating a analytical framework i would say you should write a kinda protocol of a rootviewController and viewController class and that delegate methods can be implemented in app delegate for knowing and handling your purpose.
A snippet of the default code in a Master-Detail Xcode project
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController; // *** here ***
MasterViewController *controller = (MasterViewController *)navigationController.topViewController;
controller.managedObjectContext = self.managedObjectContext;
return YES;
}
AppDelegate.h
#property (strong, nonatomic) UIWindow *window;
I am aware that #synthesize just sets the accessor methods, and no initialization happens automagically. But how does window have a non-nil rootViewController if it is never explicitly initialized? Is this just Xcode init'ing behind the scenes?
From my book:
If you choose the Storyboard option as you specify a template, the process works a little differently. The app is given a main storyboard, pointed to by the Info.plist key “Main storyboard file base name” (UIMainStoryboardFile). After UIApplicationMain instantiates the app delegate class, it asks the app delegate for the value of its window property; if that value is nil, the window is created and assigned to the app delegate’s window property. The storyboard’s initial view controller is then instantiated and assigned to the window’s rootViewController property, with the result that its view is placed in the window as its root view; the window is then sent the makeKeyAndVisible message. All of that is done behind the scenes by UIApplicationMain, with no visible code whatever. That is why, in a storyboard template, the application:didFinishLaunchingWithOptions: implementation is empty.
From the UIWindow documentation:
Note: When you use storyboards and the Xcode app templates to create an app, a window is created for you.
If you don't use storyboards, the window is explicitly created, though all the standard project templates do this out of the box. You'll see a line similar to this in the app delegate:
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
Using storyboards, the window is created behind the scenes when the main storyboard is loaded (see the View Controller Programming Guide for more info).
From Apple's docs (in "Using View Controllers in Your App"):
The Main Storyboard Initializes Your App’s User Interface
The main storyboard is defined in the app’s Information property list file. If a main storyboard is declared in this file, then when your app launches, iOS performs the following steps:
It instantiates a window for you.
It loads the main storyboard and instantiates its initial view controller.
It assigns the new view controller to the window’s rootViewController property and then makes the window visible on the screen.
The above answers only who sets the window variable without answering the main questions: "But how does window have a non-nil rootViewController if it is never explicitly initialized? Is this just Xcode init'ing behind the scenes?" and seem to suggest that there is magic afoot. Not a satisfactory answer for me, and so with a little digging, all becomes clear.
The generated code defines AppDelegate as
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
...
}
When you search the project, there is no other reference to window, so apparently it should remain nil, but actually is set to the correct value (by the methods outlined above). The "magic" is that AppDelegate conforms to the UIApplicationDelegate which includes an declaration:
optional public var window: UIWindow? { get set }
Part of conforming to the UIApplicationDelegate is the redeclaration of the public variable window. When the underlying Application references the variable window in the protocol, it is actually linked to the variable window in our class. When the calling Application updates that variable window in the protocol, it is actually updating our variable window. So when we need to access the value in our program it is ready and waiting.
This is not Xcode magic, but an integral part of the Swift language. When using protocols we can employ the same techniques in our own Swift programs. This is just the same as our implementations of various functions in our classes which we do all the time: e.g. UIApplicationDelegate defines
optional public func applicationDidEnterBackground(_ application: UIApplication)
so we can write our own implementation which is then "magically" called!
For completeness, note the #UIApplicationMain tag on the class. This defines the entry point for the application and is what makes everything work together. The actual class name is irrelevant, and can be given any name you require, as long as it is of type UIResponder and conforms to the UIApplicationDelegate.
In your Storyboard, there is a little arrow you can drag around:
If you were using xibs/nibs instead, the 'Main Interface' field would be filled out.
In the end, yep, it's iOS/Xcode magic.
Got a question, I'm trying to return to a previous view and share some data over to the frame I'm returning to. The data will be date and time and I would like to send this to a textField.
For example I'm calling the date *returneddate and the textField I'm calling *dateTime. The views are call *PickDateTime and SubmitEventsP2.
If you need more information just ask me and I'll add it if I can to make it easier for you to help me.
I'm using Xcode 4.2.
Ok.. its pretty simple.. you should use a delegate... if i understood correctly, you are on a secondary view and when you return to the main view you wish to send the data from the second view back to the main view, right?
so, in your second view, in the .h file, on top of interface, you will declare the delegate with:
#class nameOfTheViewController;
#protocol nameOfTheViewControllerDelegate <NSObject>
-(void)methodNameOfDelegateReturning:(NSString *)string otherString:(NSString *)string2;
#end
And in your interface, still in the .h, you will create a reference of this delegate like:
#property(nonatomic, weak) id <nameOfTheViewControllerDelegate> delegate;
after that, in your .m of nameOfTheViewController you will do:
#synthesize delegate = _delegate;
After you created you delegate in the nameOfTheViewController file, you will call the delegate method you just created exactly where and when you want to return to the previous view, filling it with the parameters you want to pass back... and of course, in your mainViewController, right in your didPrepareForSegue method, you will create a instance of the nameOfTheViewController class and set its delegate proeprty to self... for this to be possible, in your mainViewController .h you must conform to the nameOfTheViewControllerDelegate protocol.