(734)545-8017

The Human Element Blog

iOS App Development with Webview (Part 3/3)

Posted Aug 18, 2015 by

Android App Development - Human Element

Silly faces and top hats make for good app development.

This is part 3 of a series on Mobile app development using Webview. Want to read more? Here ya go:

iOS App Development: Introduction

In the first part of this article, we discussed the design for a pretty standard app. In this article, we will discuss how you can implement an easy iOS App using Webview for Development. Hopefully, this article will help you with your own mobile app.

  • What this article covers: Basic functionality that most all WebView apps may implement around the WebView. This is the boilerplate code written in a device’s native language.
  • Not covered: Anything related to your content, design, or concept. Anything that appears inside the WebView, coded with Javascript, HTML, and CSS. That’s up to you.

This article will focus on Objective C code for an iOS WebView application.

We will create an app that uses a WebView to “frame” in the Human Element website. Remember, this is just a “boiler-plate” template for any app; you could substitute the Human Element url with ANY url to create an entirely different app. For example, if your app needs to present internal page content instead of any old url out there on the internet, then YES, you can do that instead (I discuss where your web files can live in the first part of this article). The example in this article uses centralized hosting, so all the web files live at human-element.com.

Porting from Android to iOS

Many of the code concepts in this article are similar to those in the Android article (part 2 of this series). It describes a basic boilerplate for iOS that can read the SAME web application piece that you wrote for an Android WebView app.

80% of your app can be written in HTML/CSS/Javascript (web pages) that are then accessible to both Android and iOS through similar WebViews. This article provides a boilerplate WebView application, similar to the previous Android article.

Missing from this article

Setup of your development environment, and tools needed to create iOS are not discussed here. This article will only provide code examples. However, in order to get your code working, you will absolutely need the correct applications and operating system. When developing iOS apps, there is less freedom with which tools you can use, compared to developing Android apps. This is what you need for iOS development:

Be prepared to invest a lot of time into understanding your iOS development tools and setup, if you are starting iOS development for the first time. You will be using Apple’s online guides and tutorials frequently, which is outside of the scope of this article. For now, let’s take a look at some example code.

On this page

Starting a new XCode “Human Element Demo” project

Sorry for the blatant plug for Human Element in the demo project name, but I thought “Hello World” might already be taken in your project list. Feel free to call your project whatever you like and substitue “Human Element Demo” for your own app’s name. Let’s begin.

In XCode…

  1. Create a new XCode project
  2. Create an iOS, Single View Application
  3. Choose your project name, like “Human Element Demo”, etc…
  4. Your language should be Ojbective-C and it’s fine to leave Devices as iPhone.
  5. Create a workspace (project folder location) for your project, eg: your project name without spaces.
  6. Unless you know what a Git repository is and you want to use one, you should deselect the Source Control checkbox.

Now I have a basic blank starting point for my iOS app. If you’ve never used XCode before, you may want to take some time to acclamate yourself with features of this application (outside of the scope of this article).

“Human Element Demo” project UI Elements

Let’s briefly go over the Five (5) UI Elements that you need to configure for this project:

  • (1) View Controller (this element should be the first box you see in your Main.storyboard). It’s there by default and you probably won’t have to create it.
    • (2) Web View Add a Web View element to your View Controller.
      Any element you add, will have to be positioned on the scene, for example, by sizing the element and then going to Editor (top nav) > Resolve Auto Layout Issues > Reset to Suggested contstraints.Constraints are important for maintaining your relative positions and sizes even if your app is viewed on devices with varying screen sizes.
    • (3) Label Add a label to the very center of your web view. Change its text to “…Loading…”. Set the size and position of the label, as needed.
  • Another (4) View Controller2 you have to add this second view controller. Important: You will need to rename this View Controller’s class to a custom name, “ExternalView
    • (5) Web View2 Add a Web View element to this second View Controller.

The first Web View will be dedicated to the displaying the main view of the app. The second Web View2 will display any external web content without having to leave the app.

The label will display some simple “…Loading…” text when the app is opened and isn’t fully loaded yet.

Connect UI elements to code objects

You will need to define some of your UI elements so that they can be accessed in your app’s code.

You probably notice that some of the files that got created when you created a new project have a .h extension. XCode let’s you hold the Ctl key, click, and drag the visual control into the .h code file. This will automatically create your named definition for a control. For example, the code may look like this:

01
@property (weak, nonatomic) IBOutlet UIWebView *mainWebView;

