Last year around the same time I had written a post about Calling Objective-C code from JavaScript in iOS applications . I created a simple framework for it and described how to use it in that post.
Recently I was working on an application that required WebView to be embedded in the same View Controller along with other native iOS controls. I decided to use the above framework, but knew that my ViewController class will have to extend WebViewController class of the framework. Though I could have done that, I thought separating the functionality of UIWebViewDelegate from the WebViewController would be a better design. So I replaced WebViewController with WebViewDelegate which implements UIWebViewDelegate protocol. I also removed getInitialPageName method from WebViewInterface.h . So here are the files in the new framework –
WebViewDelegate.h
WebViewDelegate.m
WebViewInterface.h
iosBridge.js
To explain how to use this framework, I will take a simple example where a ViewController contains mix of native controls and a WebView (BTW, because delegate functionality is separated from the ViewController, you can now have multiple Web Views in the same View Controller and can still use the framework). Let’s create a ViewController that has a text box and a button (native controls) and a WebView.
The WebView contains a combo box. There is no native combo box control in the Cocoa library, so this example might be useful to you if you want to create a combo box and don’t mind if it is created using HTML.
When the view controller is loaded, it opens index.html in the web view. index.html has just one combo box and it calls a native (Objective-C) method to get the content and populates the list. If you enter text in the text box and click add button, the native code calls a method in JavaScript in index.html to add new item to the list.
This is how the storyboard for this app looks like. Text box, add button and WebView are native iOS controls. Combo box is HTML control displayed in the WebView.
I created a ViewController class for the above view and called it RKMainViewController.
#import <UIKit/UIKit.h> #import "WebViewInterface.h" @interface RKMainViewController : UIViewController <WebViewInterface> @property (weak, nonatomic) IBOutlet UIWebView *webView; @end
So unlike the previous framework, this class does not extend WebViewController. It extends UIViewController and implements WebViewInterface protocol of the framework. I have also created an outlet for the web view.
In the implementation of this class (RKMainViewController.m), I first declare local property (for WebViewDelegate) , action for ‘Add’ button and outlet for text box.
@interface RKMainViewController () @property (weak, nonatomic) IBOutlet UITextField *txt1; - (IBAction)onAdd:(id)sender; @property WebViewDelegate *webViewDelegate; @end
Of course, you need to connect outlet and action to controls in the storyboard. In the viewLoad function of this class, I create a new instance of WebViewDelegate, disable scrolling on it and load index.html from www folder –
- (void)viewDidLoad { [super viewDidLoad]; self.webViewDelegate = [[WebViewDelegate alloc] initWithWebView:self.webView withWebViewInterface:self]; self.webView.scrollView.scrollEnabled = false; [self.webViewDelegate loadPage:@"index.html" fromFolder:@"www"]; }
processFunctionFromJS (part of WebViewInterface protocol. RKMainViewController implements this protocol. See the class declaration above) function is where we handle methods called from JavaScript. In this example I handle ‘loadList’ function called from JS –
- (id) processFunctionFromJS:(NSString *) name withArgs:(NSArray*) args error:(NSError **) error { if ([name compare:@"loadList" options:NSCaseInsensitiveSearch] == NSOrderedSame) { NSArray *listElements = @[@"Item 1", @"Item 2"]; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:listElements options:0 error:nil]; NSString *result = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; return result; } return nil; }
I am returning two hard-coded list items in response to the call to loadList function. The result is returned as JSON string.
Action handler for ‘Add’ button gets text from the text box and calls JS function ‘addToList’.
- (IBAction)onAdd:(id)sender { NSDictionary *dict = @{@"newListItem":self.txt1.text}; [self.webViewDelegate callJSFunction:@"addToList" withArgs:dict]; }
Text to be added is passed in a dictionary, with key ‘newListItem’.
Now, let’s look at index.html. JS code in this file calls loadList function to populate the combo list and declares ‘addToList’ JS function, which is called from the native code, as shown in the code snippet above.
<script src="iosBridge.js"></script> <script> function onErrorCallingNativeFunction (err) { if (err) { alert(err.message); } } function onReady() { calliOSFunction("loadList", [],function(ret){ var listValues = JSON.parse(ret.result); var selectElm = document.getElementById("select1"); var options = ""; for (var i = 0; i < listValues.length; i++) { options += "<option value='" + listValues[i] + "'>" + listValues[i] + "</option>"; } selectElm.innerHTML = options; }, onErrorCallingNativeFunction); } function addToList(dataObj) { if (dataObj !== undefined && typeof dataObj.newListItem !== 'undefined') { var selectElm = document.getElementById("select1"); selectElm.innerHTML += "<option value='" + dataObj.newListItem + "' selected>" + dataObj.newListItem + "</option>"; } } </script> <body onload="onReady();"> <select id="select1" style="font-size:12pt;width:100%"> </select> </body>
Notice that calliOSFunction (declared in iosBridge.js) is called from onReady function, which is called when the HTML body is loaded. The first argument to ‘calliOSFunction’ is the name of native function to call (in this case ‘loadList’). The second argument is array of arguments to be passed to the native function and the last argument is callback function that the native code will call when result is ready.
Result is passed as JSON string to the callback function handler. So we first parse the string and create a JS object. In this case we know that result is an array of strings. So we iterate over the list and add items to the combo box.
‘addToList’ function is called from the native code and takes JS object with the key-value pair. Expected key name is ‘newListItem’ and value is string to be added to the list.
In summary, this framework has a couple of advantages over the one I had created earlier, at the same time provides all the same functionality.
- Your view controller class need not extend the framework’s view controller class.
- It can support multiple web views in the same view controller.
You can download entire source code for the above demo application from here. The project is created with Xcode 5.0.
-Ram Kulkarni
I noticed that in createCallbackFunction in iosbridge.js – you’re not returning the function name if it already exists, which causes problems with multiple calls to the same anonymous function. After the line that reads:
if (window[tmpName].toString() == callbackFuncstr){
change: return;
to: return tmpName;
I have a split view where my master is a table view menu and my detail is UIWebview content view. Whenever I click on my table cell respective urls are opening in content view , showing the table cell selected.
In one of the content view which is a web page, when I click on a button over there I want to show one of the table view cell selected in my menu view .
If I use touch events it will happen for all the view ,I want to do this action for that particular button.Also I want the username/password from a login page which is loading in UIWebview in my application.
Any idea how to get this done.
Can I use your framework for this scenario any idea.
Note that your published code is completely useless if you don’t say whether we are allowed to use it or not. You must declare it under some Open Source licence.
If you just want the code to be free without any restriction, just state that it is Public Domain.
If you want to have at least some reference of your name, choose a licence like zlib, simplified BSD or MIT.
Thanks!
I’m also interested in what Ram has to say about this… we cannot use this code with seeing the license details.
Feel free to use any code/snippet or files in this post
have you tried adding multiple web views to load different URLs in one app simultaneously? If yes what was the performance impact, specially if each web view pages loads lot of JavaScript.
Hello, I have two questions:
1) NSString *result = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
return result;
Potential leak?
2) @property (weak, nonatomic) IBOutlet UITextField *txt1;
– (IBAction)onAdd:(id)sender;
Why these two things are local (not in .h file)?
Thanks!
Sorry, I had forgotten about ARC when I wrote this =)
Hello Sir,
I have done with custom plugin and able to do changes in native code now i want come back to ionic code form native code (Objective c).
Can you please help me out for coming back to ionic page in ios…?
Thanks in advance.