Is there any way to display the CFBundleShortVersionString as a UILabel text in my LaunchStoryboard without entering it by hand every time it increments? I know how to do it in code, but it is not possible to run code while the LaunchStoryboard is shown.
Is it possible through Xcode variables?
As we all know, you can't put code in a launch screen. And unfortunately there isn't a built-in way to use a variable for a label's text in the launch screen (similar to how you can preprocess Info.plist with values in a header file).
The only option available to achieve your goal would be to write your own script that updates the LaunchScreen.storyboard file and add that script as a custom Build Phase for your target.
To make this easier, I would setup your target to use a preprocessor file for Info.plist. Once that is done and working, you now have a separate and simple header file you can interrogate in your script to process the LaunchScreen.storyboard file.
Here's a complete solution:
Create a file named Info.h and add it to the root of your project.
Add the following line:
#define APP_VERSION 2.6 // Update this version as needed
Now select your project's target in Xcode and go to the General tab. Change the Version value from whatever number you have there to APP_VERSION.
Now select the Build Settings tab. Search on Info. Under the Packaging section, set the Preprocess Info.plist File to Yes. Also set the Info.plist preprocessing Prefix File to Info.h.
Now when you do a build, the CFBundleShortVersionString value in Info.plist will be set to the value in the Info.h file.
To get the label in the launch screen file updated to match, do the following:
Select your launch screen storyboard and then select the label that will contain the version number. Show the Identity Inspector pane. Enter APP_VERSION into the Label attribute. If you look at the storyboard file now, the XML for the label will now show a userLabel attribute with the value of APP_VERSION.
Go back to the project target and select the Build Phases tab. Click the + icon and choose to add a New Run Script Phase. Rename the new phase to something useful like "Update Launch Version". Then drag the new phase to before the existing "Copy Bundle Resources" phase.
Now open the new "Update Launch Version" phase. Enter /bin/bash in the Shell field. Copy and paste the following code into the phase:
VERSION=`cat Info.h | grep APP_VERSION | cut -f3 -d' '`
sed -e "/userLabel=\"APP_VERSION\"/s/text=\"[^\"]*\"/text=\"$VERSION\"/" Storyboard.storyboard > tmp.storyboard
Now do a clean build. This is a test at this point. Have a look at tmp.storyboard and make sure it looks correct and the label for the app version is showing the proper version.
Once that is working, update the above code to:
VERSION=`cat Info.h | grep APP_VERSION | cut -f3 -d' '`
sed -i bak -e "/userLabel=\"APP_VERSION\"/s/text=\"[^\"]*\"/text=\"$VERSION\"/" Storyboard.storyboard
This final version actually updates the launch screen storyboard file. The previous version was a test to make sure everything else was working without risk to trashing your storyboard.
I figured out the script to update the Version & Build label on LaunchScreen.storyboard based on the first answer without using any extra files. Unfortunately, Clemens Brockschmidt's solution doesn't work due to some Syntax errors and incorrect paths.
Make sure to name your label to "APP_VERSION" in Identity Inspector pane -> Document -> Label.
Also create your script before "Copy Bundle Resources" phase.
UPDATE: My older answer didn't work in the newest Xcode environment. I've fixed the current issues and refactored the script.
And here's the final working script with shell: /bin/sh in XCode 11 (Swift 5):
# ON/OFF Script Toggle (script ON with #, script OFF without #)
#exit 0
# Increment Build Number Bool (Increment ON with true, increment OFF with false)
shouldIncrement=false
# App vesion / Build version constants
sourceFilePath="$PROJECT_DIR/$PROJECT_NAME/Base.lproj/LaunchScreen.storyboard"
versionNumber="$MARKETING_VERSION"
buildNumber="$CURRENT_PROJECT_VERSION"
# Increment build number
if [ "$shouldIncrement" = true ]; then
buildNumber=$(($buildNumber + 1))
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$INFOPLIST_FILE"
fi
# Output version & build numbers into a label on LaunchScreen.storyboard
sed -i .bak -e "/userLabel=\"APP_VERSION\"/s/text=\"[^\"]*\"/text=\"$versionNumber($buildNumber)\"/" "$sourceFilePath"
As a BONUS I've included a build number incrementer and ON/OFF script toggle to disable your incrementer when you build your project a lot. Let me know if you have any issues or if this works for you.
Edit
There is no way to make the launch screen dynamic. Doesn't work good
with localizations too etc
A alternative is given below
Previous Answer
You should make your inital VC similar to the LaunchScreen.xib and in that make a label.
Now in the ViewController you can access the info plist through NSBundle method and set its value. This would make the transition from Launch screen to first VC smooth and look natural with version code animating in or something if you want
let appVersion = NSBundle.mainBundle().infoDictionary["CFBundleVersion"];
myLabel.text = "\(appVersion)"
Related
I have a project with multiple targets, which represent the same app just with different styling and translations.
Since almost whole project looks the same for each target, I need to have just few strings in Localizable.strings file, that I need to be different. And I don't want to copy whole huge Localizable.strings file to each project just because of the fact it has few lines different.
It is required for me to have just 1 strings file because of third-party libraries/SDK that are included in project. So I cannot use tableName for localizedString.
The problem is - I need to have a flexible possibility to override just few lines from Localizable.strings for each target separately. And I don't like the idea just to copy whole file to each target, cause it will lead to annoying flow in the future, in case I will have 10 targets and I need to add 1 string to all of them.
The goal is to have 1 huge Localizable.strings file with all strings included, that would be common for all targets, and have small configuration for each target for the strings that should tell different. So target's file should kinda merge and override the one that is common.
AFAIK it is not natively supported by Xcode, so I'm probably looking for a script that would make it works.
So, script should look into common and target's Localizable files, merge them, and in case some keys are defined in both, then it should use the one from target's file.
Can anyone help me with such script?
P.S. Similar issue exists with .xcassets, and CocoaPods solves it by merging multiple assets into 1, and it works as expected - if some targets has an asset containing the image with the same name that is already included into a common asset, then the one from target will replace it.
P.S.2. Similar feature is natively supported for Android devs - each image, each translations can be overridden by "child" flawor, or whatever it is called :)
TL;DR:
Example project: https://github.com/JakubMazur/SO45279964
OK, the easier thing to do would be shell/python script, because it will work for every build server. I assume that you have a different scheme for each target (otherwise it will make no sense). So what you can do is:
Let's say your target is named:
target1
target2
target3
1) Create separate files contains all the strings that should be different (i will put it under Localizable directory.
Your Localizable.strings file may look like this:
"someGeneralString" = "General string 1";
"AppName" = "This is a string that you probably need to change";
"someOtherGeneralString" = "General string 2";
And any of your targetX.strings file may look like this:
"AppName" = "target[x]"
And here is how it should look like in your project:
Note that your target localizable files should has target membership set only to one target, but your Localizable.strings should be for all targets!
That's all for project configuration. Let's go to scripting (I will use python for that):
#!/usr/bin/python
import sys
supportedLanguages = ["en","pl"]
commonPath = ".lproj/Localizable.strings"
keys = ["AppName"]
class CopyLocalizable():
target = ""
def __init__(self,arg):
self.target = arg
self.perform()
def perform(self):
for lang in supportedLanguages:
pathToLocalizable = lang+commonPath
textToFile = ""
with open(pathToLocalizable,"r") as languageFile:
for line in languageFile.readlines():
for key in keys:
if key in line:
textToFile += self.foundAndReplace(key,lang)
else:
textToFile += line
self.saveInFile(pathToLocalizable,textToFile)
def foundAndReplace(self,key,lang):
pathToTargetFile = "Localizable/"+lang+".lproj/"+self.target+".strings"
with open(pathToTargetFile,"r") as targetFile:
for targetLine in targetFile.readlines():
if key in targetLine:
return targetLine
def saveInFile(self,file,stringToSave):
with open(file,"w+") as languageFile:
languageFile.write(stringToSave)
You can optimize it yourself. It's easier script i can think about to get a job done.
And in the end let's automate it a bit:
- Go to your target
- add a new build phase
- Add a new script:
export PATH="/usr/local/bin:$PATH"
cd SO45279964/
python localize.py target[x]
and watch a magic happen ;)
http://www.giphy.com/gifs/26n6NKgiwYvuQk7WU
Here you can find example project that I've created to run this example:
https://github.com/JakubMazur/SO45279964
To keep it simple, Have a Macro defined for each target in Build Settings & define target specific strings within macro section like
#ifdef __TARGET__
//key values in localizable file
#endif
Problem statement:- I am using third party library, which need to have config.plist(name must be same), Config.plist have several key-value pairs, for production,Dev and stating environment, values for key does change.
Right now i need to manually change file Name, like if i need to run app in production environment, i do change file name ConfigPROD.plist into Config.plist,
for Staging i do change file name from ConfigDev.plist into Config.plist.
Need Solution like:- can i change, file content using preprocessor macro, like
#if isProductionBuild == 1
#define kBaseURL #"https://api.base.com/bart/"
#define kEnvironmnet #"PROD"
***//copy ConfigPROD.plist file content into Config.plist***
#elif isStageBuild == 1
#define kBaseURL #"https://api-stage.base.com/bart/"
#define kEnvironmnet #"stg"
*//copy ConfigSTG.plist file content into Config.plist*
#else
#define kBaseURL #"https://api-dev.base.com/bart/"
#define kEnvironmnet #"dev"
*//copy ConfigDEV.plist file content into Config.plist*
#endif
pleae see if we do have feature like "copy ConfigDEV.plist file content into Config.plist"
Note:- This file copy operation should get completed before third party library start using Config.plist.
You don't need a preprocessor macro, you need a Run Script build phase that gets executed first. See Running a Script While Building a Product for an introduction. Just create a Bash script that looks at $CONFIGURATION (which will be Debug or Release or any other configuration name you have) and then renames one or the other file. Something like:
mv ./Config.plist ./Config.plist.orig
if [[ 'Release' == ${CONFIGURATION} ]]; then
cp ./Config.PROD.plist ./Config.plist
else
cp ./Config.DEV.plist ./Config.plist
To easily handle this, add all 3 files (Config.plist, Config.DEV.plist and Config.PROD.plist) to Xcode, but only the first one should be added to the target (check the box in the right side panel). You can have default settings in this file, it will be overwritten anyway.
If you're using git or something similar, you can add another run script phase at the end of your build to restore the original Config.plist to hide your changes from git.
I know that this is not a new question, but I have tried many solution like edit Xcode project file manually or recreating new scheme for target after renaming it.
Can somebody help me with this question how copy target in right way and then rename it.
Right now I just rename scheme and it is displayed as is. But when Xcode is running app, under progress indicator it says: Running ProjectName-copy. But I have renamed target but seems product names or some build settings still has "...-copy" in the name.
Duplicate a target.
Double click the copy of target and change its name to whatever you want.
'New target -> Build Settings -> Packaging -> Product Name', change it to the name you want.
Product (Menu item) > Schemes > Manage Schemes, then click the '+' button, select the new target and click 'OK' button.
All done.
PS:
After Duplicating the target, Xcode make a new Info.plist file named 'xxx copy-Info.plist' in the root path of project. If you want to change its name and path, after doing this, you should go to 'New target -> Build Settings -> Packaging -> Info.plist file' and change the value to the path of new file.
Press on the target name in xcode ( in the same place where you want to select the sim/device type) and select "Manage Schemes...". Highlight the scheme with the copy inside name and press enter on keyboard. You can rename it there.
To copy a target, click on your project, and go to the project inspector tab that looks like this:
Project:
SomeName
Targets:
SomeName
right click on a target, and select duplicate, then double-click slowly on the new target, or select the new target and hit enter, and just put the new name in!
To change running targets, to the top left of your xcode you will see this:
(start button) (stop Button) (target name and device)
[|>] [[]] [/\ TargetName > TargetDevice]
click the target name, next to the little compass (mathematical compass, not navigational) and select your desired target from the dropdown.
Hope I helped, Matroskin.
I'm'developing a Unity3D game that shows a different (localized) app name in the iPhone's home screen according to the user local language. Note that:
I already know how to localize the iOS app name by editing the Xcode project (create a InfoPlist.string file, localize it, add the CFBundleDisplayName key to it, etc.)
I also know how automatically localize an Android app name within the Unity editor (add a values-XX.xml file with the app_name property onto Assets/Plugins/Android/res/ folder, etc.)
The question is: how can I automatically localize my iOS app name within the Unity Editor so that I don't need to perform the error-prone task 1. every time I build the project?
I think that PostprocessBuildPlayer should be the way to go, however I haven't found any documentation on how to parse it and/or modify the Xcode project file correctly to achieve this.
Long time ago I ran into trouble when I tried to modify info.plist via the Build Player Pipeline especially when doing it in Append mode. It works only once and then subsequent builds fail with "The data couldn’t be read because it isn’t in the correct format." (s. Unity forum posts like this one and my blog posting about this problem) So I decided to take the alternative way combining a customised build with an Xcode Build Pre-action.
Three steps are required:
(1) Xcode setup:
In Xcode go to Edit Scheme / Build / Pre-actions. Then click the + sign to add a New Run Script Action.
In Provide build settings select Unity-iPhone.
Paste . ${PROJECT_DIR}/modify_info_plist.sh (note the dot and blank at the beginning, is ensures that the script is executed in the caller's shell)
So it should look like this:
(2) Script modify_info_plist.sh:
Within your script you have access to all environmet variables from Xcode (s. Xcode Build Setting Reference) and you can manipulate Info.plist using the defaults command (man page). Here is a sample I used to add gyroscope to the UIRequiredDeviceCapabilities:
# Code snippet used in Unity-iPhone scheme as "Build Pre-Action"
my_domain=${PROJECT_DIR}/Info.plist
status_bar_key=UIViewControllerBasedStatusBarAppearance
logger "Start adding keys to info.plist"
defaults write $my_domain $status_bar_key -boolean NO
if [ `defaults read $my_domain UIRequiredDeviceCapabilities | grep "gyroscope" | wc -l` = "0" ]; then
defaults write $my_domain UIRequiredDeviceCapabilities -array-add "gyroscope"
fi
logger "Keys added to info.plist successfully"
(3) Build Pipeline:
Put the following code in a static editor class to create a new menu item Tools / My iOS Build with shortcut cmd+alt+b:
static string IOSBuildDir= "Develop";
[MenuItem("Tools/My iOS Build %&b")]
public static void IOSBuild () {
string[] levels = { "Assets/Scenes/Boot.unity",
"Assets/Scenes/Level-1.unity",
// ...
"Assets/Scenes/Menu.unity"
};
string path = Directory.GetCurrentDirectory ();
path += "/" + IOSBuildDir + "/Info.plist";
if (File.Exists (path)) {
Debug.Log ("Removing file " + path);
File.Delete (path);
}
BuildPipeline.BuildPlayer (levels, "Develop", BuildTarget.iPhone,
BuildOptions.AcceptExternalModificationsToPlayer);
}
I know this is no perfect solution but it's the only one I found to work stable. Two drawbacks:
Step (1) has to be repeated after major Xcode format changes
New scenes have to be appended in the editor class code in step (3)
My iOS application uses a number of third party components licensed under Apache 2.0 and similar licenses, which requires me to include various bits of text, this kind of thing:
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
There seems to be a reasonable precedent for putting this information under a 'License' subentry in settings bundle (on the ipad facebook, pages, keynote, numbers and wikipanion all seem to do this).
I'm struggling a bit to actually achieve the same though; I seem to need to split the text up line by line and enter into xcode a line at a time (and xcode4 seems to have a crashing problem when editing the plists).
It seems like the kind of thing that there's almost certainly a somewhere script to do, or some simple way to do it that I've missed.
I think I've now managed to solve all the problems I was running into.
It seems to be best to use group element titles to hold the licenses (this is what Apple do in the iWork apps). There is however a limit on the length of these (and I've not yet discovered exactly what the limit is), so you need to break each license file into multiple strings.
You can create a line break within these by include a literal carriage return (ie. otherwise known as ^M, \r or 0x0A)
Make sure not to include any literal "s mid-text. If you do, some or all of the strings in the file will get silently ignored.
I've got a convenience script I use to help generate the .plist and .strings file, shown below.
To use it:
Create a 'licenses' directory under your project
Put script into that directory
Put each license into that directory, one per file, with filenames that end .license
Perform any necessary reformatting on the licenses. (eg. remove extra spaces at the beginning of lines, ensure that there are no line breaks mid-paragraph). There should be a blank line in-between each paragraph
Change to licenses directory & run the script
Edit your settings bundle Root.plist to include a child section called 'Acknowledgements'
Here's the script:
#!/usr/bin/perl -w
use strict;
my $out = "../Settings.bundle/en.lproj/Acknowledgements.strings";
my $plistout = "../Settings.bundle/Acknowledgements.plist";
unlink $out;
open(my $outfh, '>', $out) or die $!;
open(my $plistfh, '>', $plistout) or die $!;
print $plistfh <<'EOD';
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>StringsTable</key>
<string>Acknowledgements</string>
<key>PreferenceSpecifiers</key>
<array>
EOD
for my $i (sort glob("*.license"))
{
my $value=`cat $i`;
$value =~ s/\r//g;
$value =~ s/\n/\r/g;
$value =~ s/[ \t]+\r/\r/g;
$value =~ s/\"/\'/g;
my $key=$i;
$key =~ s/\.license$//;
my $cnt = 1;
my $keynum = $key;
for my $str (split /\r\r/, $value)
{
print $plistfh <<"EOD";
<dict>
<key>Type</key>
<string>PSGroupSpecifier</string>
<key>Title</key>
<string>$keynum</string>
</dict>
EOD
print $outfh "\"$keynum\" = \"$str\";\n";
$keynum = $key.(++$cnt);
}
}
print $plistfh <<'EOD';
</array>
</dict>
</plist>
EOD
close($outfh);
close($plistfh);
Setting up your Settings.bundle
If you haven't created a Settings.bundle, go to File --> New --> New File...
Under the Resource section, find the Settings Bundle. Use the default name and save it to the root of your project.
Expand the Settings.bundle group and select Root.plist. You will need to add a new section where its key will be Preference Items of type Array. Add the following information:
The Filename key points to the plist that was created by this script. You can change the title to what ever you want.
Execute Script At Build Time
Also, if you want this script to run whenever you build your project, you can add a build phase to your target:
Go to your project file
Select the target
Click the Build Phases tab
In the lower right corner of that pane, click on 'Add Build Phase'
Select 'Add Run Script'
Drag and drop your perl script into the section for your script. Modify to look something like this:
cd $SRCROOT/licenses ($SRCROOT points to the root of your project)
./yourScriptName.pl
After you have finished that, you can drag the Run Script build phase sooner in the build process. You'll want to move it up before Compile Sources so that the updates to your Settings Bundle get compiled and copied over.
Update for iOS 7: iOS 7 seems to handle the "Title" key different and is messing up the rendered text. To fix that the generated Acknowledgements.plist needs to use the "FooterText" key instead of "Title". This how to change the script:
for my $str (split /\r\r/, $value)
{
print $plistfh <<"EOD";
<dict>
<key>Type</key>
<string>PSGroupSpecifier</string>
<key>FooterText</key> # <= here is the change
<string>$keynum</string>
</dict>
EOD
print $outfh "\"$keynum\" = \"$str\";\n";
$keynum = $key.(++$cnt);
}
Here's the same solution that #JosephH provided (without translations), but done in Python for anyone who prefers python over perl
import os
import sys
import plistlib
from copy import deepcopy
os.chdir(sys.path[0])
plist = {'PreferenceSpecifiers': [], 'StringsTable': 'Acknowledgements'}
base_group = {'Type': 'PSGroupSpecifier', 'FooterText': '', 'Title': ''}
for filename in os.listdir("."):
if filename.endswith(".license"):
current_file = open(filename, 'r')
group = deepcopy(base_group)
title = filename.split(".license")[0]
group['Title'] = title
group['FooterText'] = current_file.read()
plist['PreferenceSpecifiers'].append(group)
plistlib.writePlist(
plist,
"../Settings.bundle/Acknowledgements.plist"
)
As an alternative, for those using CocoaPods, it will generate an 'Acknowledgements' plist for each target specified in your Podfile which contains the License details for each Pod used in that target (assuming details have been specified in the Pod spec). The property list file that can be added to the iOS settings bundle.
There's also projects under way to allow this data to be converted and displayed within the app instead:
https://github.com/CocoaPods/cocoapods-install-metadata
https://github.com/cocoapods/CPDAcknowledgements
I thought I'd throw my iteration on Sean's awesome python code in the mix. The main difference is that it takes an input directory and then recursively searches it for LICENSE files. It derives the title value from the parent directory of the LICENSE file, so it plays well with cocoapods.
The motivation was to create a build script to automatically keep the legal section of my app up to date as I add or remove pods. It also does some other things like remove forced newlines from licenses so the paragraphs look a bit better on the devices.
https://github.com/carloe/LicenseGenerator-iOS
I made a script in Ruby inspiered by #JosephH script.
This version will, in my own opinion, better represent the individual open source projects.
Wisit iOS-AcknowledgementGenerator to download the script and sample project.
This is what acknowledgements will look like in your App:
This is an addendum to JosephH's answer. (I don't have the rep to comment)
I had to move
<key>StringsTable</key>
<string>Acknowledgements</string>
down to above the last </dict> in the Perl script.
Before this modification, the Acknowledgements Section in the App was empty and XCode couldn't read the resulting Acknowledgements.plist. ( "The data couldn’t be read because it isn’t in the correct format.")
(XCode 6.3.2 iOS 8.3)
The Python script from Sean in this thread works. But there a couple of basic things to know.
in Xcode, right click on the top of the Project Navigator tree, on the name of your project, and add a New Group. This puts a new folder in your project.
Add Sean's script there and make sure to save it as: Acknowledgements.py.
Make sure you have Python installed on your system. I'm using a Mac.
Add a first license file to the folder you created in 1. Make it simple like just having one word in the file, say: Testing. Save it in the folder as Test1.license.
Set up your Settings.bundle as per JosephH above.
Use your Terminal app to CD to the folder you created in 1.
Run the script. Type: python Acknowledgements.py. If you get no errors it will return right back to the Terminal prompt. Do all of this before adding any run script to the Build.
Build and run your app.
Double tap on the iPhone home button and kill Settings. It doesn't often pick up the Settings change for your app until Settings restarts.
After restarting Settings, go to your app and look to see if it worked.
If that all worked, slowly add more license files but run the script each time. You can get errors running the script because of certain characters in the file so the easy way to debug is to add a file, run the script, see if it worked and proceed. Else, edit any special characters out of the .license file.
I did not get the Run Build Script work per the instructions above. But this process works fine if you are not changing the .license files that often.
Ack Ack: Acknowledgement Plist Generator
A while back I've created a Python script that scans for license files and creates a nice Acknowledgements plist that you can use in your Settings.plist. It does a lot of the work for you.
https://github.com/Building42/AckAck
Features
Detects Carthage and CocoaPods folders
Detects existing plists for custom licenses
Cleans up the license texts by removing unnecessary new lines and line breaks
Provides many customization options (see --help for details)
Supports both Python v2 and v3
Install
wget https://raw.githubusercontent.com/Building42/AckAck/master/ackack.py
chmod +x ackack.py
Run
./ackack.py
Screenshot
If you have suggestions for improvements, feel free to post an issue or pull request on GitHub!
Aknowlist is a strong CocoaPod candidate that is actively maintained at the time of this writing. It automates this licensing if you are okay with housing the licenses in your app rather than the settings bundle. It worked great for the project I was working on.
I had to modify sean's script for modern python3:
import os
import sys
import plistlib
from copy import deepcopy
os.chdir(sys.path[0])
plist = {'PreferenceSpecifiers': [], 'StringsTable': 'Acknowledgements'}
base_group = {'Type': 'PSGroupSpecifier', 'FooterText': '', 'Title': ''}
for filename in os.listdir("."):
if filename.endswith(".license"):
with open(filename, 'r') as current_file:
group = deepcopy(base_group)
title = filename.split(".license")[0]
group['Title'] = title
group['FooterText'] = current_file.read()
plist['PreferenceSpecifiers'].append(group)
with open("Acknowledgements.plist", "wb") as f:
plistlib.dump(plist, f)
Wait, license notation is not a setting.
Edit:
I think license notice is not a setting. I think it is irrational to expect users who want to check the license notice to open the settings app. Therefore, I thought we should create a page for the license notice in the appropriate place in the app.