Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 29 additions & 8 deletions cli/src/ios/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,19 @@ function buildBinaryTargetEntries(p: Plugin, frameworks: any[]): { binaryTargets
return { binaryTargetsText, binaryDepsText };
}

function buildResourcesText(resources: any[]) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, not sure if this is a real concern, but buildResourcesText uses .copy() for all resource types. For resources like .xcassets, .storyboard, and .xib, SPM typically requires .process() to compile them correctly, otherwise they might not be accessible at runtime. That said, the best way to validate this would probably be with a real plugin that actually uses these file types, since the dummy plugin I created for testing had XIB/xcassets files that weren't properly generated by Xcode. If the plugin you tested already included these file types and everything worked fine, then this is probably not a concern!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've only found plugins using .bundle file extension and they required .copy.

For those file extensions we shouldn't even need to add a resources entry as it's supposed to auto detect them according to the docs, but having them with the copy might conflict, so we will have to revisit if some user reports that they don't work and provide some Cordova plugin to reproduce. We will merge as it is now.

When you add a resource to your Swift package, Xcode detects common resource types for Apple platforms and treats them as a resource automatically. For example, you don’t need to make changes to your package manifest for the following resources:

Interface Builder files; for example, XIB files and storyboards

Core Data files; for example, xcdatamodeld files

Asset catalogs

.lproj folders you use to provide localized resources

https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package

const resourceEntry = [];
for (const resource of resources) {
resourceEntry.push(`.copy("resources/${resource.$.src.split('/').pop()}")`);
}
return resources.length > 0
Copy link
Copy Markdown
Member

@markemer markemer May 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A little confusing what's going on here. Could we split this up without the ternary? I thought it was returning bool, and then thought there was dead code, before I noticed what was up.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is in line with other string generations in the codebase:

const libraryTypeText = hasWeakFrameworks ? `\n type: .dynamic,` : '';

headerFiles.length > 0
? `,
publicHeadersPath: "."`
: '';

requiredSystemFrameworks.length > 0
? `,
linkerSettings: [
${requiredSystemFrameworks.map((f: any) => ` .linkedFramework("${f.$.src.replace('.framework', '')}")`).join(',\n')}
]`
: '';

const traitsSuffix = traits?.length
? `, traits: [${traits
.map((t) => {
// Any trait is written with quotes, with the exception of .defaults
return /^\.?defaults?$/i.test(t) ? '.defaults' : `"${t}"`;
})
.join(', ')}]`
: '';

? `,
resources: [
${resourceEntry.join(',\n ')}
]`
: '';
}

function buildCSettingsText(p: Plugin, sourceFiles: any[]): string {
const pluginId = p.id;
const allFlags = new Set<string>();
Expand Down Expand Up @@ -190,8 +203,9 @@ async function writeGeneratedPackageSwift(p: Plugin, config: Config, iosPlatform
? `,
publicHeadersPath: "."`
: '';
const resources = getPlatformElement(p, platform, 'resource-file');
const sourceFiles = getPlatformElement(p, platform, 'source-file');
if (sourceFiles.length === 0 && headerFiles.length === 0) {
if (sourceFiles.length === 0 && headerFiles.length === 0 && resources.length === 0) {
return;
}
const frameworks = getPlatformElement(p, platform, 'framework');
Expand All @@ -200,6 +214,7 @@ async function writeGeneratedPackageSwift(p: Plugin, config: Config, iosPlatform
const systemFrameworks = frameworks.filter((f: any) => !f.$.custom && f.$.src.endsWith('.framework'));
const hasWeakFrameworks = systemFrameworks.some((f: any) => f.$.weak === 'true');
const requiredSystemFrameworks = systemFrameworks.filter((f: any) => f.$.weak !== 'true');
const resourcesText = buildResourcesText(resources);

const libraryTypeText = hasWeakFrameworks ? `\n type: .dynamic,` : '';
const linkerSettingsText =
Expand Down Expand Up @@ -232,7 +247,7 @@ let package = Package(
dependencies: [
.product(name: "Cordova", package: "capacitor-swift-pm")${binaryDepsText}
],
path: "."${headersText}${cSettingsText}${linkerSettingsText}
path: "."${resourcesText}${headersText}${cSettingsText}${linkerSettingsText}
)${binaryTargetsText}
]
)`;
Expand Down Expand Up @@ -557,8 +572,16 @@ async function copyPluginsNativeFiles(config: Config, cordovaPlugins: Plugin[])
fileContent.includes('[NSBundle bundleForClass:[self class]]') ||
fileContent.includes('[NSBundle bundleForClass:[CDVCapture class]]')
) {
fileContent = fileContent.replace('[NSBundle bundleForClass:[self class]]', '[NSBundle mainBundle]');
fileContent = fileContent.replace('[NSBundle bundleForClass:[CDVCapture class]]', '[NSBundle mainBundle]');
const bundleName = isSPM ? 'SWIFTPM_MODULE_BUNDLE' : '[NSBundle mainBundle]';
fileContent = fileContent.replace('[NSBundle bundleForClass:[self class]]', bundleName);
fileContent = fileContent.replace('[NSBundle bundleForClass:[CDVCapture class]]', bundleName);
await writeFile(fileDest, fileContent, { encoding: 'utf-8' });
}
if (isSPM && fileContent.includes('[NSBundle mainBundle] URLForResource')) {
fileContent = fileContent.replace(
'[NSBundle mainBundle] URLForResource',
'SWIFTPM_MODULE_BUNDLE URLForResource',
);
Comment thread
ItsChaceD marked this conversation as resolved.
await writeFile(fileDest, fileContent, { encoding: 'utf-8' });
}
if (fileContent.includes('[self.webView superview]') || fileContent.includes('self.webView.superview')) {
Expand All @@ -572,10 +595,8 @@ async function copyPluginsNativeFiles(config: Config, cordovaPlugins: Plugin[])
const resourceFiles = getPlatformElement(p, platform, 'resource-file');
for (const resourceFile of resourceFiles) {
const fileName = resourceFile.$.src.split('/').pop();
await copy(
getFilePath(config, p, resourceFile.$.src),
join(config.ios.cordovaPluginsDirAbs, 'resources', fileName),
);
const rootResFolder = isSPM ? sourcesFolder : config.ios.cordovaPluginsDirAbs;
await copy(getFilePath(config, p, resourceFile.$.src), join(rootResFolder, 'resources', fileName));
}
for (const framework of frameworks) {
if (framework.$.custom && framework.$.custom === 'true') {
Expand Down
Loading