18

I just want to output the version number of my ios application in a settings file.

I understand that I have to add the settings file to the application folder.

When I build and run I can see the 4 settings which come with the standard settings-bundle.

In order to get a simple read-only string I change the second value to the following

enter image description here

In code (didFinishLaunchingWithOptions:) I call the following:

NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
[[NSUserDefaults standardUserDefaults] setValue:version forKey:@"version_number"];
[[NSUserDefaults standardUserDefaults] synchronize];

To my surprise nothing happens at all. I just see the Group element and the toogle switch and the slider but no title line. Anybody knows what I am missing?

Thank you very much!

MrBr
  • 1,884
  • 2
  • 24
  • 38

6 Answers6

26

Ok, I had this problem too. The solution (sort of) was to provide a default value field and give that a value. This is actually explicitly stated in the documentation -- Default Value is a required field for the Title property, so if you don't specify it the title won't show in the settings pane. Unfortunately, I can't seem to CHANGE the value once it's set, possibly also as designed -- the documentation also states that it's a read only property. The solution I'm going to try is to just explicitly put the version number in my Root.plist file each time I make a new build. SUPER not ideal, but will work, I think.

EDIT: Check out this post on updating version number in settings bundle

EDIT: Ok, I got this working (thanks to that post, above, and a little hacking around with bash scripts, which I had very little experience with.) Here's the script (which I just wrote inline in a 'Run Script' build phase):

#!/bin/bash

builtInfoPlistPath=${TARGET_BUILD_DIR}/${INFOPLIST_PATH}

#increment the build number
buildNumber=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$builtInfoPlistPath")
buildNumber=$(($buildNumber + 1))
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$builtInfoPlistPath"

#compose the version number string
versionString=$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$builtInfoPlistPath")
versionString+=" ("
versionString+=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$builtInfoPlistPath")
versionString+=")"

#write the version number string to the settings bundle
#IMPORTANT: this assumes the version number is the first property in the settings bundle!
/usr/libexec/PlistBuddy -c "Set :PreferenceSpecifiers:0:DefaultValue $versionString" "Settings.bundle/Root.plist"

... and that's it! Works like a charm! Hope that helps with your problem, because it solved mine. Only problem now is a slight discrepancy with the build number ...

EDIT: ... which I fixed with vakio's second comment on this post, which instead sets the path to the info.plist to the one which is already processed (before the Run Script phase!)

EDIT: Here's my more up-to-date version, which is in an external file and verifies that some source files have changed before incrementing the build number:

 #!/bin/bash

 #note: for simplicity, it's assumed that there's already a bundle version (which is an integer) and a version string. set them in the Summary pane!

 #get path to the BUILT .plist, NOT the packaged one! this fixes the off-by-one bug
 builtInfoPlistPath=${TARGET_BUILD_DIR}/${INFOPLIST_PATH}
 echo "using plist at $builtInfoPlistPath"

 modifiedFilesExist=false
 #this is the modification date to compare to -- there's a possible bug here, if you edit the built plist directly, for some reason. probably you shouldn't do that anyways.
 compModDate=$(stat -f "%m" "$builtInfoPlistPath")

 for filename in *
 do
     modDate=$(stat -f "%m" "$filename")
     if [ "$modDate" -gt "$compModDate" ]
     then
         modifiedFilesExist=true;
         echo "found newly modified file: $filename"
         break
     fi
 done

 if $modifiedFilesExist
 then
     echo "A file is new, bumping version"

     #increment the build number
     buildNumber=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$builtInfoPlistPath")
     echo "retrieved current build number: $buildNumber"
     buildNumber=$(($buildNumber + 1))
     /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$builtInfoPlistPath"
     /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$INFOPLIST_FILE"

     #compose the version number string
     versionString=$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$builtInfoPlistPath")
     versionString+=" ("
     versionString+=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$builtInfoPlistPath")
     versionString+=")"

     #write the version number string to the settings bundle
     #IMPORTANT: this assumes the version number is the second property in the settings bundle!
     /usr/libexec/PlistBuddy -c "Set :PreferenceSpecifiers:1:DefaultValue $versionString" "Settings.bundle/Root.plist"
 else
     echo "Version not incremented -- no newly modified files"
 fi 
