iOS WKWebView, UIWebView hybrid

WebView

In iOS, we use UIWebView to show web page.
Form iOS8, WKWebView is available, this includes a lot of feature which is covered by Safari.
Also, it improves performance and memory usage.
But, this is used over iOS8. If covers both iOS7 and iOS8, we should change WebView according to version.

How to

  • Wrap UIWebView and WKWebView in handler class
  • Provide UIWebView class basic features
  • Delegate should be implemented by outside class
  • If you want to need property, add property method in handler

Sample

  • UIWebView+Info.h, UIWebView+Info.m
  • Category to get Web page info(for UIWebView)

  • WebHandler.h, WebHandler.m
  • This is implementation

  • UIFooterView.h, UIFooterView.m, UIHeaderView.h, UIHeaderView.m, ViewController.h, ViewController.m
  • Usage

You need to import WebKit framework in your project.

UIWebView+Info.h

@interface UIWebView (Info)
- (NSString *)title;
- (NSString *)URLString;
- (NSURL *)URL;
@end

UIWebView+Info.m

@implementation UIWebView (Info)
- (NSString *)title {
    return [self stringByEvaluatingJavaScriptFromString:@"document.title"];
}
- (NSString *)URLString {
    return [self stringByEvaluatingJavaScriptFromString:@"document.URL"];
}
- (NSURL *)URL {
    return [NSURL URLWithString:self.URLString];
}
@end

WebHandler.h

typedef void (^CompletionBlock)(id, NSError*);
 
@interface WebHandler : NSObject
 
- (id)webView;
- (void)layout:(CGRect)frame;
- (void)setDelegate:(id)delegate;
- (void)clean;
 
// Load
- (void)loadRequest:(NSURLRequest *)request;
- (void)loadURL:(NSString *)url;
- (void)stopLoading;
 
// Web history
- (void)goBack;
- (void)goForward;
- (BOOL)canGoBack;
- (BOOL)canGoForward;
- (BOOL)isLoading;
 
// JavaScript
- (void)stringByEvaluatingJavaScriptFromString:(NSString *)javascript completion:(CompletionBlock)completion;
// Info
- (NSString *)title;
- (NSURL *)URL;
@end

WebHandler.m

@interface WebHandler()
 
@property (nonatomic) UIWebView *uiwebView;
@property (nonatomic) WKWebView *wkwebView;
@property (nonatomic) BOOL ios8;
 
@end
 
@implementation WebHandler
 
- (id)init {
    if (self = [super init]) {
        self.ios8 = [WKWebView class] != nil;
        if (self.ios8) {
            self.wkwebView = [[WKWebView alloc] init];
        }
        else {
            self.uiwebView = [[UIWebView alloc] init];
        }
    }
    return self;
}
 
- (id)webView {
    if (self.ios8) {
        return self.wkwebView;
    }
    return self.uiwebView;
}
 
- (void)layout:(CGRect)frame {
    if (self.ios8) {
        self.wkwebView.frame = frame;
 
    }
    else {
        self.uiwebView.frame = frame;
    }
}
 
- (void)setDelegate:(id)delegate {  // This is for Navigation Delegate
    if (self.ios8) {
        self.wkwebView.navigationDelegate = delegate;
    }
    else {
        self.uiwebView.delegate = delegate;
    }
}
 
- (void)setUIDelegate:(id)delegate {  // This is for only ios8
    if (self.ios8) {
        self.wkwebView.UIDelegate = delegate;
    }
}
 
- (void)clean {
    if (self.ios8) {
        self.wkwebView.navigationDelegate = nil;
    }
    else {
        self.uiwebView.delegate = nil;
    }
     
    if ([self isLoading]) {
        [self stopLoading];
    }
}
 
 
#pragma mark - Common Features
 
/*
 * Load
 */
- (void)loadRequest:(NSURLRequest *)request {
    if (self.ios8) {
        [self.wkwebView loadRequest:request];
    }
    else {
        [self.uiwebView loadRequest:request];
    }
}
 
- (void)loadURL:(NSString *)url {
    [self loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]];
}
 
- (void)stopLoading {
    if (self.ios8) {
        [self.wkwebView stopLoading];
    }
    else {
        [self.uiwebView stopLoading];
    }
}
 
/*
 *  History
 */
- (void)goBack {
    if (self.ios8) {
        [self.wkwebView goBack];
    }
    else {
        [self.uiwebView goBack];
    }
}
 
- (void)goForward {
    if (self.ios8) {
        [self.wkwebView goForward];
    }
    else {
        [self.uiwebView goForward];
    }
}
 
- (BOOL)canGoBack {
    if (self.ios8) {
        return [self.wkwebView canGoBack];
    }
    else {
        return [self.uiwebView canGoBack];
    }
}
 
- (BOOL)canGoForward {
    if (self.ios8) {
        return [self.wkwebView canGoForward];
    }
    else {
        return [self.uiwebView canGoForward];
    }
}
 
- (BOOL)isLoading {
    if (self.ios8) {
        return [self.wkwebView isLoading];
    }
    else {
        return [self.uiwebView isLoading];
    }
}
 
// goToBackForwardListItem is only for iOS8 WKWebView
 
#pragma mark - JavaScript
- (void)stringByEvaluatingJavaScriptFromString:(NSString *)javascript completion:(CompletionBlock)completion {
    if (self.ios8) {
        [self.wkwebView evaluateJavaScript:javascript completionHandler:completion];
    }
    else {
        [self.uiwebView stringByEvaluatingJavaScriptFromString:javascript];
    }
}
 
 
#pragma mark - Info
/*
 * Info
 */
- (NSString *)title {
    if (self.ios8) {
        return [self.wkwebView title];
    }
    else {
        return [self.webView title];
    }
}
 
