I usually don't do this, but I wonder if not enough developers are providing feedback so I'm going to go on a short tangent. Just to go back to my snippet of what I consider to be “normal API”:
windowController.window.frame = initialRect;
windowController.modelData = modelObject;
[windowController showWindow:sender];
This coding style has essentially been “abstracted away” in Catalyst underneath this:
NSUserActivity *newWindowActivity = [[NSUserActivity alloc]initWithActivityType:@“my.new.window.activitytype”];
//Our options to pass data to the new window are the following:
//Option 1) Write methods to convert a model object to and from one of the types NSUserActivity's userInfo accepts.
//Option 2) Make the model object conform to NSSecureCoding and convert it to and from NSData and stuff the NSData in the activity).
//Option 3) Pass a "database identifier" and query for the model object on the other end from a singleton and/or database.
//In this sample I'm using option 1 because that's the "easiest." Not always possible though.
NSArray *modelAsPlist = [modelData propertyListRepresentation];
NSDictionary *modelDictionary = @{@"NewWindowModelData":modelAsPlist};
[newWindowActivity addUserInfoEntriesFromDictionary:modelDictionary];
UISceneActivationRequestOptions *activationOptions = [[UISceneActivationRequestOptions alloc]init];
activationOptions.requestingScene = self.view.window.windowScene;
UIApplication *application = [UIApplication sharedApplication];
[application requestSceneSessionActivation:nil
userActivity:newWindowActivity
options:activationOptions
errorHandler:^(NSError * _Nonnull error)
{
//Whoops the system didn't give me a window. Tough luck. What kind of error handling are application developers expected to do here?
}];
And we still haven't achieved the three lines of the "normal API" yet. The controller that needs the model object still hasn't gotten it yet. And the initial frame for the window isn't set. Now on the other end in the UIWindowSceneDelegate:
-(void)scene:(UIWindowScene*)scene
willConnectToSession:(UISceneSession*)session
options:(UISceneConnectionOptions*)connectionOptions
{
//connectionOptions can have more than 1 activity...we gotta dig for our model object and that's the bottom line because the System says so.
NSUserActivity *activityToConfigureWith = nil;
for (NSUserActivity *aActivity in connectionOptions.userActivities)
{
if (aActivity.activityType isEqualToString:@"my.new.window.activitytype"])
{
activityToConfigureWith = aActivity;
break;
}
}
if (activityToConfigureWith != nil)
{
NSArray *topLevelModelPlist = activityToConfigureWith.userInfo[@"NewWindowModelData"];
ModelData *model = [ModelData makeWithPropertyList:topLevelModelPlist];
MyViewController *myVC = [[MyViewController alloc]initWithModel:model];
self.window = [[UIWindow alloc]initWithWindowScene:scene];
self.window.rootViewController = myVC;
[self.window makeKeyAndVisible];
}
else
{
//Not found. Maybe we have a state restoration activity to configure..
//Or maybe not. Maybe we are just on app launch. We have to sort our way through this pile of user activities just to get started here..it's kind of a mess.
}
CGRect systemFrame = scene.effectiveGeometry.systemFrame;
CGRect newFrame = systemFrame;
newFrame.size.width = DEFAULT_WIDTH;
newFrame.size.height = DEFAULT_HEIGHT;
UIWindowSceneGeometryPreferencesMac *geometryPrefs = [[UIWindowSceneGeometryPreferencesMac alloc]initWithSystemFrame:newFrame];
[scene requestGeometryUpdateWithPreferences:geometryPrefs errorHandler:^(NSError * _Nonnull error)
{
//Error setting frame. Tough luck! What kind of error handling are application developers expected to do here?
}];
}
Now in my real app a lot of this code I silo in categories because the amount of code required for such simple tasks is kind of absurd. All we did is 1) create a window 2) set its initial frame and model object and 3) show it on screen.
Not sure how this was decided on but IMHO the abstraction is 10x more complex than the internal implementation. At the very least creating and passing data to a new window shouldn't be any harder than creating a new view controller programmatically or using storyboards with -prepareForSegue: But it is a lot harder. And requires lots more boilerplate.
This doesn't include the code required to workaround the issue described in FB11885144.
If you want to make it easy to port iOS apps to the Mac maybe just expose a NSViewController subclass in the public API that embeds a UIKit view hierarchy inside of it and let developers use them in NSWindows directly? Maybe?