Skip to content

Commit 9a14701

Browse files
committed
feat: add Swift AppDelegate support for expo
- Add automatic Swift AppDelegate modification for AppsFlyer plugin - Support Expo SDK 53+ default Swift AppDelegate template - Add Bridging Header automatic import for Swift projects - Maintain backward compatibility with Objective-C AppDelegate
1 parent bb5271c commit 9a14701

1 file changed

Lines changed: 102 additions & 9 deletions

File tree

expo/withAppsFlyerIos.js

Lines changed: 102 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
const { withDangerousMod, withAppDelegate, WarningAggregator } = require('@expo/config-plugins');
22
const { mergeContents } = require('@expo/config-plugins/build/utils/generateCode');
3+
const { getAppDelegate } = require('@expo/config-plugins/build/ios/Paths');
34
const fs = require('fs');
45
const path = require('path');
56

6-
const RNAPPSFLYER_IMPORT = `#import <RNAppsFlyer.h>\n`;
7-
const RNAPPSFLYER_CONTINUE_USER_ACTIVITY_IDENTIFIER = `- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {`;
8-
const RNAPPSFLYER_OPENURL_IDENTIFIER = `- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {`;
9-
const RNAPPSFLYER_CONTINUE_USER_ACTIVITY_CODE = `[[AppsFlyerAttribution shared] continueUserActivity:userActivity restorationHandler:restorationHandler];\n`;
10-
const RNAPPSFLYER_OPENURL_CODE = `[[AppsFlyerAttribution shared] handleOpenUrl:url options:options];\n`;
7+
function modifyObjcAppDelegate(appDelegate) {
8+
const RNAPPSFLYER_IMPORT = `#import <RNAppsFlyer.h>\n`;
9+
const RNAPPSFLYER_CONTINUE_USER_ACTIVITY_IDENTIFIER = `- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {`;
10+
const RNAPPSFLYER_OPENURL_IDENTIFIER = `- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {`;
11+
const RNAPPSFLYER_CONTINUE_USER_ACTIVITY_CODE = `[[AppsFlyerAttribution shared] continueUserActivity:userActivity restorationHandler:restorationHandler];\n`;
12+
const RNAPPSFLYER_OPENURL_CODE = `[[AppsFlyerAttribution shared] handleOpenUrl:url options:options];\n`;
1113

12-
function modifyAppDelegate(appDelegate) {
1314
if (!appDelegate.includes(RNAPPSFLYER_IMPORT)) {
1415
appDelegate = RNAPPSFLYER_IMPORT + appDelegate;
1516
}
@@ -28,17 +29,108 @@ function modifyAppDelegate(appDelegate) {
2829
return appDelegate;
2930
}
3031

32+
function modifySwiftAppDelegate(appDelegateContents) {
33+
const SWIFT_OPENURL_IDENTIFIER = ` public override func application(
34+
_ app: UIApplication,
35+
open url: URL,
36+
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
37+
) -> Bool {`;
38+
const RNAPPSFLYER_SWIFT_OPENURL_CODE = 'AppsFlyerAttribution.shared().handleOpen(url, options: options)';
39+
40+
const SWIFT_CONTINUE_USER_ACTIVITY_IDENTIFIER = ` public override func application(
41+
_ application: UIApplication,
42+
continue userActivity: NSUserActivity,
43+
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
44+
) -> Bool {`;
45+
const RNAPPSFLYER_SWIFT_CONTINUE_USER_ACTIVITY_CODE = 'AppsFlyerAttribution.shared().continue(userActivity, restorationHandler: nil)';
46+
47+
if (appDelegateContents.includes(SWIFT_OPENURL_IDENTIFIER) && !appDelegateContents.includes(RNAPPSFLYER_SWIFT_OPENURL_CODE)) {
48+
appDelegateContents = appDelegateContents.replace(SWIFT_OPENURL_IDENTIFIER, `${SWIFT_OPENURL_IDENTIFIER}\n ${RNAPPSFLYER_SWIFT_OPENURL_CODE}`);
49+
}
50+
51+
if (appDelegateContents.includes(SWIFT_CONTINUE_USER_ACTIVITY_IDENTIFIER) && !appDelegateContents.includes(RNAPPSFLYER_SWIFT_CONTINUE_USER_ACTIVITY_CODE)) {
52+
appDelegateContents = appDelegateContents.replace(SWIFT_CONTINUE_USER_ACTIVITY_IDENTIFIER, `${SWIFT_CONTINUE_USER_ACTIVITY_IDENTIFIER}\n ${RNAPPSFLYER_SWIFT_CONTINUE_USER_ACTIVITY_CODE}`);
53+
}
54+
55+
if (!appDelegateContents.includes(RNAPPSFLYER_SWIFT_OPENURL_CODE) || !appDelegateContents.includes(RNAPPSFLYER_SWIFT_CONTINUE_USER_ACTIVITY_CODE)) {
56+
WarningAggregator.addWarningIOS(
57+
'withAppsFlyerAppDelegate',
58+
`
59+
Automatic Swift AppDelegate modification failed.
60+
Please add AppsFlyer integration manually:
61+
62+
1. Add this to your openURL method:
63+
AppsFlyerAttribution.shared().handleOpen(url, options: options)
64+
65+
2. Add this to your continueUserActivity method:
66+
AppsFlyerAttribution.shared().continue(userActivity, restorationHandler: nil)
67+
68+
Supported format: Expo SDK default template
69+
`
70+
);
71+
}
72+
73+
return appDelegateContents;
74+
}
75+
3176
function withAppsFlyerAppDelegate(config) {
3277
return withAppDelegate(config, (config) => {
33-
if (['objc', 'objcpp'].includes(config.modResults.language)) {
34-
config.modResults.contents = modifyAppDelegate(config.modResults.contents);
78+
const language = config.modResults.language;
79+
80+
if (['objc', 'objcpp'].includes(language)) {
81+
config.modResults.contents = modifyObjcAppDelegate(config.modResults.contents);
82+
} else if (language === 'swift') {
83+
config.modResults.contents = modifySwiftAppDelegate(config.modResults.contents);
3584
} else {
36-
WarningAggregator.addWarningIOS('withAppsFlyerAppDelegate', `${config.modResults.language} AppDelegate file is not supported yet`);
85+
WarningAggregator.addWarningIOS('withAppsFlyerAppDelegate', `${language} AppDelegate file is not supported yet`);
3786
}
3887
return config;
3988
});
4089
}
4190

91+
function withIosBridgingHeader(config) {
92+
return withDangerousMod(config, [
93+
'ios',
94+
async (config) => {
95+
const projectRoot = config.modRequest.projectRoot;
96+
const projectName = config.modRequest.projectName || config.name;
97+
const appDelegate = getAppDelegate(projectRoot);
98+
99+
if (appDelegate.language === 'swift') {
100+
const bridgingHeaderPath = path.join(
101+
config.modRequest.platformProjectRoot,
102+
config.name,
103+
`${projectName}-Bridging-Header.h`,
104+
);
105+
106+
if (fs.existsSync(bridgingHeaderPath)) {
107+
let content = fs.readFileSync(bridgingHeaderPath, 'utf8');
108+
const appsFlyerImport = '#import <RNAppsFlyer.h>';
109+
110+
if (!content.includes(appsFlyerImport)) {
111+
content += `${appsFlyerImport}\n`;
112+
fs.writeFileSync(bridgingHeaderPath, content);
113+
}
114+
} else {
115+
WarningAggregator.addWarningIOS(
116+
'withIosBridgingHeader',
117+
`
118+
Failed to detect ${bridgingHeaderPath} file. Please add AppsFlyer integration manually:
119+
#import <RNAppsFlyer.h>
120+
121+
Supported format: Expo SDK default template
122+
`
123+
);
124+
}
125+
126+
return config;
127+
}
128+
129+
return config;
130+
},
131+
]);
132+
};
133+
42134
function withPodfile(config, shouldUseStrictMode) {
43135
return withDangerousMod(config, [
44136
'ios',
@@ -69,6 +161,7 @@ function withPodfile(config, shouldUseStrictMode) {
69161

70162
module.exports = function withAppsFlyerIos(config, shouldUseStrictMode) {
71163
config = withPodfile(config, shouldUseStrictMode);
164+
config = withIosBridgingHeader(config);
72165
config = withAppsFlyerAppDelegate(config);
73166
return config;
74167
};

0 commit comments

Comments
 (0)