- (NSURL *)URL {
    if (self.ios8) {
        return [self.wkwebView URL];
    }
    else {
        return [self.webView URL];
    }
}
@end

UIHeaderView.h

@interface UIHeaderView : UIView
@property (nonatomic) UILabel *titleLabel;
@end

UIHeaderView.m

@implementation UIHeaderView
 
- (id)init {
    if(self = [super init]) {
        [self setBackgroundColor:[UIColor grayColor]];
         
        self.titleLabel = [[UILabel alloc] init];
        [self.titleLabel setTextColor:[UIColor whiteColor]];
        [self.titleLabel setTextAlignment:NSTextAlignmentCenter];
        [self addSubview:self.titleLabel];
    }
    return self;
}
 
- (void)layoutSubviews {
    self.titleLabel.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
}
@end

UIFooterView.h

@interface UIFooterView : UIView
@property (nonatomic) UIButton *backButton;
@property (nonatomic) UIButton *forwardButton;
@end

UIFooterView.m

@implementation UIFooterView
 
- (id)init {
    if(self = [super init]) {
        [self setBackgroundColor:[UIColor grayColor]];
        self.backButton = [[UIButton alloc] init];
        [self.backButton setTitle:@"Back" forState:UIControlStateNormal];
        [self.backButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
         
        self.forwardButton = [[UIButton alloc] init];
        [self.forwardButton setTitle:@"Forward" forState:UIControlStateNormal];
        [self.forwardButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
         
        [self addSubview:self.backButton];
        [self addSubview:self.forwardButton];
    }
    return self;
}
 
- (void)layoutSubviews {
    CGRect viewRect = self.frame;
    self.backButton.frame = CGRectMake(0, 0, kBUTTONSIZE, viewRect.size.height);
    self.forwardButton.frame = CGRectMake(0 + kBUTTONSIZE + 10, 0, kBUTTONSIZE, viewRect.size.height);
}
@end

ViewController.h

@interface ViewController : UIViewController<UIWebViewDelegate, WkNavigationDelegate>
@end

ViewController.m

#define kHEADERHEIGHT 44
#define kFOOTERHEIGHT 44

@interface ViewController ()
 
@property (nonatomic) WebHandler *webhandler;
@property (nonatomic) UIHeaderView *header;
@property (nonatomic) UIFooterView *footer;
@property (nonatomic) int webCount;
 
@end
 
@implementation ViewController
 
- (void)viewDidLoad {
    [super viewDidLoad];
     
    self.view.backgroundColor = [UIColor whiteColor];
     
    // Web
    self.webhandler = [[WebHandler alloc] init];
    [self.webhandler setDelegate:self];
     
    self.webCount = 0;
     
    // Header
    self.header = [[UIHeaderView alloc] init];
     
    // Footer
    self.footer = [[UIFooterView alloc] init];
    [self.footer.backButton addTarget:self action:@selector(backClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.footer.forwardButton addTarget:self action:@selector(forwardClick:) forControlEvents:UIControlEventTouchUpInside];
     
    [self.view addSubview:self.header];
    [self.view addSubview:self.webhandler.webView];
    [self.view addSubview:self.footer];
     
    [self.webhandler loadURL:@"http://google.com.sg"];
}
 
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    
    if (self.isViewLoaded && [self.view window] == nil) {
        [self dispose];
        self.view = nil;
    }
}
 
- (void)dealloc {
    [self dispose];
}
 
- (void)dispose {
    if (self.webhandler != nil) {
        [self.webhandler clean];
    }
}
 
- (void) viewWillLayoutSubviews {
     
    // Calculate layout
    CGRect viewRect = self.view.frame;
    self.header.frame = CGRectMake(viewRect.origin.x,
                                   viewRect.origin.y,
                                   viewRect.size.width,
                                   kHEADERHEIGHT);
    [self.webhandler layout:CGRectMake(viewRect.origin.x,
                                      viewRect.origin.y + kHEADERHEIGHT,
                                      viewRect.size.width,
                                      viewRect.size.height - kHEADERHEIGHT - kFOOTERHEIGHT)];
     
    self.footer.frame = CGRectMake(viewRect.origin.x,
                                   viewRect.origin.y + viewRect.size.height - kFOOTERHEIGHT,
                                   viewRect.size.width,
                                   kFOOTERHEIGHT);
     
}
 
#pragma mark - UIWebViewDelegate(iOS7)
- (void)webViewDidStartLoad:(UIWebView *)webView {
    // Start Loading
    NSLog(@"Start iOS7");
    self.webCount++;
}
 
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    self.webCount--;
    if (self.webCount == 0) {
        NSLog(@"Finish iOS7");
        [self.header.titleLabel setText:[self.webhandler title]];
    }
}
 
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
    NSLog(@"Erorr iOS7");
}
 
#pragma mark - WKWebViewDelegate(iOS8)
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
    NSLog(@"Start iOS8");
}
 
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
    [self.header.titleLabel setText:[self.webhandler title]];
    NSLog(@"title %@", [self.webhandler title]);
    NSLog(@"Finish iOS8");
}
 
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
    NSLog(@"Error iOS8");
}

- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error {
    NSLog(@"Error iOS8");
}
 
#pragma mark - Button Click
- (void)backClick:(id)sender {
    if ([self.webhandler canGoBack]) {
        [self.webhandler goBack];
    }
}
 
- (void)forwardClick:(id)sender {
    if ([self.webhandler canGoForward]) {
        [self.webhandler goForward];
    }
}
@end

Known Issue

UIWebView has memory leak.
WKWebView doesn’t have a lot of feature UIWebView has.

Ref

NS
iOS WKWebView
Qiita