While holding Ctl, I clicked on my first Web View and dragged over into my ViewController.h file to create the definition. I named this Web View “mainWebView“. You will need to do this too. Give your UI elements the following names (used to identify the element in the code):

  • (1) View Controller
    • (2) Web View = mainWebView
    • (3) Label = loadingLabel
  • (4) View Controller2
    • (5) Web View2 = externalWebView

View Controller2 doesn’t have .h and .m files yet, like View Controller. has ViewController.h and ViewController.m. Go ahead and create two new files next to ViewController.h and ViewController.m:

  1. ExternalView.m (Objective-C)
  2. ExternalView.h (Header)

You can link your externalWebView Web View to the code in the ExternalView.h file. If XCode won’t let you link externalWebView to ExternalView.h, make sure your View Controller2 has a custom class “ExternalView”… also, you will need to make sure ExternalView.m also contains a class called “ExternalView”. You can copy the code example below to make sure.

Complete code examples (4 files)

So far I’ve covered some of the grunt work in getting an XCode project set up. Assuredly, you will have to try for yourself and use other online resources to help with some of the details that weren’t covered here. But for the remainder of this article, let’s focus on the actual code.

Note: I ran this exact code as a test to make sure it works (July 22nd, 2015). I appreciate it when authors have tested the code they are demonstrating, so I’ll return the favor. Cheers!

Of course, there are more files involved in an iOS than just these four. However, these are the core files that you will have to edit. Most of the other files are automatically created for you when you create your app.

ViewController.h

The main purpose of header .h files, for this app, is to define properties used within the classes.

There is one class per View Controller. This app has two view controllers, so naturally, it has two classes:

  1. ViewController this class handles the primary logic for the app including the home screen and sub pages of the app. Anything that you consider a part of your core app will be handled within this ViewController class.
  2. ExternalView this class handles displaying any external page (within a WebView, whithin the app). For example, if you want a user to access a web page that you didn’t create, you can show the external page within the app (without opening a separate browser application)

ViewController.h is the header file for the ViewController class. It defines some UI Elements: loadingLabel and mainWebView, which can be accessed and controlled inside your code.

You can also see that ViewController.h imports some required libraries, in addition to importing the separate ExternalView class (on line 3); ViewController needs to send external URLs to the separate ExternalView class. So importing ExternalView here allows the ViewController class to recognize and communicate with ExternalView, as needed.

01
02
03
04
05
06
07
08
09
10
#import <UIKit/UIKit.h>
#import <UIKit/UIWebView.h>
#import "ExternalView.h"
@interface ViewController : UIViewController <UIWebViewDelegate>
@property (weak, nonatomic) IBOutlet UIWebView *mainWebView;
@property (weak, nonatomic) IBOutlet UILabel *loadingLabel;
@end

ViewController.m

This is the heart of your application. It contains the boilerplate code for managing the primary webview element, mainWebView.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
NSString *fullURL = @"https://www.human-element.com";
NSString *deviceType = @"?d=iphone";
/*NEVER open any url, containing this string, INSIDE the app.
 ONLY open outside the app. iOS cannot accept donations directly
 within the app as it violates Apple's terms*/
