1

Our iOS apps include many forms of advertising by third party frameworks, running remotely fetched web creative. We are constantly looking for and dealing with instances where users are tossed out of our app (to Safari) without any interaction. Sometimes this is malicious, sometimes it is just accidental.

In any case, does anyone know of any mechanisms we can use on iOS to make it impossible for URLs to be opened in our app (without our discretion)?

Ideally, Apple offering some UIApplicationDelegate method like -(BOOL)shouldOpenURLInSafari:... would be best.

Ricky
  • 3,101
  • 1
  • 25
  • 33

1 Answers1

0

For an app to launch Safari with some URL, the code must call the openURL method of UIApplication.

You can block such calls by subclassing UIApplication and overriding both the old and new openURL methods (the iOS 10+ and pre iOS 10 versions). Your implementation of these overridden methods can simply do nothing if your app has no need to open any URLs or your can check the URL and only allow URLs you know your app should be making.

The trick is to install your custom UIApplication class.


In Objective-C you need to update main.m by passing in your custom class to the third parameter to the call to UIApplicationMain. By default this parameter will be nil.

Updated main.m:

#import <UIKit/UIKit.h>
#import "MyApplication.h"
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv,
                                 NSStringFromClass([MyApplication class]),
                                 NSStringFromClass([AppDelegate class]));
    }
}

Custom UIApplication:

#import "MyApplication.h"

@implementation MyApplication

- (void)openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options completionHandler:(void (^)(BOOL))completion {
    // optionally allow some URLs as needed

    NSLog(@"block %@", url);

    if (completion) {
        completion(YES);
    }
}

- (BOOL)openURL:(NSURL *)url {
    // optionally allow some URLs as needed

    NSLog(@"block %@", url);

    return YES;
}

@end

In Swift you need to remove the use of @UIApplicationMain in AppDelegate.swift, add main.swift, and add a call to UIApplicationMain to that file.

Here is an example custom UIApplication:

import UIKit

class MyApplication: UIApplication {
    override func open(_ url: URL, options: [String : Any] = [:], completionHandler completion: ((Bool) -> Void)? = nil) {
        // optionally allow some URLs as needed

        print("block \(url)")

        completion?(true)
    }

    override func openURL(_ url: URL) -> Bool {
        // optionally allow some URLs as needed

        print("block \(url)")

        return true
    }
}

Here is an example main.swift (courtesy of https://stackoverflow.com/a/24021180/1226963):

import Foundation
import UIKit

UIApplicationMain(
    CommandLine.argc,
    UnsafeMutableRawPointer(CommandLine.unsafeArgv)
        .bindMemory(
            to: UnsafeMutablePointer<Int8>.self,
            capacity: Int(CommandLine.argc)),
    NSStringFromClass(MyApplication.self),
    NSStringFromClass(AppDelegate.self)
)
rmaddy
  • 314,917
  • 42
  • 532
  • 579