2

In my app, I have implemented a private method in my AppDelegate to override the default openURL: method in order to open links inside my app within UIWebView. But now I need the default functionalities in place too.

Here's what I did:

@implementation UIApplication (Private)

- (BOOL)customOpenURL:(NSURL*)url
{ 
    AppDelegate *MyWatcher = (AppDelegate *)[[UIApplication sharedApplication] delegate];

    if (MyWatcher.currentViewController) {
        [MyWatcher.currentViewController handleURL:url];
        return YES;
    }
    return NO;
}

@end

- (void)applicationDidBecomeActive:(UIApplication *)application {  
    Method customOpenUrl = class_getInstanceMethod([UIApplication class], @selector(customOpenURL:));
    Method openUrl = class_getInstanceMethod([UIApplication class], @selector(openURL:));

   method_exchangeImplementations(openUrl, customOpenUrl);  
}

I also implemented handleURL: in my class where the custom open URL handling was needed. However, this is hindering my other class in which I just want to do a simple open of an iTunes link in iTunes. So what I don't know how to achieve is how to use the original openURL: in place of customOpenURL:.

Krizz
  • 11,362
  • 1
  • 30
  • 43
Ashutosh
  • 5,614
  • 13
  • 52
  • 84
  • why can't you just call `customOpenURL` instead of `openURL`? No need for overriding then. – Krizz Apr 14 '12 at 21:57
  • Because customURL will open the link in UIWebView but for one link i want it open in UIwebView and for the other one i want it to be open with iTunes. – Ashutosh Apr 14 '12 at 22:21
  • But I wonder, why you can't call `customOpenURL` for the link you want to open in `UIWebView` and `openURL` for the one you want to launch externally in iTunes? – Krizz Apr 14 '12 at 22:25
  • because i m doing this in appdelegate:Method customOpenUrl = class_getInstanceMethod([UIApplication class], @selector(customOpenURL:)); Method openUrl = class_getInstanceMethod([UIApplication class], @selector(openURL:)); method_exchangeImplementations(openUrl, customOpenUrl); – Ashutosh Apr 14 '12 at 22:53
  • Yep, I know, but I was wondering why are you doing that method exchange. Anyway, see my answer below. – Krizz Apr 14 '12 at 22:54

3 Answers3

4

You can just subclass UIApplication and override openURL: directly. Be sure to change the principle class in your Info.plist to use your UIApplication subclass.

Example:

@interface ECApplication : UIApplication

@end

@implementation ECApplication

- (BOOL)openURL:(NSURL*)url
{

    AppDelegate *MyWatcher = (AppDelegate *)[[UIApplication sharedApplication] delegate];

    if (MyWatcher.currentViewController) {
        [MyWatcher.currentViewController handleURL:url];
        return YES;
    }
    return NO;
}

@end

Then, in your Info.plist file, look for the Principle Class key, and change the value to ECApplication (or whatever you name your subclass).

edc1591
  • 10,146
  • 6
  • 40
  • 63
  • And from appdelegate should i remove the private implementation of UIApplication. But now as the principal class will be ECApplication how will the openURL method would work – Ashutosh Apr 14 '12 at 22:04
  • Yes, remove the category on `UIApplication`. Using this setup, everything should work normally in `UIApplication` except your custom `openURL:` method will be used. – edc1591 Apr 14 '12 at 22:24
  • I did the same thing you said but now when i say openURL for the url which i want to open in iTunes it also goes to this subclass. – Ashutosh Apr 14 '12 at 23:29
  • And as the condition is still true it calls the handleURL method again. – Ashutosh Apr 14 '12 at 23:49
  • If you wan't to access the original implementation of `openURL:` you can just call `[super openURL:url];` from anywhere in your custom `openURL:` method. – edc1591 Apr 15 '12 at 00:18
  • @edc1591 If you're around today I'd like to pick your brain about this. I realize this thread is fairly old now, but nevertheless... – Miles Alden Jan 07 '13 at 23:19
  • @MilesAlden Sure, feel free to shoot me an email evan at evancoleman.net – edc1591 Jan 09 '13 at 23:38
2

You can set the original implementation to some other method and then just call it:

@implementation UIApplication (Private)
- (BOOL)originalOpenURL:(NSURL*)url 
{
     return NO;
}

- (BOOL)customOpenURL:(NSURL*)url
{
     if (/* some condition */)
     {
        // your code
     }
     else
     {
        return [self originalOpenURL: url];
     }
}

@end

- (void)applicationDidBecomeActive:(UIApplication *)application {  
     Method customOpenUrl = class_getInstanceMethod([UIApplication class], @selector(customOpenURL:));
     Method openUrl = class_getInstanceMethod([UIApplication class], @selector(openURL:));
     Method originalOpenUrl = class_getInstanceMethod([UIApplication class], @selector(originalOpenURL:));

     method_exchangeImplementations(openUrl, originalOpenUrl); 
     method_exchangeImplementations(openUrl, customOpenUrl);  
}

Note: This is just a solution giving direct answer to your question. The clearer approach to this problem is the one suggested by @edc1591. You can access original openURL: with [super openURL:url].

Krizz
  • 11,362
  • 1
  • 30
  • 43
0

The approach pointed out by Krizz works on the first app launch only, if you happen to open an URL that redirects you to another App (i.e.: Facebook app), it messes with the implementations when your app is resumed. Adding a flag to make sure the method_exchangeImplementations is called only on the first app launch seems to work.

dorbsz
  • 33
  • 1
  • 7