NSString *outsideAppFlag = @"mydonationssite-notarealsite.com";
- (void)viewDidLoad {
    //load webview url
    NSString *urlWithDeviceType = [fullURL stringByAppendingString:deviceType];
    NSURL *url = [NSURL URLWithString:urlWithDeviceType];
    NSURLRequest *Request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
    [_mainWebView loadRequest:Request];
    [_mainWebView setDelegate:self];
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Connection"
                                                    message: @"Having trouble reaching HE Demo? Please check your internet connection and restart the app."
                                                   delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
    _loadingLabel.hidden = TRUE;
    //should an error message popup for this error?
    BOOL ignoreError = FALSE;
    if (error.domain == NSURLErrorDomain) {
        if (error.code == NSURLErrorCancelled) {
            //ignore this one, interrupted load
            ignoreError = TRUE;
        }
    }
    //for some reason, twitter triggers a url request to about:blank in order to load.
    //This is necessary for twitter to load, but it triggers a webView error... just ignore about:blank errors...
    //NSString *theURLString = [webView.request.URL absoluteString]; this won't work - it just returns the last successful url
    NSString *urlStr = [error.userInfo objectForKey:@"NSErrorFailingURLStringKey"]; //this works
    if([urlStr hasPrefix:@"about:blank"]){
        //ignore about:blank error
        ignoreError = TRUE;
    }
    //if this error shouldn't be ignored
    if(!ignoreError){
        //show the error message
        [alert show];
    }
}
- (void)webViewDidStartLoad:(UIWebView *)webView
{
    _loadingLabel.hidden = FALSE;
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    _loadingLabel.hidden = TRUE;
    //add "newtab:" before each <a> tag href that fits criteria: 1) must have target="_blank", 2) must NOT have onclick attribute
    NSString *JSInjection = @"javascript: var allLinks = document.getElementsByTagName('a'); if (allLinks) {var i;for (i=0; i<allLinks.length; i++) {var link = allLinks[i];var oncli = link.getAttribute('onclick');if(oncli==undefined||oncli.length<1){var target = link.getAttribute('target'); if (target && target == '_blank') {link.setAttribute('target','_self');link.href = 'newtab:'+link.href;}}}}";
    [webView stringByEvaluatingJavaScriptFromString:JSInjection];
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
 navigationType:(UIWebViewNavigationType)navigationType
{
    BOOL isExternal = FALSE;
    BOOL isAllow = TRUE;
    NSString *urlStr = request.URL.absoluteString;
    //if the url begins with 'newtab:'
    if([urlStr hasPrefix:@"newtab:"]){
        isExternal = TRUE;
        //while newtab: is at the start of the URL
        while([urlStr hasPrefix:@"newtab:"]){
            //remove the 'newtab:' from the start of the url
            urlStr = [urlStr substringFromIndex:7];
        }
    }else{
        //url does NOT begin with 'newtab:'...
        //if the url request doesn't start with anchor tag
        if (![urlStr hasPrefix:@"#"]){
            //if the url request doesn't start with the webview url
            if(![urlStr hasPrefix:fullURL]){
                //if the link is not a phone number
                if(![urlStr hasPrefix:@"tel:"]){
                    //if the link is not a mailto link
                    if(![urlStr hasPrefix:@"mailto:"]){
                        //then this link should be opened in a separate view
                        isExternal = TRUE;
                    }
                }
            }
        }
    }
    //if this is an external url
    if(isExternal){
        //if this link isn't blank
        if(![urlStr hasPrefix:@"about:blank"]){
            //load the external page
            [self loadExternalView:urlStr];
            isAllow = FALSE; //ignore the url request (let the other "sharedApplication" handle it, instead, outside of the webview)
        }
    }
    return isAllow;
}
//private function to load the external view
- (void)loadExternalView:(NSString *)externalUrl{
    //if the URL does NOT contain the cachnet donations url string
    if ([externalUrl rangeOfString:outsideAppFlag].location == NSNotFound) {
        //if(![externalUrl containsString:outsideAppFlag]){
        //get the alternate ExternalView that holds the external webview
        UIStoryboard* mainStoryboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
        ExternalView *ev = [mainStoryboard instantiateViewControllerWithIdentifier:@"ExternalView"];
        //set the external url that needs to load
        ev.loadUrl = externalUrl;
        ev.outsideAppFlag = outsideAppFlag;
        // Create the navigation controller and present it without unloading the current home view
        UINavigationController *navCtl = [[UINavigationController alloc]initWithRootViewController:ev];
        [self presentViewController:navCtl animated:YES completion: nil];
    }else{
        //if the URL DOES contain the cachnet donations url string...
        //open the url in a SEPARATE browser outside of the app's webview
        NSURL *url = [NSURL URLWithString:externalUrl];
        [[UIApplication sharedApplication] openURL:url];
    }
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
@end

ExternalView.h

Similar to ViewController.h, this file defines some key properties and imports.

01
02
03
04
05
06
07
08
09
10
11
#import <UIKit/UIKit.h>
#import <UIKit/UIWebView.h>
#import "ViewController.h"
@interface ExternalView : UIViewController <UIWebViewDelegate>
@property (weak, nonatomic) IBOutlet UIWebView *externalWebView;
@property (strong, nonatomic) NSString *loadUrl;
@property (strong, nonatomic) NSString *outsideAppFlag; //if a URL contains this string, then open the URL outside of the app, instead of within the web view
@end

ExternalView.m

Similar to ViewController.m, this class handles functionality for a webview UI element. Instead of handling mainWebView, this file handles functionality and events for the externalWebView ui element.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#import "ExternalView.h"
@interface ExternalView ()
@end
@implementation ExternalView
UIActivityIndicatorView *spinner;
//handle back button press (exits external page view)
- (void)backBtnPress:(id) sender {
    //dismiss the current view to go back home
    [self dismissViewControllerAnimated:YES completion:nil];
}
- (void)viewDidLoad
{
    //programmatically create the spinner animation. The spinner never stops if it's added via storyboard, but it works when added this way
    spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
    spinner.center = CGPointMake(self.view.bounds.size.width / 2.0f, self.view.bounds.size.height / 2.0f);
    spinner.autoresizingMask = (UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin);
    [self.view addSubview:spinner];
    //load webview url
    NSURL *url = [NSURL URLWithString:_loadUrl];
    NSURLRequest *Request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
    _externalWebView.delegate = self;
    [_externalWebView loadRequest:Request];
    [super viewDidLoad];
    // Do any additional setup after loading the view
    UIColor *barColor = [UIColor colorWithRed:0/255.0 green:0/255.0 blue:0/255.0 alpha:1.0]; //2d5b8e
    //set bar color
    [self.navigationController.navigationBar setBarTintColor:barColor];
    //optional, i don't want my bar to be translucent
    [self.navigationController.navigationBar setTranslucent:NO];
    //set title and title color
    [self.navigationItem setTitle:@"HE Demo"];
    [self.navigationController.navigationBar setTitleTextAttributes:[NSDictionary dictionaryWithObject:[UIColor whiteColor] forKey:NSForegroundColorAttributeName]];
    //set back button color
    [[UIBarButtonItem appearanceWhenContainedIn:[UINavigationBar class], nil] setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIColor whiteColor], NSForegroundColorAttributeName,nil] forState:UIControlStateNormal];
    //set back button arrow color
    [self.navigationController.navigationBar setTintColor:[UIColor whiteColor]];
    //create back button icon and attach event
    self.navigationItem.leftBarButtonItem=[[UIBarButtonItem alloc]init];
    UIImage *img1=[UIImage imageNamed:@"logo_hedemo.png"];
    CGRect frameimg1 = CGRectMake(0, 0, img1.size.width, img1.size.height);
    UIButton *backBtn=[[UIButton alloc]initWithFrame:frameimg1];
    [backBtn setBackgroundImage:img1 forState:UIControlStateNormal];
    [backBtn addTarget:self action:@selector(backBtnPress:)
      forControlEvents:UIControlEventTouchUpInside];
    [backBtn setShowsTouchWhenHighlighted:YES];
    UIBarButtonItem *barButton=[[UIBarButtonItem alloc]initWithCustomView:backBtn];
    self.navigationItem.leftBarButtonItem=barButton;
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Connection"
                                                    message: @"Having trouble reaching page. Please check your internet connection and restart the app."
                                                   delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
    //should an error message popup for this error?
    BOOL ignoreError = FALSE;
    if (error.domain == NSURLErrorDomain) {
        if (error.code == NSURLErrorCancelled) {
            //ignore this one, interrupted load
            ignoreError = TRUE;
        }
    }
    //for some reason, twitter triggers a url request to about:blank in order to load.
    //This is necessary for twitter to load, but it triggers a webView error... just ignore about:blank errors...
    //NSString *theURLString = [webView.request.URL absoluteString]; this won't work - it just returns the last successful url
    NSString *urlStr = [error.userInfo objectForKey:@"NSErrorFailingURLStringKey"]; //this works
    if([urlStr hasPrefix:@"about:blank"]){
        //ignore about:blank error
        ignoreError = TRUE;
    }
    //if this error shouldn't be ignored
    if(!ignoreError){
        [spinner stopAnimating];
        //show the error message
        [alert show];
    }
}
- (void)webViewDidStartLoad:(UIWebView *)webView
{
    [spinner startAnimating];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    if ([[webView stringByEvaluatingJavaScriptFromString:@"document.readyState"] isEqualToString:@"complete"]) {
        //stop spinner animation
        [spinner stopAnimating];
        //get the page title
        NSString *theTitle=[webView stringByEvaluatingJavaScriptFromString:@"document.title"];
        //change the title to match the loaded page title
        [self.navigationItem setTitle:theTitle];
    }
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
 navigationType:(UIWebViewNavigationType)navigationType
{
    //if the URL CONTAINS the url string, open in a separate browser instead of within the app
    NSString *externalUrl = request.URL.absoluteString;
    if ([externalUrl rangeOfString:_outsideAppFlag].location != NSNotFound) {
        //if([externalUrl containsString:_outsideAppFlag]){
        //open the url in a SEPARATE browser outside of the app's webview
        NSURL *url = [NSURL URLWithString:externalUrl];
        [[UIApplication sharedApplication] openURL:url];
    }
    return true;
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
@end

That’s all for now. Hopefully, this article was a good launching point for your iOS app. If you are interested in also porting your application to Android, you should check out part 2 of this 3-part series. That’s all so happy coding!

< Back to Menu of Code

Posted By

Senior Software Engineer

Comments (1)

Juan C Martinez posted

Nice work. I am having problems trying to show a webpage located in my own machine (for example: 192.168.1.14:8080/index.html). I don't know why it is not working. I have tried other sites, and they work fine. Any suggestion?

Post a comment