Sunday, 3 November 2013

ICTextView - UITextView with search highlighting

I just took a quick break from my studies to finish this small, fun project I've been working on for some days. I'm open sourcing it because I'm not aware of any other open-source implementation that also supports iOS 4-5.

ICTextView is a custom UITextView subclass that supports string or regex search and match highlighting. It's decently optimized, very easy to use, highly customizable, and it supports highlighting additional search results while the user scrolls.

It also contains a number of iOS 7 improvements and bugfixes over the original UITextView. As an example, the characterRangeAtPoint: method works fine with it, and I introduced a custom implementation of the scrollRangeToVisible: method that accounts for content insets in iOS 7. After reading how many people are having trouble with the countless iOS 7 UITextView bugs, I felt pretty much obliged to share this.

Enough talk. You can find the code and a nicely formatted readme file on GitHub.

ICTextView on GitHub

UPDATE: ICTextView is now available via Cocoapods. Just add "pod ICTextView" to your Podfile and run "pod install". Feel free to report any issue via email, or contribute via GitHub pull requests.

Wednesday, 6 March 2013

iCleaner Pro

iCleaner goes PRO!: if you are an advanced user, and wish to have full control over your device, you can get iCleaner Pro for free from my Cydia repository:

NOTE: please uninstall "iCleaner" before installing "iCleaner Pro". If you don't, you will end up having two iCleaner icons, or just a non-working one. In that case, please respring your device.

There has been a lot going on about iCleaner recently. It evolved from a simple system cleaner, to a complete suite of tools that allow users to have full control upon their devices. This great control iCleaner offers, however, has been misused in ways I did not foresee.

Basically, it has lead to some major issues:
  • Some users were not happy, because they disabled some daemons they actually needed, MobileSubstrate addons required for other tweaks to work correctly, deleted images blindly without even reading the included readme file and understanding how removal tools should be used.
  • Some users actually sent support requests to devs and repo maintainers, reporting some of their tweaks were not working correctly and they didn't know why. When they were asked if they used iCleaner to disable Launch Daemons or MobileSubstrate tweaks, they replied they did not know what they did.
  • Some users even restored their devices, without being aware that the changes they made were entirely reversible.

I was contacted by the BigBoss' repo maintainer, who was concerned about iCleaner becoming a threat for user experience and unfair towards other developers. All I can say is that he is definitely right, not because of the features offered by the app itself, but because of the incorrect usage people were making of it.

Therefore, I agreed to remove the most misused functions (MobileSubstrate and Launch Daemons management being the most relevant) from the package hosted on BigBoss'. This way, such functionality will be kept away from the vast majority of uninformed users. Version 6.3.0 also features a button to undo the changes made to Launch Daemons and MobileSubstrate addons.

The very same thing happened to other developers offering similar functions (Springtomize, for example), and I'm afraid iCleaner is no exception.

Introducing 'iCleaner Pro'

I still believe there are a lot of savvy and advanced users who might want to make use of those functions. Because of that, I decided to release a "Pro" version of iCleaner, that will not be distributed on any community source. It features all the functions iCleaner had before, plus a nice amount of new tools. And, of course, it's still free!

So, in the end, there are no steps back being done. Just two separate releases for two separate types of users.

You can download "iCleaner Pro" from my new repository, or from one of the provided download links.


- Ivano Bilenchi / @SoftHardW

Tuesday, 22 January 2013

iOS - Run application with root privileges

Please view this page in desktop version, or visit it from a PC. It contains syntax-highlighted code snippets which are badly displayed on mobile devices.

As you may already know, regular iOS apps run as Unix user "mobile", with limited privileges (most notably, limited filesystem access). If you wish to gain full control over the whole directory tree, your app has to run as user "root".

In order to accomplish this, your app has to satisfy the following conditions:

  1. Its bundle must be located in "/Applications".
  2. Its executable must belong to the root user.
  3. Its executable must be able to set user and group IDs.
  4. It has to explicitly set user and group ID at runtime.
  5. It must not be run directly. Rather, something else should launch it (keep reading to learn how to do this).

