Upgrading from UIWebView to WKWebView
From later this year you won't be able to submit new app releases containing the deprecated UIWebView. Apple recommend swapping these for the new WKWebView, which is all fine, but they don't give any useful examples, particularly in Objective-C, so here are some tips.
If you are targetting iOS before 11, you can't add this view using Interface Builder! If you try you will get an error, and your project won't compile. So instead you'll need to add it programmatically with your ViewController.
In the header file switch out the UIWebViewDelegate for the new WKNavigationDelegate:
@interface MyViewController : UIViewController <WKNavigationDelegate>
and add in a property to hold the new WKWebView:
// Needs to be added programmatically
// See: https://stackoverflow.com/questions/46221577/xcode-9-gm-wkwebview-nscoding-support-was-broken-in-previous-versions
@property (strong, nonatomic) WKWebView *webView;
Then in the main ViewController file update your viewDidLoad function:
- (void)viewDidLoad {
[super viewDidLoad];
...
// Create the new configuration object to set useful options
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
configuration.suppressesIncrementalRendering = YES;
configuration.ignoresViewportScaleLimits = NO;
configuration.dataDetectorTypes = WKDataDetectorTypeNone;
// Set the position - Use the full page, but above my tabBar
CGRect frame = self.view.bounds;
CGFloat tabBarHeight = frame.size.height - self.tabBarController.tabBar.frame.origin.y;
frame.size.height -= tabBarHeight;
// Create the new WKWebView
_webView = [[WKWebView alloc] initWithFrame:frame configuration:configuration];
// Set the delegate - note this is 'navigationDelegate' not just 'delegate'
_webView.navigationDelegate = self;
// Add it to the view
[self.view addSubview:_webView];
[self.view sendSubviewToBack:_webView];
// Load a blank page
[_webView loadHTMLString:@"<html style='margin:0;padding:0;height:100%;width:100%;background:#fff'><body style='margin:0;padding:0;height:100%;width:100%;background:#fff'></body><html>" baseURL:nil];
...
Then you can load up some content. I do this in viewWillAppear, and here's how to load a file called filename.html from bundle resources:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
...
// Load HTML File
[_webView loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"filename" ofType:@"html"] isDirectory:NO]]];
...
UPDATE: In iOS 13, Xcode prefers you to use - (WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL instead, so you can also load the local file using:
// Load HTML File
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"filename" ofType:@"html"] isDirectory:NO];
NSURL *dir = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"filename" ofType:@"html"] isDirectory:YES];
[_webView loadFileURL:url allowingReadAccessToURL:dir];
I have an activity indicator on the page, which spins until the content is first loaded. This helps if the app is ever a bit laggy. This is added via Interface Builder and centered in the view. Once the content first loads I want to hide it. I used to use - (void)webViewDidFinishLoad:(UIWebView *)webView, but we need to swap this out for - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation.
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
[UIView animateWithDuration:0.15 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self->_activityIndicator.alpha = 0.0;
} completion:^(BOOL finished){
if (finished) {
self->_activityIndicator.hidden = YES;
self->_activityIndicator.alpha = 1.0;
[self->_activityIndicator stopAnimating];
}
}];
}
I also like to track clicks on the page, because this is just an easy way to display nicely formatted text, and I want external web links to open in the default browser. This used to be done using - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType, but now you need to use - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler instead.
Here I use the default browser to open the external webpage if the link contains 'http'. It would be more robust to check the first four characters, but I know this will work for my app.
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSString *url = [NSString stringWithFormat:@"%@",navigationAction.request.URL.absoluteString];
BOOL httpRequest = [url containsString:@"http"];
if (navigationAction.navigationType == WKNavigationTypeLinkActivated && httpRequest) {
[[UIApplication sharedApplication] openURL:navigationAction.request.URL];
decisionHandler(WKNavigationActionPolicyCancel);
} else {
decisionHandler(WKNavigationActionPolicyAllow);
}
}