8

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?

Zhu Shengqi
  • 3,632
  • 3
  • 24
  • 29
  • For one thing, it enables the user activity based state saving mechanism. https://developer.apple.com/documentation/uikit/uiresponder/1621089-useractivity – matt Feb 13 '20 at 04:31
  • @matt UIScene also inherits from UIResponder. Can we just make scene delegate class inherits from NSObject and use UIScene for state restoration? – Zhu Shengqi Feb 13 '20 at 06:25

1 Answers1

5

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;
}

Scene Manfiest showing Class Name

malhal
  • 26,330
  • 7
  • 115
  • 133
  • 1
    Another approach would be to subclass the window `class ResponderWindow: UIWindow { override var next: UIResponder? { windowScene?.delegate as! SceneDelegate} }` – glotcha Dec 10 '21 at 06:40