How do I do all of that? Well, that's actually pretty simple:

  1. Put your .app bundle in the ./Applications folder of your debian package.
  2. Set ownership of your app binary to "root:wheel":
    chown root:wheel ./Applications/
  3. Give your app binary the "set user ID" and "set group ID" flags.
    chmod 6755 ./Applications/
  4. Explicitly call "setuid(0)" and "setgid(0)" very early in your code (specifically in "main", somewhere before calling "UIApplicationMain"). Here's a quick example:
    #import <UIKit/UIKit.h>
    #import "MyAppDelegate.h"
    int main(int argc, char *argv[]){
            // Set uid and gid
            if (!(setuid(0) == 0 && setgid(0) == 0))
                NSLog(@"Failed to gain root privileges, aborting...");
            // Launch app
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([MyAppDelegate class]));
Well that was easy, wasn't it? But what about that "don't run directly" madness?

Essentially, you need to add a "launch script" to the app bundle, which will be called when you tap on the SpringBoard icon, and will actually launch the app.

Not doing this will result in your app being terminated as soon as it attempts to setuid.

Assuming the executable of your app is named "myApp", all you have to do is rename it to "myApp_", and add a script called "myApp" to the .app bundle. The script should be structured like this:

myAppPath=$(dirname "$0")
exec "$myAppPath"/myApp_ "$@"

Finally, make sure to set the ownership of the script to "root:wheel" and its permissions to "755".

Whew! After all of this, your app will finally be able to run as root.

  • This doesn't apply to regular AppStore apps. Running as root is only possible on jailbroken devices.
  • The fact that your app will be running with root privileges does not mean you can skip the code signature step. Your app has to be properly pseudo-signed or self-signed in order to run. See this article from Saurik about bypassing code signature.
  • You should only give root privileges to your app if it really needs them. Misusing root access may potentially have catastrophic consequences if you don't know what you're doing.

Friday, 4 January 2013

Programmatically terminate an iOS application (with animation)

Please view this page in desktop version, or visit it from a PC. It contains syntax-highlighted code snippets which are badly displayed on mobile devices.

Before reading, please note that proper AppStore apps should not programmatically terminate themselves, as it's against Apple's recommendations. Also, I'll be using private API function calls in this code, which will automatically have your app rejected. This approach is only meant for open application development and/or Cydia submission.

iOS applications may be programmatically terminated by calling the private method:
[[UIApplication sharedApplication] terminateWithSuccess];

Or the C function:

These, however, will instantly terminate the app, without showing any animation. Basically, it will look like the app has crashed.

The only method that will show an animation while sending the app in background is the private:
[[UIApplication sharedApplication] suspend];

This method will act differently depending on the device and iOS version:
  • On iOS 4.0 and later, and if the device supports multitasking, it will suspend the application and send it to background.
  • On previous iOS versions, or if the device does not support multitasking, it will actually close the application.

To have the app terminated even in the first case, you might want to add the "UIApplicationExitsOnSuspend" key to the app's Info.plist. This works fine if you wish to terminate the app every time you call the "suspend" method, or the user presses the home button.

However, you might want to selectively suspend or close the app depending on various circumstances, but still retain the zooming animation (which is lost by calling "exit" or "terminateWithSuccess"). I came up with this approach: enable background execution, call the suspend method, then kill the app after a certain delay.

The most coherent solution would be using a category to extend the UIApplication class with a "close" method. Here's the code I have been using in my apps:

// Needed to directly call the private methods
@interface UIApplication (existing)
- (void)suspend;
- (void)terminateWithSuccess;

@interface UIApplication (close)
- (void)close;

@implementation UIApplication (close)

- (void)close
    // Checks if the current device supports background execution
    BOOL multitaskingSupported = NO;
    // iOS < 4.0 compatibility check
    if ([[UIDevice currentDevice] respondsToSelector:@selector(isMultitaskingSupported)])
        multitaskingSupported = [UIDevice currentDevice].multitaskingSupported;
    // Checks if application responds to selector "suspend" (good practice, we're using a private method)
    if ([self respondsToSelector:@selector(suspend)])
        if (multitaskingSupported)
            [self beginBackgroundTaskWithExpirationHandler:^{}];
            // Change the delay to your liking. I think 0.4 seconds feels just right (the "close" animation lasts 0.3 seconds).
            [self performSelector:@selector(exit) withObject:nil afterDelay:0.4];
        [self suspend];
        [self exit];

- (void)exit
    // Again, good practice
    if ([self respondsToSelector:@selector(terminateWithSuccess)])
        [self terminateWithSuccess];

This will allow you to call:
[[UIApplication sharedApplication] close];
And have your application gracefully animate and close itself.