Rich iOS User Interface using UIWebViews and MGTemplateEngine

SoftLayer Mobile Team is part of the Interface Development group that is dedicated to providing access to SoftLayer global platform. Our goal is to bring an outstanding user experience by clear presentation of data and friendly navigation through resources right in the palms of our customers.

Our mobile application users are very often interested in not only browsing tickets or devices – data that can efficiently be presented using native iOS UI controls like table views – but also in seeing resources health and usage info like bandwidth billing data, monitoring alarms updates and maintenance events – data sets that are more complex and not easily presentable using native controls. The greatest challenge is how to display this kind of information on a small screen.

To solve our dilemma, we came up with several options:

1. Use native controls like labels, text and scroll views and dynamically create complex data views.
2. Use collection views introduced in iOS 6.X.
3. Use “hybrid views” i.e.: wrap HTML content in the native web views.

We decided to dynamically generate HTML content and present it in the iOS web views. The results speak for themselves – take a look at the bellow screenshots.

iPhone scheduled event details view:

iPad bandwidth data set view:

Let’s dive deeper into the implementation of the Scheduled Events Details view that was shown earlier. There are two main components needed to make the “hybrid view” work:

1. View controller that owns a web view: SLScheduledEventDetailsViewController
2. Scheduled Events HTML web page template: ScheduledEventDetails.mghtml

The view controller makes use of the MGTemplateEngine to load the template and perform necessary template substitutions based on the actual Scheduled Event item being viewed.

SLScheduledEventDetailsViewController declaration:

#import <UIKit/UIKit.h>
 
@interface SLScheduledEventDetailsViewController : UIViewController < UIWebViewDelegate>
 
@end

SLScheduledEventDetailsViewController declaration:

#import <MGTemplateEngine/MGTemplateEngine.h>
#import <MGTemplateEngine/ICUTemplateMatcher.h>
#import <SLPortalAPI/SLPortalAPI.h>
#import " SLScheduledEventDetailsViewController.h"
 
static const CGFloat kUpdatesTableViewMargin = 8.0;
 
@interface SLScheduledEventDetailsViewController ()
@property (strong, nonatomic) SLSecheduledEvent *eventItem;
@end
 
@implementation SLScheduledEventDetailsViewController
 
- (id)initWithScheduledEventItem: (SLSecheduledEvent *) item
{
    self = [super init];
    if (self)
    {
        self.eventItem = item;
    }
    return self;
}
 
- (void) viewWillAppear: (BOOL) animated
{
	[super viewWillAppear: animated];
 
	[self reloadWebContent];
}
 
- (void) reloadWebContent
{
	NSString *templatePath = [[NSBundle mainBundle] pathForResource: @"ScheduledEventDetails" 
                                                                ofType: @"mghtml" 
                                                           inDirectory: nil];
	MGTemplateEngine *templateEngine = [MGTemplateEngine templateEngine];
	[templateEngine setMatcher: [ICUTemplateMatcher matcherWithTemplateEngine: templateEngine]];
 
	NSMutableString *descriptionHTMLized = [self.eventItem.summary mutableCopy];
 
	NSError *regularExpressionError = nil;
	NSRegularExpression *eachLineExpression = 
                                           [NSRegularExpression regularExpressionWithPattern:@"^(.*)$" 
                                           options: NSRegularExpressionAnchorsMatchLines 
                                             error: &regularExpressionError];
 
	[eachLineExpression replaceMatchesInString: descriptionHTMLized
					        options: 0
					          range: NSMakeRange(0, [descriptionHTMLized length]) 
                                     withTemplate: @"<p>$1</p>"];
 
    NSArray *updateItems = self.eventItem.updates;
   [NSMutableArray arrayWithCapacity: [self.eventItem.EventUpdates count]];
    NSArray *sortedEvents =[ sortedArrayUsingComparator:
                    ^NSComparisonResult(SLScheduledEventUpdate *lhs, SLScheduledEventUpdate *rhs) {
        return [lhs.createDate compare: rhs.createDate];
    }];
 
    for (int i = [sortedEvents count] - 1; i >= 0; --i)
    {
        SLScheduledEventUpdate *update = [sortedEvents objectAtIndex: i];
 
        NSMutableString *contentHTMLized = [update.contents mutableCopy];
        NSRegularExpression *eachLineContentExpression = 
                                  [NSRegularExpressionregularExpressionWithPattern: @"^(.*)$" 
                                                        options: NSRegularExpressionAnchorsMatchLines  
                                                          error: &regularExpressionError];
 
        [eachLineContentExpression replaceMatchesInString: contentHTMLized
                                                  options: 0
                                                    range: NSMakeRange(0, [contentHTMLized length]) 
                                             withTemplate: @"<p>$1</p>"];
 
        NSDictionary *updateItem = @{@"formattedDate" : [update createDateString],
        @"contents" : contentHTMLized};
 
        [updateItems addObject: updateItem];
    }
 
    NSDictionary *substitutions = @{@"ScheduledEventDescription": descriptionHTMLized,
                                    @"updateItems" : updateItems};
    NSString *newHTMLString = [templateEngine processTemplateInFileAtPath: templatePath 
                                                               withVariables: substitutions];
    [self.webView loadHTMLString: newHTMLString baseURL: [NSURL fileURLWithPath: [[NSBundle 
                                                   mainBundle] resourcePath] 
                                               isDirectory: YES]];
}
 
