226 lines
7.2 KiB
JavaScript
226 lines
7.2 KiB
JavaScript
const fs = require("fs");
|
|
const path = require("path");
|
|
const {
|
|
createRunOncePlugin,
|
|
withAppDelegate,
|
|
withDangerousMod,
|
|
withInfoPlist,
|
|
} = require("@expo/config-plugins");
|
|
|
|
const SCENE_MARKER = "Xcode 27 UIScene lifecycle compatibility";
|
|
const DEPLOYMENT_TARGET_MARKER = "Xcode 27 minimum deployment target compatibility";
|
|
|
|
const SCENE_INTERFACE = `// ${SCENE_MARKER}
|
|
@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
|
|
|
|
@property (nonatomic, strong) UIWindow *window;
|
|
|
|
@end`;
|
|
|
|
const SCENE_CONFIGURATION = `- (UISceneConfiguration *)application:(UIApplication *)application
|
|
configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession
|
|
options:(UISceneConnectionOptions *)options
|
|
{
|
|
UISceneConfiguration *configuration =
|
|
[[UISceneConfiguration alloc] initWithName:@"Default Configuration"
|
|
sessionRole:connectingSceneSession.role];
|
|
configuration.delegateClass = [SceneDelegate class];
|
|
return configuration;
|
|
}
|
|
`;
|
|
|
|
const SCENE_IMPLEMENTATION = `@implementation SceneDelegate
|
|
|
|
- (void)scene:(UIScene *)scene
|
|
willConnectToSession:(UISceneSession *)session
|
|
options:(UISceneConnectionOptions *)connectionOptions
|
|
{
|
|
UIWindowScene *windowScene = (UIWindowScene *)scene;
|
|
AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;
|
|
|
|
self.window = appDelegate.window;
|
|
self.window.windowScene = windowScene;
|
|
[self.window makeKeyAndVisible];
|
|
|
|
if (connectionOptions.URLContexts.count > 0) {
|
|
[self scene:scene openURLContexts:connectionOptions.URLContexts];
|
|
}
|
|
|
|
for (NSUserActivity *userActivity in connectionOptions.userActivities) {
|
|
[self scene:scene continueUserActivity:userActivity];
|
|
}
|
|
}
|
|
|
|
- (void)sceneDidBecomeActive:(UIScene *)scene
|
|
{
|
|
AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;
|
|
[appDelegate applicationDidBecomeActive:UIApplication.sharedApplication];
|
|
}
|
|
|
|
- (void)sceneWillResignActive:(UIScene *)scene
|
|
{
|
|
AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;
|
|
[appDelegate applicationWillResignActive:UIApplication.sharedApplication];
|
|
}
|
|
|
|
- (void)sceneWillEnterForeground:(UIScene *)scene
|
|
{
|
|
AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;
|
|
[appDelegate applicationWillEnterForeground:UIApplication.sharedApplication];
|
|
}
|
|
|
|
- (void)sceneDidEnterBackground:(UIScene *)scene
|
|
{
|
|
AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;
|
|
[appDelegate applicationDidEnterBackground:UIApplication.sharedApplication];
|
|
}
|
|
|
|
- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts
|
|
{
|
|
AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;
|
|
|
|
for (UIOpenURLContext *urlContext in URLContexts) {
|
|
NSMutableDictionary<UIApplicationOpenURLOptionsKey, id> *options = [NSMutableDictionary dictionary];
|
|
options[UIApplicationOpenURLOptionsOpenInPlaceKey] = @(urlContext.options.openInPlace);
|
|
|
|
if (urlContext.options.sourceApplication != nil) {
|
|
options[UIApplicationOpenURLOptionsSourceApplicationKey] = urlContext.options.sourceApplication;
|
|
}
|
|
if (urlContext.options.annotation != nil) {
|
|
options[UIApplicationOpenURLOptionsAnnotationKey] = urlContext.options.annotation;
|
|
}
|
|
|
|
[appDelegate application:UIApplication.sharedApplication openURL:urlContext.URL options:options];
|
|
}
|
|
}
|
|
|
|
- (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity
|
|
{
|
|
AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;
|
|
[appDelegate application:UIApplication.sharedApplication
|
|
continueUserActivity:userActivity
|
|
restorationHandler:^(NSArray<id<UIUserActivityRestoring>> *restorableObjects) {
|
|
}];
|
|
}
|
|
|
|
@end`;
|
|
|
|
const DEPLOYMENT_TARGET_BLOCK = ` # ${DEPLOYMENT_TARGET_MARKER}
|
|
installer.pods_project.targets.each do |target|
|
|
target.build_configurations.each do |build_configuration|
|
|
build_configuration.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = ios_deployment_target
|
|
end
|
|
end`;
|
|
|
|
function applySceneLifecycle(contents) {
|
|
if (contents.includes(SCENE_MARKER)) {
|
|
return contents;
|
|
}
|
|
|
|
const importAnchor = "#import <React/RCTLinkingManager.h>";
|
|
const configurationAnchor = "- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge";
|
|
|
|
if (!contents.includes(importAnchor) || !contents.includes(configurationAnchor)) {
|
|
throw new Error(
|
|
"withXcode27Compatibility: could not find the Objective-C AppDelegate anchors."
|
|
);
|
|
}
|
|
|
|
const withInterface = contents.replace(
|
|
importAnchor,
|
|
`${importAnchor}\n\n${SCENE_INTERFACE}`
|
|
);
|
|
const withConfiguration = withInterface.replace(
|
|
configurationAnchor,
|
|
`${SCENE_CONFIGURATION}\n${configurationAnchor}`
|
|
);
|
|
|
|
return `${withConfiguration.trimEnd()}\n\n${SCENE_IMPLEMENTATION}\n`;
|
|
}
|
|
|
|
function applyDeploymentTarget(contents) {
|
|
let patched = contents;
|
|
|
|
if (!patched.includes("ios_deployment_target =")) {
|
|
const platformLine =
|
|
"platform :ios, podfile_properties['ios.deploymentTarget'] || '15.1'";
|
|
if (!patched.includes(platformLine)) {
|
|
throw new Error(
|
|
"withXcode27Compatibility: could not find the iOS platform declaration."
|
|
);
|
|
}
|
|
patched = patched.replace(
|
|
platformLine,
|
|
"ios_deployment_target = podfile_properties['ios.deploymentTarget'] || '15.1'\n" +
|
|
"platform :ios, ios_deployment_target"
|
|
);
|
|
}
|
|
|
|
if (!patched.includes(DEPLOYMENT_TARGET_MARKER)) {
|
|
const anchor =
|
|
" # This is necessary for Xcode 14, because it signs resource bundles by default";
|
|
if (!patched.includes(anchor)) {
|
|
throw new Error(
|
|
"withXcode27Compatibility: could not find the Podfile post-install anchor."
|
|
);
|
|
}
|
|
patched = patched.replace(
|
|
anchor,
|
|
`${DEPLOYMENT_TARGET_BLOCK}\n\n${anchor}`
|
|
);
|
|
}
|
|
|
|
return patched;
|
|
}
|
|
|
|
const withXcode27Compatibility = (config) => {
|
|
config = withInfoPlist(config, (config) => {
|
|
config.modResults.UIApplicationSceneManifest = {
|
|
UIApplicationSupportsMultipleScenes: false,
|
|
UISceneConfigurations: {
|
|
UIWindowSceneSessionRoleApplication: [
|
|
{
|
|
UISceneConfigurationName: "Default Configuration",
|
|
UISceneDelegateClassName: "SceneDelegate",
|
|
},
|
|
],
|
|
},
|
|
};
|
|
return config;
|
|
});
|
|
|
|
config = withAppDelegate(config, (config) => {
|
|
if (!["objc", "objcpp"].includes(config.modResults.language)) {
|
|
throw new Error(
|
|
"withXcode27Compatibility: expected an Objective-C AppDelegate."
|
|
);
|
|
}
|
|
config.modResults.contents = applySceneLifecycle(config.modResults.contents);
|
|
return config;
|
|
});
|
|
|
|
config = withDangerousMod(config, [
|
|
"ios",
|
|
async (config) => {
|
|
const podfilePath = path.join(
|
|
config.modRequest.platformProjectRoot,
|
|
"Podfile"
|
|
);
|
|
const podfile = fs.readFileSync(podfilePath, "utf8");
|
|
const patched = applyDeploymentTarget(podfile);
|
|
if (patched !== podfile) {
|
|
fs.writeFileSync(podfilePath, patched);
|
|
}
|
|
return config;
|
|
},
|
|
]);
|
|
|
|
return config;
|
|
};
|
|
|
|
module.exports = createRunOncePlugin(
|
|
withXcode27Compatibility,
|
|
"with-xcode-27-compatibility",
|
|
"1.0.0"
|
|
);
|