Mercury - Tweak and Fun Code

I just released my most recent tweak, Mercury, which you can check out on my Cydia repo or just look at the source on GitHub. It's an iOS version of Message Indicator for macOS that I released back in May. I've been sitting on it for over a year, and finally carved out a weekend to flesh it out a bit for release.

Customizable indicator in effect

You can see how it looks in effect above, with the border option. The options and details of that implementation can be seen in the video below:

However, the purpose of this post isn't really to discuss the tweak, but two of the more useful code bits that I wrote for the tweak that are more widely applicable. They aren't even really part of the tweak, but are part of the preferences page, specifically the searchSubviews method and the configureText method.

First, the searchSubviews method. Unfortunately, it's hard to hook into the exact moments that a view is created, because it can be unclear with of the potentially thousands of functions it's initialized in. Additionally, knowing when all of the subviews are properly created is a near-unknowable task. This fixes that. First, in the header it defines typedef BOOL (^ Evaluator)(UIView *);. This creates the type "Evaluator" which is a block function which takes in a UIView and returns a bool. This will be used in the search parameter for searchSubviews. Then, for the main function:

static UIView *searchSubviews(UIView *view, Evaluator search) {
  if (search(view)) {
    return view;
  } else {
    for (UIView *subview in [view subviews]) {
      UIView *possible = searchSubviews(subview, search);
      if (possible) {
        return possible;
      }
    }
    return nil;
  }
}

This is a recursive function. It evaluates every subview in a depth-first-search manner evaluating against your search criteria until a view returns true, at which point it bubbles up to the top and returns. This is super useful because you can just plop the method into viewDidAppear and you can find the view you want to adjust and adjust it then, when it's guaranteed to be all set up and have all of its children setup.

Secondly, the configureTextView option. If you want to use a decimal or number input on a preferences pane, the keyboard by default doesn't come with a "Done" button. This adds it. For the base specifier you use PSEditTextCell for the cell. Then you hook into reloadSpecifiers and viewDidAppear and run the configureTextView option which uses searchSubviews to find the added UITextField, sets the alignment to right and keyboard type to decimal/number, creates a UIToolbar with a "Done" button and adds it as an inputAccessoryView for the textField.

Hope this helps someone out in the future (or more likely me when I look back at how I did this previously).