- (void) loadView
{
	self.view = [[UIWebView alloc] init];
	self.webView.dataDetectorTypes = UIDataDetectorTypeNone;
}
 
- (void) performURLCommand: (NSString *) urlCommand
{
	if([urlCommand isEqualToString: @"showDevices"])
	{
		[self performSelector:@selector(showDevices:) withObject: self];
	}
}
 
- (IBAction) showDevices: (id) sender
{
    SLImpactedResourcesViewController *impactedResourcesController = 
                               [[SLImpactedResourcesViewController alloc] initWithImpactedResources:  
                                                                          [self.eventItem resources]];
    [self.navigationController pushViewController: impactedResourcesController animated: YES];
}
 
#pragma mark -
#pragma mark UIWebKitDelegate
 
- (BOOL)           webView: (UIWebView *) webView
shouldStartLoadWithRequest: (NSURLRequest *)request
	        navigationType: (UIWebViewNavigationType)navigationType
{
	BOOL shouldLoad = YES;
 
	if(navigationType = = UIWebViewNavigationTypeLinkClicked)
	{
		NSURL *requestURL = request.URL;
		if(requestURL)
		{
			if([[requestURL scheme] isEqualToString: @"slmc-cmd"])
			{
				[self performURLCommand: [requestURL resourceSpecifier]];
			}
		}
 
		shouldLoad = NO;
	}
 
	return shouldLoad;
}
 
@end

The key method is (void)reloadWebContent, where the template substitutions take place. Another method that is worth taking a closer look at is the (BOOL) webView: (UIWebView *) webView shouldStartLoadWithRequest: (NSURLRequest *)request that allows us to react to user events i.e.: link/buttons taps.

ScheduledEventsDetails.mghtml template file:

<!DOCTYPE HTML>
<html>
<head>
<title>SoftLayer Scheduled Events</title>
<meta name ='' "viewport" content ='' "initial-scale ='' 1.0,user-scalable ='' no">
<link rel="stylesheet" href="ScheduledEventDetails.css" type="text/css" media="screen" title="no title" charset="utf-8">
</head>
<body>
<div id="title">
	<table>
	<tr>
		<td>
			<img id="toolboxIcon" src="EventsIcon.pdf"/>
		</td>
		<td>
			<h1>Scheduled Event</h1>
		</td>
	</tr>
	</table>
</div>
<div id="summary">
 
</div>
<div class="updateTable">
	<table id="UpdateTable" width="100%">
	<tr>
		<th class="value_header">
			Date
		</th>
		<th class="value_header">
			Update
		</th>
	</tr>
	 {% for updateItem in updateItems %}
	<tr>
		<td class="key">
 
		</td>
		<td class="value">
 
		</td>
	</tr>
	 {% /for %}
	<table>
</div>
<br>
<center><a href="slmc-cmd:showDevices" class="buttonLookingLink"/><img src="ServerIconSmallWhite.png" height="26" width="26" align="center" style="padding:2px;"/>Associated Devices</a></center>
</body>
</html>

The technique of using “hybrid views” has proven not only to be quite robust and maintainable, but also that it can be seamlessly integrated with the native iOS UI components. It is also worth pointing out that the “hybrid views” allow for easy rich text formatting, which is limited on pre iOS 6.X versions of the Apple OS.

Check out the “hybrid views” in action today!

Pawel