Creating Aggregate Projects on Theos: A Configurable Tweak.
More often than not, you will want to create a Theos project that is composed of one or various subprojects. A very common case for this would be to write a Tweak that can be configured by the user (guess what we’re writing today…): You’d create a Tweak project that contains a PreferencesLoader subproject that is in charge of writing the tweak’s behaviour. Aggregrate projects with Theos are very easy to build.
In this tutorial we’re going to write an Aggregate project that does just that: We will write a very simple tweak with a simple PreferencesLoader that will decide whether the tweak should alert the user when the app has been launched or not.
You have seen this simple tweak in action before: I wrote it in my Writing a MobileSubstrate Tweak for iOS tutorial. In fact to save us the time, I will assume you followed that tutorial, and that you managed to compile it and run it. We will write the same tweak with a twist. In that way we will save us the trouble of writing a MobileSubstrate tweak and get straight to the point of this tutorial.
In this tutorial…
- You’ll learn the basics of a PreferencesLoader project (Note that this is NOT the same a Settings.bundle found in App Store apps, but they can share similarities).
- You will learn to write aggregate projects with Theos. An aggregate project is a project that contains one or more sub projects.
The following assumptions are made:
- You know how to write iOS apps. You know the SDK and you have written an app. It doesn’t matter if said app has been published. You have the necessary skills to write an iOS app.
- You know how a Settings.bundle works and its limitations for iOS Apps. You also know how to make bundles and edit their plists manually.
- You know how to write Mobile Substrate tweaks. If you don’t, head over to my tutorial to get started…
Alright, without further ado, let’s get started!
Writing The Mobile Substrate Tweak: Aggregate Projects Don’t Come Alone!
Since we’re going to build this project on top of an old tweak from an old tutorial, go build the tweak described in the tutorial linked above. It shouldn’t take you long (it’s literally less than 15 lines long), so go ahead and build it. I will wait for you.
Got it up and running? Cool, let’s continue.
Discussion on PreferencesLoader: The Most Common Use For Aggregate Projects
PreferencesLoader was written by DHowett as a way to let developers create settings pages for their apps and tweaks in the Settings app. PreferencesLoader can be very different than the standard Settings.bundle you’re used to.
When it comes to PreferencesLoader, there are two approaches to fetch to create a settings page for your app: The simple approach and the PreferencesLoader approach.
The Simple Approach
The simple approach is very similar to a Settings.bundle in that the cells are loaded from a plist file stored in /Library/PreferenceLoader/Preferences/
. This plist contains the specifiers that your app requires. These can be simple cells that contain switches, text, text fields, and so on. The specifiers are loaded from their respective files and using the simple approach your bundle doesn’t execute code. It just loads and saves the values in the modifiable fields of the bundle.
The PreferenceBundle Approach.
Unlike the static settings the Simple Approach offers, this approach actually allows your settings bundle to execute code. The entry plist for this approach must have the same name of the PreferenceBundle to run and define it’s isController option to true. You can show your own view and everything you need. I will cover the PreferenceBundle Approach in another tutorial.
For more information on PreferenceLoader, refer to the iPhoneDevWiki page on the topic.
Creating Aggregate Projects: Getting Our Hands Dirty.
Now you have the tweak that alerts the user when an app has been launched. This is cool, very cool, but what if we wanted the user to have the ability to turn it off and on as he pleases? We need to write a settings page for it. But it wouldn’t make sense to write a different project for it. Does it make sense to ask the user to install the tweak, and then separately install it’s settings page? I don’t think so.
Aggregate projects come to the rescue. We’re going to create the settings project for our tweak, and they will be installed at the same time, since the settings project will be a subproject of the main project! But how do we do that? There is no template or anything to simplify the job of creating an aggregate project.
You don’t really need to do much to create an aggregate project. In fact, it’s just a matter of adding two extra lines to your makefile and that’s it.
But we don’t have the preferences project set up yet. In your terminal window, cd to your current project’s directory and run Theos’ nic.pl to create a new project. Select “iphone/preference_bundle” and finish the wizard.
If you’re lucky, Theos will automatically detect you created a project inside another one, and it will modify the makefile for now. If it doesn’t, don’t worry. Simply add the following two lines to it and you will be fine:
SUBPROJECTS += notifiersettings include $(THEOS_MAKE_PATH)/aggregate.mk
It’s important the SUBPROJECTS
specifier is before the include of the aggregate makefile.
Wiring Things Up: The Core Of Aggregate Projects.
make package install
your project now. Open the settings app. Apart from our nice alert that tells the user Settings has been launched, if you scroll down a bit you should see a cell containing our settings preferences name for our tweak. Open it up and you should see this:
If you change the switch and go back, you will see its current state was automatically saved by our good friend PreferenceLoader. At least with saving settings, you don’t have to worry about that if you use the simple approach.
Naturally now we gotta modify its strings so it makes sense to the user.
Go to the PreferencesLoader subproject. Now open its Resources
folder and open the plist there (NOT the one called Info.plist – the other one that has then name of your project). Since you know how to modify these, just change the name of the label to something like “Alert user on app launch”:
<?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>items</key> <array> <dict> <key>cell</key> <string>PSGroupCell</string> <key>label</key> <string>Notifier Settings.</string> </dict> <dict> <key>cell</key> <string>PSSwitchCell</string> <key>default</key> <true/> <key>defaults</key> <string>com.AndyIbanez.NotifierSettings</string> <key>key</key> <string>alertLaunch</string> <key>label</key> <string>Alert User on App Launch</string> </dict> </array> <key>title</key> <string>NotifierSettings</string> </dict> </plist>
Now we just have to modify the actual tweak to read that value and alert the user if he so wishes (AKA if alertLaunch
is YES).
With jailbreak apps you don’t have many “commodities” as you do with App Store apps, so you can’t use the same methods you’d use in App Store apps to read settings from a plist.
Instead, we have to read the plist manually, fetch the values we have to check for manually, and do whatever we want with those values.
You should know this by now, but in case you don’t remember, just create an NSDictionary
object and use initWithContentsOfFile
or dictionaryWithContentsOfFile
. The full path to your settings plist is /var/mobile/Library/Preferences/YOUR_PLIST.plist.
In other words, your reading code would look like this:
NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithContentsOfFile: [NSString stringWithFormat:@"%@/Library/Preferences/%@", NSHomeDirectory(), @"com.AndyIbanez.NotifierSettings.plist"]];
(Note the use of NSHomeDirectory(). The iPhoneDev Wiki which is maintained by the guys behind our awesome jailbreak development tools recommend we never refer to /var/mobile directly, and that we should use NSHomeDirectory() instead.)
With that in mind, then you just read the value you want (alertLaunch in my case and probably yours too), and use it accordingly. When done, your whole method should look like this:
-(void)launch { NSString *appName = [self displayName]; NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithContentsOfFile: [NSString stringWithFormat:@"%@/Library/Preferences/%@", NSHomeDirectory(), @"com.AndyIbanez.NotifierSettings.plist"]]; NSNumber* shouldNotify = [settings objectForKey:@"alertLaunch"]; if([shouldNotify boolValue] == YES) { NSString *message = [NSString stringWithFormat:@"The app %@ has been launched", appName, nil]; UIAlertView *alert1 = [[UIAlertView alloc] initWithTitle:appName message:message delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert1 show]; [alert1 release]; } %orig; }
And you’re done! Try it now. When the switch is on, you’ll be alerted when an app has launched. When it’s off, it will do the opposite.
You can downloade the source code for the project in my Github here. Study it and improve it, break it, etc..
If you have any questions or comments, you can as usual use the comments section below.
CHANGELOG:
_____________
May 10, 2013 at 6:00 PM [America/La_Paz]: First version of this post.
May 12, 2012 at 2:00 PM [America/La_Paz]: I forgot to add the source code for the settings plist. It’s added now.
Hi have used your tutorial and theos to build this tweak. This process does so successfully and when i place it on my iPhone running iOS 7.0 it installs fine with no errors. I go into may settings page and see the NotifierSettings page has been added with the switch allowing to turn the UIAlert On or Off. However when the switch is On And I press on a app the UIAlert never appears to show.
If you’re compiling for 64-Bit devices this is likely to happen. I’m assuming you’re compiling for the iPhone 5S?
Add this line at the top of your project makefile:
This will work as long as you don’t have to support iOS 4.2.1, that should work.
Now i seem to be receiving this error in the terminal when trying to compile…
llvm-g++-4.2: Invalid arch name : arm64
make[2]: *** [obj/Tweak.xm.c2d4d5e5.o] Error 1
make[1]: *** [internal-library-all_] Error 2
make: *** [LaunchNotifier.all.tweak.variables] Error 2
And I have tried your tweak original code from your Github project on both my iPhone 4 Running IOS7 and My iPod Running IOS 5.0 and is still not working! This is without this line ‘ARCHS = armv7 arm64’ because as stated in my last comment i am receiving compile errors
Do you have the latest XCode and have you installed all its command line tools?
Right I installed the latest version of Xcode and tried to compile the Tweak twice. Once with “ARCHS = armv7 arm64” and the other without it. Here is the error received without:
make[3]: Nothing to be done for `internal-bundle-compile’.
Making stage for tweak LaunchNotifier…
Making stage in notifiersettings…
Making stage for bundle NotifierSettings…
dpkg-deb: building package `com.andyibanez.launchnotifier’ in `./com.AndyIbanez.launchNotifier_0.0.1-14_iphoneos-arm.deb’.
make: *** [internal-package] Error 2
Here is the error received with:
ld: warning: ignoring file /Users/Thomas/Downloads/TutorialProjects-master/launchnotifier/theos/lib/libsubstrate.dylib, missing required architecture arm64 in file /Users/Thomas/Downloads/TutorialProjects-master/launchnotifier/theos/lib/libsubstrate.dylib (2 slices)
Undefined symbols for architecture arm64:
“_MSHookMessageEx”, referenced from:
_logosLocalInit() in Tweak.xm.a3b00512.o
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[2]: *** [obj/LaunchNotifier.dylib.ba964c90.unsigned] Error 1
make[1]: *** [internal-library-all_] Error 2
make: *** [LaunchNotifier.all.tweak.variables] Error 2
A quick Google of portion of the error message yielded me this link:
https://github.com/DHowett/theos/issues/23
Give it a try.
Right i checked out the link you pointed me to and downloaded a new MobileSubstare dylib file an placed within theos/lib where i replace the old ones with the one just downloaded. However I am Receiving this error:
ld: warning: ignoring file /Users/Thomas/Downloads/TutorialProjects-master/launchnotifier/theos/lib/libsubstrate.dylib, missing required architecture arm64 in file /Users/Thomas/Downloads/TutorialProjects-master/launchnotifier/theos/lib/libsubstrate.dylib (2 slices)
Undefined symbols for architecture arm64:
“_MSHookMessageEx”, referenced from:
_logosLocalInit() in Tweak.xm.a3b00512.o
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[2]: *** [obj/LaunchNotifier.dylib.ba964c90.unsigned] Error 1
make[1]: *** [internal-library-all_] Error 2
make: *** [LaunchNotifier.all.tweak.variables] Error 2
I’m running out of ideas, can I recommend you come by Saurik’s IRC (irc.saurik.com) and see if anybody with more experience can help you?
Right i have a solution to the problem by downloading the dylib supporting arm64 from this site http://sharedinstance.net/2013/12/how-to-support-arm64/ . So now i am not receiving any errors. However once again on running on my iPhone 4 running IOS 7 I do see any alerts when the settings section is set to TRUE or is ON. So it does show the settings page but not the alert. Any Suggestions ???
It was brought to my attention that, in the MobileSubstrate tweak tutorial, I made a mistake with the method we are supposed to hook. I went back to the MobileSubstrate tutorial now. I’m in a rush so I can’t link you to it right now, but just look for the source code for iOS 7.
Okay All Fixed now. But just wondering if i have to switches in the settings page and i want one to be disabled whilst the other is enabled. How would i come about doing this?
I don’t answer Cocoa based questions as a personal policy, but learn how to use UISwitch, and how to make them call a selector when their value change.
Thanx so much for this tutorial!!!
I’m writing to see if you could help me out, though…
I want to make a PrefBundle but don’t know how to make it call upon my entire tweak. I want a simple On/Off switch and thought you might know how to attach this switch to the whole tweak.
Here’s a basic example of my tweak setup:
%hook SBHeader-1
– (id) function-1 {
return (NULL);
}
%end
%hook SBHeader-2
– (void) function-2 {
return;
}
%end
%hook SBHeader-3
– (id) function-3 {
return FALSE;
}
%end
So….do you know how i’d make a PrefBundle to turn this On and Off? I’m so confused…
Can’t really tell what you’re trying to achieve with your code or what you mean with “attaching a switch to a whole tweak”.
So, I have created a tweak (for theming) that removes latent images when opening and closing an app or folder. I have it hosted on MMI as MaskMuter. Basically, it disables several headers and functions to achieve the singular effect that I need. If you want, I can add a link to my Tweak.xm file.
One header/function i’ve disabled is this but there are many more similar to this:
%hook SBFolderIconView
-(id)darkeningOverlayImage {
return (NULL);
}
%end
I would like to have a PrefBundle to turn this (and the others) On or Off using a PSSwithCell. Any ideas?
I must be misunderstanding something here… Because creating a switch and grabbing it’s value to see if the option is enabled or not is the thing I made in this tutorial…
You will need to check the plist value every time you need it.
I see, so your method won’t allow the user to turn my tweak On or Off, at will. I’d need to use a different approach for that, I guess…
All other options that store settings will require you to read the value that is stored in the plist and check it to see what the methods should do.
Are you by any chances new to programming or iOS development?
That is correct! I do mostly theming. I was somehow able to figure out how to make this tweak but have no knowledge of iOS development. I know that really holds me back but I’m trying to learn so I can wrap my brain around it and understand how things work.
I absolutely, never ever recommend people to attempt writing Tweaks when they don’t know the process of writing a “normal” app.
I recommend you pick up an Objective-C book (Stephen Kochan’s the best), learn Objective-C, and then iPhone development. If you have programmed before, it shouldn’t take you even months. If you haven’t, well you gotta start somewhere and learn programming basics there.
As much as I love helping people here, I refrain from answering basic questions as to not shift the focus of my blog. Once you pick up programming a bit, you will see how easy it is to do the switch that toggles the settings for you.
Good luck on your journey, my friend!
Sounds good! thanx!
My mistake, the last header was supposed to be:
%hook SBHeader-3
– (BOOL) function-3 {
return FALSE;
}
%end
I get 2 errors when i try make the package:
Tweak.xm:14:5: error: unknown type name ‘UIAlertView’
UIAlertView *myAlert = [[UIAlertView alloc]initWithTitle:appName message:…
^
Tweak.xm:14:30: error: use of undeclared identifier ‘UIAlertView’
UIAlertView *myAlert = [[UIAlertView alloc]initWithTitle:appName message:…
You need to define it in an interface along with the methods you’re using.