Community
  • 1
  • 1
samson
  • 1,152
  • 11
  • 23
  • I think I even saw a post on here somewhere about editing the settings bundle from a SCRIPT at BUILD TIME! That might be the best solution, if you can figure it out. If I find a solution, I'll post it. – samson Oct 31 '12 at 18:28
  • Thanks a lot - that brought me closer to the solution. There's just one error in the last line of the script ``Set: Entry, ":PreferenceSpecifiers:0:DefaultValue", Does Not Exist File Doesn't Exist, Will Create: Settings.bundle/Root.plist Command /bin/sh failed with exit code 1`` Any idea? – MrBr Nov 06 '12 at 12:03
  • hmm... I guess it means you don't have the Settings.bundle file in the same directory your .xcodeproj file... which I think is required anyway... – samson Nov 06 '12 at 22:25
  • I've actually since then moved that script into an external file, with some improvements -- I'll post it as an edit. THAT script NEEDS to be in the project's root directory (i.e. in the same directory as the .xcodeproj file) – samson Nov 06 '12 at 22:27
  • also possibly, your root.plist isn't named that? check in the Settings.bundle -- right click and select Show Package Contents (in Finder). or Settings.bundle isn't named that? – samson Nov 06 '12 at 22:36
  • Could you please explain where to put this script and how to connect it in xCode? Placing it in pre-action breaks the signature. Is that necessary to add new target to the project like in linked answer? – Yevhen Dubinin Dec 15 '13 at 19:44
  • @EugeneDubinin, I haven't used Xcode in a long time, but I think you just add a "Run Script" build phase to the target (from the Editor menu while viewing the target's "Build Phases" list). – samson Dec 23 '13 at 20:25
26

I had the same issue. To display Title property in the Settings.bundle you need also to add "Default Value" (it could be empty).

1) Right click on the Title object (in my case it is Item 0) and select Add Row.

2) In the drop down of the created row select "Default Value"

enter image description here

3) In didFinishLaunchingWithOptions set value to NSUserDefaults you want to display, for example:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setValue:@"0.0.1" forKey:@"appVersion"];
[defaults synchronize];

Result:

enter image description here

sash
  • 8,423
  • 5
  • 63
  • 74
3

You can sync the Settings bundle to NSUserDefaults, but weirdly it does not do at first. You have to first retrieve the values from Settings to NSUserDefaults and then after that, edit that you make to those values in NSUserDefaults are automatically applied to Settings bundle.

I referenced this nice article.

EDIT:

For your case to just to save your version, something like this would work. (This sample code is overkill in a way, but should be simpler to understand the flow)

//Get the bundle file
NSString *bPath = [[NSBundle mainBundle] bundlePath];
NSString *settingsPath = [bPath stringByAppendingPathComponent:@"Settings.bundle"];
NSString *plistFile = [settingsPath stringByAppendingPathComponent:@"Root.plist"];

//Get the Preferences Array from the dictionary
NSDictionary *settingsDictionary = [NSDictionary dictionaryWithContentsOfFile:plistFile];
NSArray *preferencesArray = [settingsDictionary objectForKey:@"PreferenceSpecifiers"];

//Save default value of "version_number" in preference to NSUserDefaults 
for(NSDictionary * item in preferencesArray) {
    if([[item objectForKey:@"key"] isEqualToString:@"version_number"]) {
        NSString * defaultValue = [item objectForKey:@"DefaultValue"];
        [[NSUserDefaults standardUserDefaults] setObject:defaultValue forKey:@"version_number"];
        [[NSUserDefaults standardUserDefaults] synchronize];
    }
}

