|
|
|
@@ -55,6 +55,9 @@ public: |
|
|
|
getTargetLocationValue() = getDefaultBuildsRootFolder() + (iOS ? "iOS" : "MacOSX");
|
|
|
|
|
|
|
|
initialiseDependencyPathValues();
|
|
|
|
|
|
|
|
if (getScreenOrientationValue().toString().isEmpty())
|
|
|
|
getScreenOrientationValue() = "portraitlandscape";
|
|
|
|
}
|
|
|
|
|
|
|
|
static XCodeProjectExporter* createForSettings (Project& project, const ValueTree& settings)
|
|
|
|
@@ -78,6 +81,25 @@ public: |
|
|
|
Value getPreBuildScriptValue() { return getSetting (Ids::prebuildCommand); }
|
|
|
|
String getPreBuildScript() const { return settings [Ids::prebuildCommand]; }
|
|
|
|
|
|
|
|
Value getScreenOrientationValue() { return getSetting (Ids::iosScreenOrientation); }
|
|
|
|
String getScreenOrientationString() const { return settings [Ids::iosScreenOrientation]; }
|
|
|
|
|
|
|
|
Value getCustomResourceFoldersValue() { return getSetting (Ids::customXcodeResourceFolders); }
|
|
|
|
String getCustomResourceFoldersString() const { return getSettingString (Ids::customXcodeResourceFolders).replaceCharacters ("\r\n", "::"); }
|
|
|
|
|
|
|
|
Value getCustomXcassetsFolderValue() { return getSetting (Ids::customXcassetsFolder); }
|
|
|
|
String getCustomXcassetsFolderString() const { return settings [Ids::customXcassetsFolder]; }
|
|
|
|
|
|
|
|
Value getInAppPurchasesValue() { return getSetting (Ids::iosInAppPurchases); }
|
|
|
|
bool isInAppPurchasesEnabled() const { return settings [Ids::iosInAppPurchases]; }
|
|
|
|
Value getBackgroundAudioValue() { return getSetting (Ids::iosBackgroundAudio); }
|
|
|
|
bool isBackgroundAudioEnabled() const { return settings [Ids::iosBackgroundAudio]; }
|
|
|
|
Value getBackgroundBleValue() { return getSetting (Ids::iosBackgroundBle); }
|
|
|
|
bool isBackgroundBleEnabled() const { return settings [Ids::iosBackgroundBle]; }
|
|
|
|
|
|
|
|
Value getIosDevelopmentTeamIDValue() { return getSetting (Ids::iosDevelopmentTeamID); }
|
|
|
|
String getIosDevelopmentTeamIDString() const { return settings [Ids::iosDevelopmentTeamID]; }
|
|
|
|
|
|
|
|
bool usesMMFiles() const override { return true; }
|
|
|
|
bool isXcode() const override { return true; }
|
|
|
|
bool isOSX() const override { return ! iOS; }
|
|
|
|
@@ -85,19 +107,48 @@ public: |
|
|
|
|
|
|
|
void createExporterProperties (PropertyListBuilder& props) override
|
|
|
|
{
|
|
|
|
if (projectType.isGUIApplication() && ! iOS)
|
|
|
|
if (iOS)
|
|
|
|
{
|
|
|
|
props.add (new TextPropertyComponent (getSetting ("documentExtensions"), "Document file extensions", 128, false),
|
|
|
|
"A comma-separated list of file extensions for documents that your app can open. "
|
|
|
|
"Using a leading '.' is optional, and the extensions are not case-sensitive.");
|
|
|
|
props.add (new TextPropertyComponent (getCustomXcassetsFolderValue(), "Custom Xcassets folder", 128, false),
|
|
|
|
"If this field is not empty, your Xcode project will use the custom xcassets folder specified here "
|
|
|
|
"for the app icons and launchimages, and will ignore the Icon files specified above.");
|
|
|
|
}
|
|
|
|
else if (iOS)
|
|
|
|
|
|
|
|
props.add (new TextPropertyComponent (getCustomResourceFoldersValue(), "Custom Xcode Resource folders", 8192, true),
|
|
|
|
"You can specify a list of custom resource folders here (separated by newlines or whitespace). "
|
|
|
|
"References to these folders will then be added to the Xcode resources. "
|
|
|
|
"This way you can specify them for OS X and iOS separately, and modify the content of the resource folders "
|
|
|
|
"without re-saving the Introjucer project.");
|
|
|
|
|
|
|
|
if (iOS)
|
|
|
|
{
|
|
|
|
static const char* orientations[] = { "Portrait and Landscape", "Portrait", "Landscape", nullptr };
|
|
|
|
static const char* orientationValues[] = { "portraitlandscape", "portrait", "landscape", nullptr };
|
|
|
|
|
|
|
|
props.add (new ChoicePropertyComponent (getScreenOrientationValue(), "Screen orientation",StringArray (orientations), Array<var> (orientationValues)),
|
|
|
|
"The screen orientations that this app should support");
|
|
|
|
|
|
|
|
props.add (new BooleanPropertyComponent (getSetting ("UIFileSharingEnabled"), "File Sharing Enabled", "Enabled"),
|
|
|
|
"Enable this to expose your app's files to iTunes.");
|
|
|
|
|
|
|
|
props.add (new BooleanPropertyComponent (getSetting ("UIStatusBarHidden"), "Status Bar Hidden", "Enabled"),
|
|
|
|
"Enable this to disable the status bar in your app.");
|
|
|
|
|
|
|
|
props.add (new BooleanPropertyComponent (getInAppPurchasesValue(), "In-App purchases capability", "Enabled"),
|
|
|
|
"Enable this to grant your app the capability for in-app purchases. "
|
|
|
|
"This option requires that you specify a valid Development Team ID.");
|
|
|
|
|
|
|
|
props.add (new BooleanPropertyComponent (getBackgroundAudioValue(), "Audio background capability", "Enabled"),
|
|
|
|
"Enable this to grant your app the capability to access audio when in background mode.");
|
|
|
|
|
|
|
|
props.add (new BooleanPropertyComponent (getBackgroundBleValue(), "Bluetooth MIDI background capability", "Enabled"),
|
|
|
|
"Enable this to grant your app the capability to connect to Bluetooth LE devices when in background mode.");
|
|
|
|
}
|
|
|
|
else if (projectType.isGUIApplication())
|
|
|
|
{
|
|
|
|
props.add (new TextPropertyComponent (getSetting ("documentExtensions"), "Document file extensions", 128, false),
|
|
|
|
"A comma-separated list of file extensions for documents that your app can open. "
|
|
|
|
"Using a leading '.' is optional, and the extensions are not case-sensitive.");
|
|
|
|
}
|
|
|
|
|
|
|
|
props.add (new TextPropertyComponent (getPListToMergeValue(), "Custom PList", 8192, true),
|
|
|
|
@@ -114,6 +165,14 @@ public: |
|
|
|
|
|
|
|
props.add (new TextPropertyComponent (getPostBuildScriptValue(), "Post-build shell script", 32768, true),
|
|
|
|
"Some shell-script that will be run after a build completes.");
|
|
|
|
|
|
|
|
if (iOS)
|
|
|
|
{
|
|
|
|
props.add (new TextPropertyComponent (getIosDevelopmentTeamIDValue(), "Development Team ID", 10, false),
|
|
|
|
"The Development Team ID to be used for setting up code-signing your iOS app. This is a ten-character "
|
|
|
|
"string (for example, \"S7B6T5XJ2Q\") that describes the distribution certificate Apple issued to you. "
|
|
|
|
"You can find this string in the OS X app Keychain Access under \"Certificates\".");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool launchProject() override
|
|
|
|
@@ -325,6 +384,7 @@ private: |
|
|
|
void createObjects() const
|
|
|
|
{
|
|
|
|
addFrameworks();
|
|
|
|
addCustomResourceFolders();
|
|
|
|
addMainBuildProduct();
|
|
|
|
|
|
|
|
if (xcodeCreatePList)
|
|
|
|
@@ -337,7 +397,14 @@ private: |
|
|
|
if (iOS)
|
|
|
|
{
|
|
|
|
if (! projectType.isStaticLibrary())
|
|
|
|
createiOSAssetsFolder();
|
|
|
|
{
|
|
|
|
String customXcassetsPath = getCustomXcassetsFolderString();
|
|
|
|
|
|
|
|
if (customXcassetsPath.isEmpty())
|
|
|
|
createXcassetsFolderFromIcons();
|
|
|
|
else
|
|
|
|
addCustomResourceFolder (customXcassetsPath, "folder.assetcatalog");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
@@ -662,22 +729,8 @@ private: |
|
|
|
// Forcing full screen disables the split screen feature and prevents error ITMS-90475
|
|
|
|
addPlistDictionaryKeyBool (dict, "UIRequiresFullScreen", true);
|
|
|
|
|
|
|
|
static const char* kDefaultiOSOrientationStrings[] =
|
|
|
|
{
|
|
|
|
"UIInterfaceOrientationPortrait",
|
|
|
|
"UIInterfaceOrientationPortraitUpsideDown",
|
|
|
|
"UIInterfaceOrientationLandscapeLeft",
|
|
|
|
"UIInterfaceOrientationLandscapeRight",
|
|
|
|
nullptr
|
|
|
|
};
|
|
|
|
|
|
|
|
StringArray iOSOrientations (kDefaultiOSOrientationStrings);
|
|
|
|
|
|
|
|
dict->createNewChildElement ("key")->addTextElement ("UISupportedInterfaceOrientations");
|
|
|
|
XmlElement* plistStringArray = dict->createNewChildElement ("array");
|
|
|
|
|
|
|
|
for (int i = 0; i < iOSOrientations.size(); ++i)
|
|
|
|
plistStringArray->createNewChildElement ("string")->addTextElement (iOSOrientations[i]);
|
|
|
|
addIosScreenOrientations (dict);
|
|
|
|
addIosBackgroundModes (dict);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < xcodeExtraPListEntries.size(); ++i)
|
|
|
|
@@ -689,6 +742,36 @@ private: |
|
|
|
overwriteFileIfDifferentOrThrow (infoPlistFile, mo);
|
|
|
|
}
|
|
|
|
|
|
|
|
void addIosScreenOrientations (XmlElement* dict) const
|
|
|
|
{
|
|
|
|
String screenOrientation = getScreenOrientationString();
|
|
|
|
StringArray iOSOrientations;
|
|
|
|
|
|
|
|
if (screenOrientation.contains ("portrait")) { iOSOrientations.add ("UIInterfaceOrientationPortrait"); }
|
|
|
|
if (screenOrientation.contains ("landscape")) { iOSOrientations.add ("UIInterfaceOrientationLandscapeLeft"); iOSOrientations.add ("UIInterfaceOrientationLandscapeRight"); }
|
|
|
|
|
|
|
|
addArrayToPlist (dict, "UISupportedInterfaceOrientations", iOSOrientations);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void addIosBackgroundModes (XmlElement* dict) const
|
|
|
|
{
|
|
|
|
StringArray iosBackgroundModes;
|
|
|
|
if (isBackgroundAudioEnabled()) iosBackgroundModes.add ("audio");
|
|
|
|
if (isBackgroundBleEnabled()) iosBackgroundModes.add ("bluetooth-central");
|
|
|
|
|
|
|
|
addArrayToPlist (dict, "UIBackgroundModes", iosBackgroundModes);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void addArrayToPlist (XmlElement* dict, String arrayKey, const StringArray& arrayElements)
|
|
|
|
{
|
|
|
|
dict->createNewChildElement ("key")->addTextElement (arrayKey);
|
|
|
|
XmlElement* plistStringArray = dict->createNewChildElement ("array");
|
|
|
|
|
|
|
|
for (int i = 0; i < arrayElements.size(); ++i)
|
|
|
|
plistStringArray->createNewChildElement ("string")->addTextElement (arrayElements[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void deleteRsrcFiles() const
|
|
|
|
{
|
|
|
|
for (DirectoryIterator di (getTargetFolder().getChildFile ("build"), true, "*.rsrc", File::findFiles); di.next();)
|
|
|
|
@@ -981,6 +1064,9 @@ private: |
|
|
|
StringArray s (xcodeFrameworks);
|
|
|
|
s.addTokens (getExtraFrameworksString(), ",;", "\"'");
|
|
|
|
|
|
|
|
if (iOS && isInAppPurchasesEnabled())
|
|
|
|
s.addIfNotAlreadyThere ("StoreKit");
|
|
|
|
|
|
|
|
if (project.getConfigFlag ("JUCE_QUICKTIME") == Project::configFlagDisabled)
|
|
|
|
s.removeString ("QuickTime");
|
|
|
|
|
|
|
|
@@ -993,6 +1079,31 @@ private: |
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void addCustomResourceFolders() const
|
|
|
|
{
|
|
|
|
StringArray crf;
|
|
|
|
|
|
|
|
crf.addTokens (getCustomResourceFoldersString(), ":", "");
|
|
|
|
crf.trim();
|
|
|
|
|
|
|
|
for (int i = 0; i < crf.size(); ++i)
|
|
|
|
addCustomResourceFolder (crf[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void addCustomResourceFolder (String folderPathRelativeToProjectFolder, const String fileType = "folder") const
|
|
|
|
{
|
|
|
|
String folderPath = RelativePath (folderPathRelativeToProjectFolder, RelativePath::projectFolder)
|
|
|
|
.rebased (projectFolder, getTargetFolder(), RelativePath::buildTargetFolder)
|
|
|
|
.toUnixStyle();
|
|
|
|
|
|
|
|
const String fileRefID (createFileRefID (folderPath));
|
|
|
|
|
|
|
|
addFileOrFolderReference (folderPath, "<group>", fileType);
|
|
|
|
|
|
|
|
resourceIDs.add (addBuildFile (folderPath, fileRefID, false, false));
|
|
|
|
resourceFileRefs.add (createFileRefID (folderPath));
|
|
|
|
}
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
void writeProjectFile (OutputStream& output) const
|
|
|
|
{
|
|
|
|
@@ -1072,11 +1183,18 @@ private: |
|
|
|
sourceTree = "<absolute>";
|
|
|
|
}
|
|
|
|
|
|
|
|
String fileType = getFileType (path);
|
|
|
|
|
|
|
|
return addFileOrFolderReference (pathString, sourceTree, fileType);
|
|
|
|
}
|
|
|
|
|
|
|
|
String addFileOrFolderReference (String pathString, String sourceTree, String fileType) const
|
|
|
|
{
|
|
|
|
const String fileRefID (createFileRefID (pathString));
|
|
|
|
|
|
|
|
ScopedPointer<ValueTree> v (new ValueTree (fileRefID));
|
|
|
|
v->setProperty ("isa", "PBXFileReference", nullptr);
|
|
|
|
v->setProperty ("lastKnownFileType", getFileType (path), nullptr);
|
|
|
|
v->setProperty ("lastKnownFileType", fileType, nullptr);
|
|
|
|
v->setProperty (Ids::name, pathString.fromLastOccurrenceOf ("/", false, false), nullptr);
|
|
|
|
v->setProperty ("path", sanitisePath (pathString), nullptr);
|
|
|
|
v->setProperty ("sourceTree", sourceTree, nullptr);
|
|
|
|
@@ -1332,7 +1450,7 @@ private: |
|
|
|
ValueTree* const v = new ValueTree (createID ("__root"));
|
|
|
|
v->setProperty ("isa", "PBXProject", nullptr);
|
|
|
|
v->setProperty ("buildConfigurationList", createID ("__projList"), nullptr);
|
|
|
|
v->setProperty ("attributes", "{ LastUpgradeCheck = 0440; }", nullptr);
|
|
|
|
v->setProperty ("attributes", getProjectObjectAttributes(), nullptr);
|
|
|
|
v->setProperty ("compatibilityVersion", "Xcode 3.2", nullptr);
|
|
|
|
v->setProperty ("hasScannedForEncodings", (int) 0, nullptr);
|
|
|
|
v->setProperty ("mainGroup", createID ("__mainsourcegroup"), nullptr);
|
|
|
|
@@ -1415,6 +1533,23 @@ private: |
|
|
|
return getiOSAssetContents (images);
|
|
|
|
}
|
|
|
|
|
|
|
|
String getProjectObjectAttributes() const
|
|
|
|
{
|
|
|
|
String attributes;
|
|
|
|
|
|
|
|
attributes << "{ LastUpgradeCheck = 0440; ";
|
|
|
|
|
|
|
|
if (iOS && isInAppPurchasesEnabled())
|
|
|
|
{
|
|
|
|
attributes << "TargetAttributes = { " << createID ("__target") << " = { ";
|
|
|
|
attributes << "DevelopmentTeam = " << getIosDevelopmentTeamIDString() << "; ";
|
|
|
|
attributes << "SystemCapabilities = { com.apple.InAppPurchase = { enabled = 1; }; }; }; };";
|
|
|
|
}
|
|
|
|
|
|
|
|
attributes << "}";
|
|
|
|
return attributes;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
struct ImageType
|
|
|
|
{
|
|
|
|
@@ -1502,7 +1637,7 @@ private: |
|
|
|
return JSON::toString (var (v));
|
|
|
|
}
|
|
|
|
|
|
|
|
void createiOSAssetsFolder() const
|
|
|
|
void createXcassetsFolderFromIcons() const
|
|
|
|
{
|
|
|
|
const File assets (getTargetFolder().getChildFile (project.getProjectFilenameRoot())
|
|
|
|
.getChildFile ("Images.xcassets"));
|
|
|
|
|