Monday, August 27, 2012

Deep linking in iOS application


Deep linking is a concept where-in links within your application either link into some other sections/modules of your application or link into other applications with in your device. Links work the same way as they would in a web page the driving change for this behavior is the schema. Under normal circumstances URLs will have "Schema:domainname\address". In this case we customize the 'schema name' part to something we have identified within our application

How does it work?
The way we will see this at work is construct a simple UIWebView and render HTML page in it. Let say you display "www.apple.com". When you tap on the links in the web page (from your device), they are not intercepted by your application. It is handled within the webview, request forwarded to the domain server and finally reaches the web server, fetches data and comes back to the device in the same route. That's normal HTML request. On the other hand if you pick a custom URL (deep linked), URL will be stopped, verified against the settings in the current application and also in the list of applications registered with in the device for schema identification. If a match is found eithr the respective module is invoked from within the application or the external application is either awaken (from it's deep slumbers) or starts a new lease of life

Now enough of talk, let's get down to some code
Internal Links
1. Let's start with a very simple single-view application
2. I then created 4 additional UIViewControllers with xib file
3. Each of these view controllers will have a UIWebView, that will display contents from a website
4. 4th WebView will display a custom HTML text in the webview. Link in the webview has been modified to open a section from within the application
5. Open the .plist file for the project and add URL Types like the one appended. Feel free to choose a name of your choice

Changes to .plist file


Sample Webview for your reference. I created 2 more additional views that point to different websites like - www.techcrunch.com, www.engadget.com
Header file

// BIDFirstWebViewController.h



#import

@interface BIDFirstWebViewController : UIViewController <UIWebViewDelegate>
@property (retain, nonatomic) IBOutlet UIWebView *webview;
@property (retain, nonatomic) UIActivityIndicatorView *activityView;
@end

Implementation File


//
//  BIDFirstWebViewController.m
//

#import "BIDFirstWebViewController.h"

@interface BIDFirstWebViewController ()

@end

@implementation BIDFirstWebViewController
@synthesize activityView = _activityView;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)dealloc {
    [_webview release];
    [super dealloc];
}

- (void)viewWillAppear:(BOOL)animated {
    NSURL *url = [[NSURL alloc] initWithString:@"http://www.zdnet.com"];
    NSURLRequest *req = [[NSURLRequest alloc] initWithURL:url];
    [_webview setDelegate:self];
    [_webview loadRequest:req];
    //[_webview loadHTMLString:[self htmlTextForURL] baseURL:nil];
}

- (void)viewWillDisappear:(BOOL)animated {
    [_webview setDelegate:nil];
}

#pragma - WebViewDelegates
- (void)webViewDidStartLoad:(UIWebView *)webView {
    NSLog(@"Start download");
    UIActivityIndicatorView *activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
    [activityView setTag:10];
    [activityView setFrame:CGRectMake(10, 10, 25, 25)];
    [activityView setHidesWhenStopped:YES];
    [activityView startAnimating];
    [self.view addSubview:activityView];
    [activityView release];
}

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    UIActivityIndicatorView *activityView = (UIActivityIndicatorView*)[self.view viewWithTag:10];
    [activityView stopAnimating];
    [activityView removeFromSuperview];
    NSLog(@"Completed download");
}

@end


My custom webview - This view hosts a custom HTML page that will reference internal link
Header file

//
//  BIDFourthWebViewController.h
//

#import

@interface BIDFourthWebViewController : UIViewController <UIWebViewDelegate>
@property (retain, nonatomic) IBOutlet UIWebView *webview;

@end

Implementation file
//
//  BIDFourthWebViewController.m
//

#import "BIDFourthWebViewController.h"

@interface BIDFourthWebViewController ()

@end

@implementation BIDFourthWebViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
}

- (void)viewWillAppear:(BOOL)animated {
    [_webview setDelegate:self];
    [_webview loadHTMLString:[self htmlTextForURL] baseURL:nil];
}

- (void)viewWillDisappear:(BOOL)animated {
    [_webview setDelegate:nil];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark - webview dele gate implementation

- (void)webViewDidStartLoad:(UIWebView *)webView {
    UIActivityIndicatorView *activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
    [activityView setTag:10];
    [activityView setFrame:CGRectMake(10, 10, 25, 25)];
    [activityView setHidesWhenStopped:YES];
    [activityView startAnimating];
    [self.view addSubview:activityView];
    [activityView release];
}

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    UIActivityIndicatorView *activityView = (UIActivityIndicatorView*)[self.view viewWithTag:10];
    [activityView stopAnimating];
    [activityView removeFromSuperview];
}

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
    UIActivityIndicatorView *activityView = (UIActivityIndicatorView*)[self.view viewWithTag:10];
    [activityView stopAnimating];
    [activityView removeFromSuperview];
}

