Part 6: Preferences, Preferences, a little Tweak, and Heaps of More Preferences

derv edited this page Sep 6, 2016 · 1 revision

Hook code (in Logos)

The hooking code itself is really really simple. This is all that is needed in my Tweak.xm file:

%hook DATaskManager {
  -(id) userAgent {
    return @"iPhone8C2/1307.36"; // iPhone6S+ running iOS 9.3.5
  }
}
%end

And this works fine!

But what happens when iOS 9.3.6 comes out? Or iOS 10.0? I don't want to have to check out, modify, re-build, re-package, and re-submit my tweak for every upgrade. That's ludicrous.

There should be a way for the user to specify what User-Agent they want to use...

Creating a Preference Bundle

I used nic.pl to create a preference_bundle inside of my existing tweak, and named it ExchangentPrefs under the namespace com.derv82.exchangentprefs. This created the exchangentprefs directory seen in the code on this repo.

The benefit of creating the PreferenceBundle inside of the existing tweak is:

  1. It gets packaged together with the main tweak
  2. nic.pl knows to update THEOS' Makefile to automatically include the preference bundle when building/packaging.
  3. Everything is all in one place. You don't need create multiple Github repos for one project.

I followed a lot of guides and looked at sample code when creating my preference bundle. The guides were useful but the main points that took me a while to get are:

  1. PreferenceBundles used by Theos integrate with MobileSubstrate's PreferenceLoader.
  • PreferenceLoader injects MobileSubstrate tweaks' PreferenceBundles (like the one we're making, or Activator's bundle, etc) into the iOS Settings application at runtime (above 3rd-party non-jailbreak tweaks).
  1. PreferenceBundles are their own bundle.
  • Your Tweak.xm cannot directly call your preference bundle to get preferences; it has to go through some other way (like reading preferences from a .plist file).
  • See PreferenceBundles#Loading_Preferences (iPhoneDevWiki) for examples of loading preferences within your Tweak.xm
  • See kirb's guide on the "right way" to load/store preferences: http://sharedinstance.net/2014/11/settings-the-right-way/
  • I couldn't get this to work. I should've gone to the #iphonedev IRC channel to get help! You can too!
  • I ended up modifying my PreferenceBundle code (ExchangentRootListController.m) to write to the .plist file on every change, and read from the .plist file on every load. This required sending PostNostification messages to the Tweak.xm class which would then reload the .plist file. Very circuitious.
  1. PreferenceBundles are mostly-defined by .plist files that dictate the order of elements (enabled, the text for "Enabled", sliders, buttons, icons, etc).
  2. However, PreferenceBundles have their own code (in Objective-C, usually .m or .mm files) that is executed when the user taps on the tweak in the "Settings" app.
  • There's generic/boiler-plate code that "just works" for most simple preferences: PreferenceBundles#Issues_with_OS_3.2_and_4.0
  • More-complicated preference logic (like hiding/showing preference fields, changing fields, etc) require adding code to the PreferenceBundle's Objective-C code. This is what I did for Exchangent.

Preferences get really complicated really fast. I suggest keeping your settings as simple as possible.

Protip: Validate .plist files using plutil

Example:

$ plutil "/var/mobile/Library/Preferences/com.derv82.exchangentprefs.plist"
{
    customUserAgent = "iPhone8C2/1307.69";
    device = iPhone8C2;
    enabled = 1;
    iosVersion = "1307.36";
    useCustom = 0;
    userAgent = "iPhone8C2/1307.37";
}

If there is no output, then it's not a valid .plist file. This came in handy when I made a typo in a plist file but didn't get any errors when compiling/installing with Theos.