iOS - Adding native UI elements to hybrid applications
improve this page | report issueOverview
You can write hybrid applications by using solely web technologies. However, IBM MobileFirst Platform Foundation also allows you to mix and match native code with web code as necessary.
For example, use native UI controls, use native elements, provide an animated native introduction screen, etc. To do so, you must take control of part of the application startup flow.
Prerequisite: This tutorial assumes working knowledge of native iOS development.
This tutorial covers the following topics:
- Taking control of the startup flow
- Native SplashScreen sample
- Sending commands from JavaScript code to native code
- Sending commands from native code to JavaScript code
- The SendAction sample
- Shared session
- Sample application
Taking control of the startup flow
When you create a hybrid application, MobileFirst Platform generates an App Delegate (YourAppName.m
) which handles the various stages of the application startup flow, as follows:
- The
showSplashScreen
method is called to display a simple splash screen while resources are being loaded. This is the location that can be modified with any native introduction screen. - The
initializeWebFrameworkWithDelegate
method loads the resources that the web view needs to work correctly.
As soon as the web framework initialization finishes, thewlInitWebFrameworkDidCompleteWithResult
method is called. - At this point, by default, the application is still displaying the splash screen and no web view is displayed yet.
You can modify the implementation to handle more of the status codes that are returned by the framework. - A Cordova web view controller (
CDVViewController
class) is initialized and the start page of the application is set.
The Cordova controller is then added as a child to the root view and displayed on the screen. - If you decide to implement a custom introduction screen as explained before, consider delaying the display of the Cordova view until the user is done with the custom native screen.
By default, a successful load calls the wlInitDidCompleteSuccessfully
method.
Native SplashScreen sample
The NativeUIInHybrid project includes a hybrid application called NativeSplashScreen.
This example uses a Page View Controller to show a sliding introduction to the application. The user interface was created using a Storyboard in XCode.
Two classes, PageViewController
and PageContentViewController
, were created to handle the slides.
- In the
MyAppDelegate.didFinishLaunchingWithOptions
method, the window is initialized and the PageViewController instance is set as the root of the window.@implementation MyAppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //... self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; PageViewController* pageViewController = [[UIStoryboard storyboardWithName:@"Storyboard" bundle: nil] instantiateViewControllerWithIdentifier:@"PageViewController"]; [self.window setRootViewController:pageViewController]; [self.window makeKeyAndVisible]; //.. }
- The
initializeWebFrameworkWithDelegate
method is called from within thedidFinishLaunchingWithOptions
method.
This method initializes the MobileFirst framework in the background and calls thewlInitWebFrameworkDidCompleteWithResult
method after the framework is initialized.@implementation MyAppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //... [self.window setRootViewController:pageViewController]; [self.window makeKeyAndVisible]; [[WL sharedInstance] initializeWebFrameworkWithDelegate:self]; return result; }
- Inside the
wlInitWebFrameworkDidCompleteWithResult
method, different scenarios are handled depending on thestatusCode
value of theWLWebFrameworkInitResult
object.
In this sample, only the common case of theWLWebFrameworkInitResultSuccess
value is modified.-(void)wlInitWebFrameworkDidCompleteWithResult:(WLWebFrameworkInitResult *)result { if ([result statusCode] == WLWebFrameworkInitResultSuccess) { [self wlInitDidCompleteSuccessfully]; } else { [self wlInitDidFailWithResult:result]; } }
- In the
wlInitDidCompleteSuccessfully
method, a Cordova controller is being prepared but is not displayed yet.
Optionally, you can set the frame to itself so that the web view initializes in the background if initialization of the JavaScript code is required to start in the background.-(void)wlInitDidCompleteSuccessfully { // Create a Cordova View Controller self.cordovaViewController = [[CDVViewController alloc] init] ; self.cordovaViewController.startPage = [[WL sharedInstance] mainHtmlFilePath]; //This will trigger initialization in the background, optional self.cordovaViewController.view.frame = self.cordovaViewController.view.frame; }
- In this sample, the
PageViewController
instance ends with a button that triggers a custom method calledonSplashScreenDone
in the AppDelegate.
TheonSplashScreenDone
custom method resumes where the flow was interrupted and displays the previously initialized Cordova view.-(void)onSplashScreenDone { UIViewController* rootViewController = [[UIViewController alloc] init]; [self.window setRootViewController:rootViewController]; [self.window makeKeyAndVisible]; self.cordovaViewController.view.frame = rootViewController.view.bounds; [rootViewController addChildViewController:self.cordovaViewController]; [rootViewController.view addSubview:self.cordovaViewController.view]; }
Sending commands from JavaScript code to native code
In MobileFirst applications, commands are sent with parameters from the web view (via JavaScript) to a native class (written in Objective-C).
You can use this feature to trigger native code to be run in the background, to update the native UI, to use native-only features, etc.
Step 1
In JavaScript, use the following API:
WL.App.sendActionToNative("doSomething", {customData: 12345});
doSomething
is an arbitrary action name to be used on the native side (see the next step).- The second parameter is a JSON object that contains any data.
Step 2
The native class to receive the action must implement the WLActionReceiver
protocol:
@interface MyReceiver: NSObject <WLActionReceiver>
The WLActionReceiver
protocol requires an onActionReceived
method in which the action name can be checked for and perform any native code that the action needs:
-(void) onActionReceived:(NSString *)action withData:(NSDictionary *) data {
if ([action isEqualToString:@"doSomething"]){
// perform required actions, e.g., update native user interface
}
}
Step 3
For the action receiver to receive actions from the MobileFirst web view, it must be registered. The registration can be done during the startup flow of the application to catch any actions early enough:
[[WL sharedInstance] addActionReceiver:[[myReceiver alloc] init]]
Sending commands from native code to JavaScript code
In MobileFirst applications, commands can be sent with parameters from native Objective-C code to the JavaScript code of web views.
You can use this feature to receive responses from a native method, notify the web view when background code finished running, have a native UI control the content of the web view, etc.
Step 1
In Objective-C, the following API is used:
NSDictionary *data = @{@"someProperty": @"12345"};<br />
[[WL sharedInstance] sendActionToJS:@"doSomething" withData:data];
doSomething
is an arbitrary action name to be used on the JavaScript side.- The second parameter is an
NSDictionary
object that contains any data.
Step 2
A JavaScript function, which verifies the action name and implements any JavaScript code.
function actionReceiver(received){
if (received.action == "doSomething" && received.data.someProperty == "12345"){
//perform required actions, e.g., update web user interface
}
}
Step 3
For the action receiver to receive actions, it must first be registered. This should be done early enough in the JavaScript code so that the function can handle those actions as early as possible.
WL.App.addActionReceiver ("MyActionReceiverId", actionReceiver);
The first parameter is an arbitrary name. It can be used later to remove an action receiver.
WL.App.removeActionReceiver("MyActionReceiverId");
The SendAction sample
Overview
Download the NativeUIInHybrid project, which includes a hybrid application called SendAction.
Note: This sample requires the MapKit framework.
This sample divides the screen in two parts.
- The top half is a Cordova web view with a form to enter a street address.
- The bottom half is a native map view that shows the entered location if it is valid. If the address is invalid, the native map forwards the error to the web view, which displays it.
HTML
The HTML page shows the following elements:
- A simple input field to enter an address
- A button to trigger validation
- An empty line to show potential error messages
<p>This is a MobileFirst WebView.</p>
<p>Enter a valid address (requires Internet connection):<br/>
<input type="text" name="address" id="address"/>
<input type="button" value="Display" id="displayBtn"/>
</p>
<p id="errorMsg" style="color:red;"></p>
JavaScript
When the button is clicked, the sendActionToNative
method is called to send the address to the native code.
$('#displayBtn').on('click', function(){
$('#errorMsg').empty();
WL.App.sendActionToNative("displayAddress",
{ address: $('#address').val()}
);
});
The code also registers an action receiver to display potential error messages from the native code.
WL.App.addActionReceiver ("MyActionReceiverId", function actionReceiver(received){
if(received.action == 'displayError'){
$('#errorMsg').html(received.data.errorReason);
}
});
Storyboard
The interface was designed with a Storyboard file. It features a generic view controller with the ViewController
custom class (described later).
The view controller contains a MKMapView
object and a Container View.
The Container View contains a view controller, which is set to use the HybridScreenViewController
class (described later).
HybridScreenViewController
HybridScreenViewController
extends CDVViewController
, the Cordova web view provided by MobileFirst Platform (@interface HybridScreenViewController : CDVViewController
).
The implementation of the class is almost empty, except for setting the startPage
of the Cordova web view.
@implementation HybridScreenViewController
- (id)initWithCoder:(NSCoder*)aDecoder {
self = [super initWithCoder:aDecoder];
self.startPage = [[WL sharedInstance] mainHtmlFilePath];
return self;
}
//...
ViewController
The ViewController
class extends UIViewController
.
This class:
- Contains a reference to the
MKMapView
object as a property. - Adheres to the
MKMapViewDelegate
protocol to receive updates about the map. - Adheres to the
WLActionReceiver
protocol to receive actions from the MobileFirst web view. - Contains a reference to a
CLGeocoder
object to enable geocoding addresses.
@interface ViewController ()<MKMapViewDelegate, WLActionReceiver>
@property (weak, nonatomic) IBOutlet MKMapView *map;
@property CLGeocoder* geocoder;
@end
The title of the controller is set to be displayed as part of a UINavigationController
object.
The geocoder is initialized.
The map delegate is set.
The ViewController
object is registered as an action receiver for MobileFirst.
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"This is a native header";
self.geocoder = [[CLGeocoder alloc] init];
[self.map setDelegate:self];
[[WL sharedInstance] addActionReceiver:self];
}
The onActionReceived
method is called when the user submits the form. The action name is checked and the entered address is retrieved.
The geocoder is given the address.
-(void) onActionReceived:(NSString *)action withData:(NSDictionary *) data {
if ([action isEqualToString:@"displayAddress"] && [data objectForKey:@"address"]){
NSString* address = (NSString*) [data objectForKey:@"address"];
[self.geocoder geocodeAddressString:address completionHandler:^(NSArray* placemarks, NSError* error){
//DO STUFF
}];
}
}
If a location is found, the region is centered and a new MKPlacemark
is added to the map.
completionHandler:^(NSArray* placemarks, NSError* error){
if([placemarks count]){
CLPlacemark *topResult = [placemarks objectAtIndex:0];
float spanX = 0.00725;
float spanY = 0.00725;
MKCoordinateRegion region;
region.center.latitude = topResult.location.coordinate.latitude;
region.center.longitude = topResult.location.coordinate.longitude;
region.span = MKCoordinateSpanMake(spanX, spanY);
[self.map setRegion:region animated:YES];</p>
MKPlacemark *placemark = [[MKPlacemark alloc]initWithPlacemark:topResult];
[self.map addAnnotation:placemark];
}
}
If the search fails or no location is found, the sendActionToJS
method is called to transmit the error to the web view.
completionHandler:^(NSArray* placemarks, NSError* error){
if([placemarks count]){
//...
}
else{
[[WL sharedInstance] sendActionToJS:@"displayError" withData:@{@"errorReason": [error localizedDescription]}];
}
}
MyAppDelegate
- The
didFinishLaunchingWithOptions
method of the app delegate is generated by MobileFirst Platform and is left unchanged in this example. - The
wlInitDidCompleteSuccessfully
status code is modified to load theViewController
object from the Storyboard instead of loading theCDVViewController
object directly. - The splash screen is hidden in native code because JavaScript has not started yet.
-(void)wlInitDidCompleteSuccessfully {
UINavigationController* rootViewController = self.window.rootViewController;
ViewController* viewController = [[UIStoryboard storyboardWithName:@"Storyboard" bundle:nil] instantiateViewControllerWithIdentifier:@"ViewController"];
[rootViewController pushViewController:viewController animated:YES];
[[WL sharedInstance] hideSplashScreen];
}
Shared session
When you use both JavaScript and native code in the same application, you might need to make HTTP requests to MobileFirst Server (connection, procedure invocation, etc.)
HTTP requests are explained in other tutorials about authentication, application authenticity, and HTTP adapters (both for hybrid and native applications).
IBM Worklight Foundation 6.2, and IBM MobileFirst Platform Foundation 6.3 and later, keep your session (cookies and HTTP headers) automatically synchronized between the JavaScript client and the native client.
Sample application
Click to download the MobileFirst project.
▲Inclusive terminology note: The Mobile First Platform team is making changes to support the IBM® initiative to replace racially biased and other discriminatory language in our code and content with more inclusive language. While IBM values the use of inclusive language, terms that are outside of IBM's direct influence are sometimes required for the sake of maintaining user understanding. As other industry leaders join IBM in embracing the use of inclusive language, IBM will continue to update the documentation to reflect those changes.