//Save your real version number to NSUserDefaults
NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
[[NSUserDefaults standardUserDefaults] setValue:version forKey:@"version_number"];
[[NSUserDefaults standardUserDefaults] synchronize];    
barley
  • 4,403
  • 25
  • 28
  • Wooow! How is it possible that for something that simple so much code is needed... – MrBr Oct 17 '12 at 11:49
  • I have added a sample code. I haven't tested above myself, but the logic is there. (Also, I have tested similar code in different environment, and it worked.) – barley Oct 18 '12 at 05:21
  • Thanks for posting the code @barley. I copied it to the `didFinishLaunchingWithOptions` but to no avail. Also the compiler complains that there is no save operation that can be performed on `NSUserDefaults`... – MrBr Oct 18 '12 at 16:34
  • Ooops. I am sorry, I should call it `synchronize` instead of `save`. Did it crash there? – barley Oct 18 '12 at 16:56
  • No, it's just not building til the end as the compiler interupts the process – MrBr Oct 18 '12 at 16:57
  • Well I changed it to synchronize but it still doesn't show up in the settings... Is it that I have to chose Title as Type or is it a TextField which is set to readonly then? – MrBr Oct 18 '12 at 17:00
  • Ah, you are right. You have to change it to TextField. Title is a readonly field, which I didn't notice originally... – barley Oct 18 '12 at 17:24
  • Well, but what I want is a readonly field for the version number – MrBr Oct 19 '12 at 14:54
  • is it possible to change the bundle settings fields(like Textfield, Label) title in run time. – iPhone Guy Mar 12 '14 at 12:20
  • If it is not a readonly field like 'Title', you should be able to change the values that appears in Settings via approach I specified above. But you cannot directly edit the bundle itself at runtime, I believe. – barley Mar 12 '14 at 15:05
3

This worked for me :

Edit the Root.plist as source : Right click on the file and choose Open As->Source Code

Add the part for the title :

   <key>PreferenceSpecifiers</key>
        <array>
            <dict>
                <key>DefaultValue</key>
                <string>NoVersion</string>
                <key>Key</key>
                <string>Version</string>
                <key>Title</key>
                <string>Version</string>
                <key>Type</key>
                <string>PSTitleValueSpecifier</string>
            </dict>

And in - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

Add this:

NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
    [[NSUserDefaults standardUserDefaults] setObject:version forKey:@"Version"];

It will work after the app has been launched the first time.

Ralph
  • 716
  • 5
  • 9
0

Another solution, without writing swift code is :

1/ Create a Setting bundle

This will create a new section for your app in the Settings of your device

  • Right click your project name -> New File -> Settings Bundle

enter image description here

2/ Modify the Root.plist

This will set what you want to display in your app settings - Inside your new Setting Bundle, right click on the Root.plist -> Open As -> Source Code

  • Copy Paste the Following code in it :

    <?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>PreferenceSpecifiers</key>
         <array>
            <dict>
                <key>Title</key>
                <string>About</string>
                <key>Type</key>
                <string>PSGroupSpecifier</string>
            </dict>
            <dict>
                <key>DefaultValue</key>
                <string></string>
                <key>Key</key>
                <string>version_preference</string>
                <key>Title</key>
                <string>Version</string>
                <key>Type</key>
                <string>PSTitleValueSpecifier</string>
            </dict>
        </array>
        <key>StringsTable</key>
        <string>Root</string>
    </dict>
    </plist>
    

3/ Create a Run Script

This will always get the version of your app from the Info.plist and display it in your app Settings

  • Navigate to your Project Target on the left panel and click on Build Phases
  • Click the "+" button and hit "New Run Script Phase"
  • In the Newest Run Script section , Copy/Paste the following :

    #Getting Current Version
    VERSIONNUM=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString"   "${PROJECT_DIR}/${INFOPLIST_FILE}")
    
    #Setting the version number in settings
    /usr/libexec/PlistBuddy -c "Set PreferenceSpecifiers:1:DefaultValue $VERSIONNUM" <YOUR-APP-NAME>/Settings.bundle/Root.plist
    
  • Just replace YOUR-APP-NAME by your app name

Ugo Marinelli
  • 989
  • 1
  • 11
  • 18
-1

NSUserDefaults doen't write any values to a settings file. It will only save data in the user defaults plist in your app.

To save something in another file you will have to write this your own. Maybe this will help:

iOS - How to write a data in plist?

Community
  • 1
  • 1
app_
  • 697
  • 5
  • 12