In my plugin.xml, I'm trying to declare a custom .framework and have it weak linked, but once I open Xcode I see the added framework is still marked as "required" instead of "optional".
Here's my plugin.xml entry:
<framework src="ios/libs/BlaBla.framework" custom="true" weak="true" />
It's a 3rd party custom .framework
I've received that contains Headers (obviously) and a shared dynamic lib file (which I will load during runtime using dlopen("TheDylib", RTLD_LAZY|RTLD_GLOBAL);
).
The reason I can't use <header-file src="BlaBla.framework/Headers/Bla.h" />
is that the headers in the .framework
themselves refer to inner headers with #import <BlaBla.framework/SomeHeader.h>
so the <header-file>
tag can't help in this case.
Better use "Embedded Frameworks" functionality instead of this solution because dlopen
is forbidden since iOS 8.0 on non-mac/simulator devices (real iPhones/iPads).
Take a look at Custom Cordova Plugin: Add framework to "Embedded Binaries"
I ended up doing something a bit different, Instead of declaring the .framework
as a <framework ... />
tag, I did the following.
I created a plugin hook that adds the plugin dir to the FRAMEWORK_SEARCH_PATHS Xcode build property.
<hook type="after_platform_add" src="hooks/addPluginDirToFrameworkSearchPaths/hook.js" />
Hook Code:
module.exports = function(context) {
const includesiOS = context.opts.platforms.indexOf('ios') != -1;
if(!includesiOS) return;
const
deferral = context.requireCordovaModule('q').defer(),
pluginId = context.opts.plugin.id;
const xcode = require('xcode'),
fs = require('fs'),
path = require('path');
function fromDir(startPath,filter, rec) {
if (!fs.existsSync(startPath)){
console.log("no dir ", startPath);
return;
}
const files=fs.readdirSync(startPath);
for(var i=0;i<files.length;i++){
var filename=path.join(startPath,files[i]);
var stat = fs.lstatSync(filename);
if (stat.isDirectory() && rec){
fromDir(filename,filter); //recurse
}
if (filename.indexOf(filter)>=0) {
return filename;
}
}
}
const xcodeProjPath = fromDir('platforms/ios','.xcodeproj', false);
const projectPath = xcodeProjPath + '/project.pbxproj';
const myProj = xcode.project(projectPath);
function unquote(str) {
if (str) return str.replace(/^"(.*)"$/, "$1");
}
function getProjectName(myProj) {
var projectName = myProj.getFirstTarget().firstTarget.name;
projectName = unquote(projectName);
return projectName;
}
function set_FRAMEWORK_SEARCH_PATHS(proj) {
const lineToAdd = '"\\"' + getProjectName(proj) + '/Plugins/' + pluginId + '\\""'
const FRAMEWORK_SEARCH_PATHS = proj.getBuildProperty("FRAMEWORK_SEARCH_PATHS");
if(FRAMEWORK_SEARCH_PATHS != null) {
const isArray = typeof FRAMEWORK_SEARCH_PATHS != 'string';
if(isArray) {
for(var entry of FRAMEWORK_SEARCH_PATHS) {
if(entry.indexOf(pluginId) != -1) {
return false; // already exists, no need to do anything.
}
}
} else { // string
if(FRAMEWORK_SEARCH_PATHS.indexOf(pluginId) != -1) {
return false; // already exists, no need to do anything.
}
}
var newValueArray = isArray?FRAMEWORK_SEARCH_PATHS:[FRAMEWORK_SEARCH_PATHS];
newValueArray.push(lineToAdd);
proj.updateBuildProperty("FRAMEWORK_SEARCH_PATHS", newValueArray);
} else {
proj.addBuildProperty("FRAMEWORK_SEARCH_PATHS", lineToAdd);
}
return true;
}
myProj.parse(function (err) {
if(err) {
deferral.reject('Error while parsing project');
}
if(set_FRAMEWORK_SEARCH_PATHS(myProj)) {
fs.writeFileSync(projectPath, myProj.writeSync());
console.log('Added Framework Search Path for ' + pluginId);
} else {
console.log('Framework Search Path was already added for ' + pluginId);
}
deferral.resolve();
});
return deferral.promise;
};
Note: the hook depends on an NPM dependency named "xcode" so do npm i xcode --save
before (no need to edit the hook code).
Now the way we declare in plugin.xml to import the .framework
content to our project is the following:
<source-file src="ios/libs/CameraWizard.framework" />
<resource-file src="ios/libs/CameraWizard.framework/CameraWizard" />
We use the source-file
tag simply to import the .framework
because we only want it to be copied to the iOS platform plugins directory, we do not wish to have it "strongly" linked, the reason we need it there is only for it's Headers
, not it's binary. Our hook will add the correct framework search path for it.
Then we use resource-file
to import only the shared library file inside the .framework
directory, we add it as a resource so that when the app starts and we call dlopen(...)
, the shared library will be found in runtime.
#import <dlfcn.h>
(also import your .framework
's headers).Under -(void)pluginInitialize
method, load the shared lib:
NSString* resourcePath = [[NSBundle mainBundle] resourcePath]; NSString* dlPath = [NSString stringWithFormat: @"%@/FrameworkFileNameInResourceTag", resourcePath]; const char* cdlpath = [dlPath UTF8String]; dlopen(cdlpath, RTLD_LAZY|RTLD_GLOBAL);
Now to use a class from the shared library:
SomeClassInFramework someInstance = [(SomeClassInFramework)[NSClassFromString(@"SomeClassInFramework") alloc] init];