- (void)dealloc {
    [_webview release];
    [super dealloc];
}

#pragma mark - Private methods
- (NSString*)htmlTextForURL {
    NSString *htmlText = @"Hello World!
ZDNet";
    return htmlText;
}

@end


Now Open AppDelegate and make sure you have an implementation of the following

//
//  BIDAppDelegate.m
//

#import "BIDAppDelegate.h"

#import "BIDViewController.h"
#import "BIDUrlProcessor.h"

@implementation BIDAppDelegate
@synthesize navigationController = _navigationController;
- (void)dealloc
{
    [_window release];
    [_viewController release];
    [_navigationController release];
    [super dealloc];
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
    // Override point for customization after application launch.
    self.viewController = [[[BIDViewController alloc] initWithNibName:@"BIDViewController" bundle:nil] autorelease];
    self.navigationController = [[UINavigationController alloc] initWithRootViewController:self.viewController];
    //self.window.rootViewController = self.viewController;
    [self.window addSubview:self.navigationController.view];
    [self.window makeKeyAndVisible];
    return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application
{
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}

- (void)applicationWillTerminate:(UIApplication *)application
{
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}


- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
    NSLog(@"Absolute :%@, Base :%@, Fragment: %@, Parameter: %@, Query: %@, Scheme: %@", [url absoluteString], [url baseURL], [url fragment], [url parameterString], [url query], [url scheme]);
    // parse URL for domain name and parameters
    // obtain parameter information
    if(![[url scheme] isEqualToString:@"cameraoperations"])
        return NO;

    NSDictionary *urlParameters = [BIDUrlProcessor getParametersFromUrlString:[url query]];
    // invoke view controller to update itself
    if([[urlParameters objectForKey:@"openpage"] isEqualToString:@"zdnet"]) {
        [self.viewController openViewForSegment:ZDNET];
    }
    return YES;
}

@end


My custom URL processor
Header file

//
//  BIDUrlProcessor.h
//

#import

@interface BIDUrlProcessor : NSObject
+ (NSDictionary*)getParametersFromUrlString:(NSString*)url;
@end

Implementation file


//
//  BIDUrlProcessor.m
//

#import "BIDUrlProcessor.h"

@implementation BIDUrlProcessor
+(NSDictionary*)getParametersFromUrlString:(NSString *)url {
    NSMutableDictionary *URLParameters = [[NSMutableDictionary alloc] init];
    if(url == nil || [url length] <= 0)
        return nil;
    // split on '&'
    NSArray *parameters = [url componentsSeparatedByString:@"&"];
    for (NSString *parameter in parameters) {
        NSArray *nameValuePair = [parameter componentsSeparatedByString:@"="];
        NSString *parameterName = [nameValuePair objectAtIndex:0];
        NSString *parameterValue = [nameValuePair objectAtIndex:1];
        [URLParameters setObject:parameterValue forKey:parameterName];
    }
    return URLParameters;
}
@end

Before you execute the application, make sure you set up a breakpoint in AppDelegate for the method - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
Now execute the application and browse to the view controller that displays the custom HTML, tap the link in the page, you will see the breakpoint in AppDelegate fired. That's it. Have fun with internal links

External Link
Now that we have an application with registered custom scheme, we can create another single-view application that will open the above application from a link.

Let's start by creating a simple Single-View application and drop a webview in it. This view controller resembles pretty much the same as the Custom webview we created for previous exercise. When the user taps on the link in this webview, iOS will look for the registerd schema if it find one associated with an installed application, it will open the application, if there is no matching schema registered, no action will be taken

Here's the sample ViewController I have in the new application
Header file
//
//  BIDWebViewController.h
//  OpenApplications
//

#import

@interface BIDWebViewController : UIViewController <UIWebViewDelegate>

@property (retain, nonatomic) IBOutlet UIWebView *webview;
@end

Implementation File

//
//  BIDWebViewController.m
//  OpenApplications
//

#import "BIDWebViewController.h"

@interface BIDWebViewController ()

@end

@implementation BIDWebViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
        [_webview setDelegate:self];
    }
    return self;
}

- (void)viewWillAppear:(BOOL)animated {
    [self setTitle:@"Open Custom application"];
    [_webview loadHTMLString:[self htmlTextForURL] baseURL:nil];
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)dealloc {
    [_webview release];
    [super dealloc];
}

#pragma mark - UIWebviewDelegate implementation
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
    // guess, not needed
}

- (void)webViewDidStartLoad:(UIWebView *)webView {
    // guess, not needed
}

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    // guess, not needed
}

#pragma mark - Private methods
- (NSString*)htmlTextForURL {
    NSString *htmlText = @"Hello World!
ZDNet";
    return htmlText;
}
@end


With these code changes in place, execute the application, tap on the link, and see this application open another application from a web link.

Hope you enjoyed reading this article and code snippet. Cheers



No comments: