PageRenderTime 49ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/articles/mobile-engagement/mobile-engagement-bridge-webview-native-ios.md

https://gitlab.com/yeah568/azure-content
Markdown | 272 lines | 201 code | 71 blank | 0 comment | 0 complexity | b71b3661db8ef376a10a17e5d7d49de7 MD5 | raw file
  1. <properties
  2. pageTitle="Bridge iOS WebView with native Mobile Engagement iOS SDK"
  3. description="Describes how to create a bridge between WebView running Javascript and the native Mobile Engagement iOS SDK"
  4. services="mobile-engagement"
  5. documentationCenter="mobile"
  6. authors="piyushjo"
  7. manager="erikre"
  8. editor="" />
  9. <tags
  10. ms.service="mobile-engagement"
  11. ms.workload="mobile"
  12. ms.tgt_pltfrm="mobile-ios"
  13. ms.devlang="objective-c"
  14. ms.topic="article"
  15. ms.date="02/25/2016"
  16. ms.author="piyushjo" />
  17. #Bridge iOS WebView with native Mobile Engagement iOS SDK
  18. > [AZURE.SELECTOR]
  19. - [Android Bridge](mobile-engagement-bridge-webview-native-android.md)
  20. - [iOS Bridge](mobile-engagement-bridge-webview-native-ios.md)
  21. Some mobile apps are designed as a hybrid app where the app itself is developed using native iOS Objective-C development but some or even all of the screens are rendered within an iOS WebView. You can still consume Mobile Engagement iOS SDK within such apps and this tutorial describes how to go about doing this.
  22. There are two approaches to achieve this though both are undocumented:
  23. - First one is described on this [link](http://stackoverflow.com/questions/9826792/how-to-invoke-objective-c-method-from-javascript-and-send-back-data-to-javascrip) which involves registering a `UIWebViewDelegate` on your web view and catch-and-immediatly-cancel a location change done in Javascript.
  24. - Second one is based on this [WWDC 2013 session](https://developer.apple.com/videos/play/wwdc2013/615), an approach which is cleaner than the first and which we will follow for this guide. Note that this approach only works on iOS7 and above.
  25. Follow the steps below for the iOS bridge sample:
  26. 1. First of all, you need to ensure that you have gone through our [Getting Started tutorial](mobile-engagement-ios-get-started.md) to integrate the Mobile Engagement iOS SDK in your hybrid app. Optionally, you can also enable test logging as follows so that you can see the SDK methods as we trigger them from the webview.
  27. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  28. ....
  29. [EngagementAgent setTestLogEnabled:YES];
  30. ....
  31. }
  32. 2. Now make sure that your hybrid app has a screen with a webview on it. You can add it to the `Main.storyboard` of the app.
  33. 3. Associate this webview with your **ViewController** by clicking and dragging the webview from the View Controller Scene to the `ViewController.h` edit screen, placing it just below the `@interface` line.
  34. 4. Once you do this, a dialog box will pop up asking for a name. Provide the name as **webView**. Your `ViewController.h` file should look like the following:
  35. #import <UIKit/UIKit.h>
  36. #import "EngagementViewController.h"
  37. @interface ViewController : EngagementViewController
  38. @property (strong, nonatomic) IBOutlet UIWebView *webView;
  39. @end
  40. 5. We will update the `ViewController.m` file later but first we will create the bridge file which creates a wrapper over some commonly used Mobile Engagement iOS SDK methods. Create a new header file called **EngagementJsExports.h** which uses the `JSExport` mechanism described in the aforementioned [session](https://developer.apple.com/videos/play/wwdc2013/615) to expose the native iOS methods.
  41. #import <Foundation/Foundation.h>
  42. #import <JavaScriptCore/JavascriptCore.h>
  43. @protocol EngagementJsExports <JSExport>
  44. + (void) sendEngagementEvent:(NSString*) name :(NSString*)extras;
  45. + (void) startEngagementJob:(NSString*) name :(NSString*)extras;
  46. + (void) endEngagementJob:(NSString*) name;
  47. + (void) sendEngagementError:(NSString*) name :(NSString*)extras;
  48. + (void) sendEngagementAppInfo:(NSString*) appInfo;
  49. @end
  50. @interface EngagementJs : NSObject <EngagementJsExports>
  51. @end
  52. 6. Next we will create the second part of the bridge file. Create a file called **EngagementJsExports.m** which will contain the implementation creating the actual wrappers by calling the Mobile Engagement iOS SDK methods. Also note that we are parsing the `extras` being passed from the webview javascript and putting that into an `NSMutableDictionary` object to be passed with the Engagement SDK method calls.
  53. #import <UIKit/UIKit.h>
  54. #import "EngagementAgent.h"
  55. #import "EngagementJsExports.h"
  56. @implementation EngagementJs
  57. +(void) sendEngagementEvent:(NSString*)name :(NSString*)extras {
  58. NSMutableDictionary* extrasInput = [self ParseExtras:extras];
  59. [[EngagementAgent shared] sendEvent:name extras:extrasInput];
  60. }
  61. + (void) startEngagementJob:(NSString*) name :(NSString*)extras {
  62. NSMutableDictionary* extrasInput = [self ParseExtras:extras];
  63. [[EngagementAgent shared] startJob:name extras:extrasInput];
  64. }
  65. + (void) endEngagementJob:(NSString*) name {
  66. [[EngagementAgent shared] endJob:name];
  67. }
  68. + (void) sendEngagementError:(NSString*) name :(NSString*)extras {
  69. NSMutableDictionary* extrasInput = [self ParseExtras:extras];
  70. [[EngagementAgent shared] sendError:name extras:extrasInput];
  71. }
  72. + (void) sendEngagementAppInfo:(NSString*) appInfo {
  73. NSMutableDictionary* appInfoInput = [self ParseExtras:appInfo];
  74. [[EngagementAgent shared] sendAppInfo:appInfoInput];
  75. }
  76. + (NSMutableDictionary*) ParseExtras:(NSString*) input {
  77. NSData *data = [input dataUsingEncoding:NSUTF8StringEncoding];
  78. NSError* error = nil;
  79. NSMutableDictionary* extras = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
  80. return extras;
  81. }
  82. @end
  83. 5. Now we come back to the **ViewController.m** and update it with the following code:
  84. #import <JavaScriptCore/JavaScriptCore.h>
  85. #import "ViewController.h"
  86. #import "EngagementJsExports.h"
  87. @interface ViewController ()
  88. @end
  89. @implementation ViewController
  90. - (void)viewDidLoad
  91. {
  92. self.webView.delegate = self;
  93. [super viewDidLoad];
  94. [self loadWebView];
  95. }
  96. - (void)loadWebView {
  97. NSBundle* mainBundle = [NSBundle mainBundle];
  98. NSURL* htmlPage = [mainBundle URLForResource:@"LocalPage" withExtension:@"html"];
  99. NSURLRequest* urlReq = [NSURLRequest requestWithURL:htmlPage];
  100. [self.webView loadRequest:urlReq];
  101. }
  102. - (void)webViewDidFinishLoad:(UIWebView*)wv
  103. {
  104. JSContext* context = [wv valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
  105. context[@"EngagementJs"] = [EngagementJs class];
  106. }
  107. - (void)webView:(UIWebView*)wv didFailLoadWithError:(NSError*)error
  108. {
  109. NSLog(@"Error for WEBVIEW: %@", [error description]);
  110. }
  111. - (void)didReceiveMemoryWarning {
  112. [super didReceiveMemoryWarning];
  113. // Dispose of any resources that can be recreated.
  114. }
  115. @end
  116. 6. Note the following points about the **ViewController.m** file:
  117. - In the `loadWebView` method, we are loading a local HTML file called **LocalPage.html** whose code we will review next.
  118. - In the `webViewDidFinishLoad` method, we are grabbing the `JsContext` and associating our wrapper class with it. This will allow calling our wrapper SDK methods using the handle **EngagementJs** from the webView.
  119. 7. Create a file called **LocalPage.html** with the following code:
  120. <!doctype html>
  121. <html>
  122. <head>
  123. <style type='text/css'>
  124. html { font-family:Helvetica; color:#222; }
  125. h1 { color:steelblue; font-size:22px; margin-top:16px; }
  126. h2 { color:grey; font-size:14px; margin-top:18px; margin-bottom:0px; }
  127. </style>
  128. <script type="text/javascript">
  129. window.onerror = function(err)
  130. {
  131. alert('window.onerror: ' + err);
  132. }
  133. function send(inputId)
  134. {
  135. var input = document.getElementById(inputId);
  136. if(input)
  137. {
  138. var value = input.value;
  139. // Example of how extras info can be passed with the Engagement logs
  140. var extras = '{"CustomerId":"MS290011"}';
  141. }
  142. if(value && value.length > 0)
  143. {
  144. switch(inputId)
  145. {
  146. case "event":
  147. EngagementJs.sendEngagementEvent(value, extras);
  148. break;
  149. case "job":
  150. EngagementJs.startEngagementJob(value, extras);
  151. window.setTimeout(
  152. function(){
  153. EngagementJs.endEngagementJob(value);
  154. }, 10000 );
  155. break;
  156. case "error":
  157. EngagementJs.sendEngagementError(value, extras);
  158. break;
  159. case "appInfo":
  160. var appInfo = '{"customer_name":"' + value + '"}';
  161. EngagementJs.sendEngagementAppInfo(appInfo);
  162. break;
  163. }
  164. }
  165. }
  166. </script>
  167. </head>
  168. <body>
  169. <h1>Bridge Tester</h1>
  170. <div id='engagement'>
  171. <br/>
  172. <h2>Event</h2>
  173. <input type="text" id="event" size="35">
  174. <button onclick="send('event')">Send</button>
  175. <br/>
  176. <h2>Job</h2>
  177. <input type="text" id="job" size="35">
  178. <button onclick="send('job')">Send</button>
  179. <br/>
  180. <h2>Error</h2>
  181. <input type="text" id="error" size="35">
  182. <button onclick="send('error')">Send</button
  183. <br/>
  184. <h2>AppInfo</h2>
  185. <input type="text" id="appInfo" size="35">
  186. <button onclick="send('appInfo')">Send</button>
  187. </div>
  188. </body>
  189. </html>
  190. 8. Note the following points about the HTML file above:
  191. - It contains a set of input boxes where you can provide data to be used as names for your Event, Job, Error, AppInfo. When you click on the button next to it, a call is made to the Javascript which eventually calls the methods from the bridge file to pass this call to the Mobile Engagement iOS SDK.
  192. - We are tagging on some static extra info to the events, jobs and even errors to demonstrate how this could be done. This extra info is sent as a JSON string which, if you look in the `EngagementJsExports.m` file, is parsed and passed along with sending Events, Jobs, Errors.
  193. - A Mobile Engagement Job is kicked off with the name you specify in the input box, run for 10 seconds and shut down.
  194. - A Mobile Engagement appinfo or tag is passed with 'customer_name' as the static key and the value that you entered in the input as the value for the tag.
  195. 9. Run the app and you will see the following. Now provide some name for a test event like the following and click **Send** next to it.
  196. ![][1]
  197. 10. Now if you go to the **Monitor** tab of your app and look under **Events -> Details**, you will see this event show up along with the static app-info that we are sending.
  198. ![][2]
  199. <!-- Images. -->
  200. [1]: ./media/mobile-engagement-bridge-webview-native-ios/sending-event.png
  201. [2]: ./media/mobile-engagement-bridge-webview-native-ios/event-output.png