/* * Copyright (C) 2016 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #import "WebViewController.h" #import "TabViewController.h" #import #import #import #import #import @implementation NSURL (BundleURLMethods) + (NSURL *)__bundleURLForFileURL:(NSURL *)url bundle:(NSBundle *)bundle { if (![url.scheme isEqualToString:@"file"]) return nil; NSString *resourcePath = [bundle.resourcePath stringByAppendingString:@"/"]; if (![url.path hasPrefix:resourcePath]) return nil; NSURLComponents *bundleComponents = [[NSURLComponents alloc] init]; bundleComponents.scheme = @"bundle"; bundleComponents.path = [url.path substringFromIndex:resourcePath.length]; return [bundleComponents.URL copy]; } + (NSURL *)__fileURLForBundleURL:(NSURL *)url bundle:(NSBundle *)bundle { if (![url.scheme isEqualToString:@"bundle"]) return nil; return [bundle.resourceURL URLByAppendingPathComponent:url.path]; } @end @interface WebViewController () { WKWebView *_currentWebView; } - (WKWebView *)createWebView; - (void)removeWebView:(WKWebView *)webView; - (void)setCurrentWebView:(WKWebView *)webView; @end void* EstimatedProgressContext = &EstimatedProgressContext; void* TitleContext = &TitleContext; void* URLContext = &URLContext; @implementation WebViewController - (void)viewDidLoad { [super viewDidLoad]; self.webViews = [[NSMutableArray alloc] initWithCapacity:1]; self.tabViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"idTabViewController"]; self.tabViewController.parent = self; self.tabViewController.modalPresentationStyle = UIModalPresentationPopover; WKWebView *webView = [self createWebView]; [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://webkit.org"]]]; [self setCurrentWebView:webView]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } #pragma mark Actions - (IBAction)reload:(id)sender { [self.currentWebView reload]; } - (IBAction)goBack:(id)sender { [self.currentWebView goBack]; } - (IBAction)goForward:(id)sender { [self.currentWebView goForward]; } - (IBAction)urlFieldEditingBegan:(id)sender { self.urlField.selectedTextRange = [self.urlField textRangeFromPosition:self.urlField.beginningOfDocument toPosition:self.urlField.endOfDocument]; } - (IBAction)navigateTo:(id)sender { [self.urlField resignFirstResponder]; NSString* requestedDestination = self.urlField.text; if ([requestedDestination rangeOfString:@"^[\\p{Alphabetic}]+:" options:(NSRegularExpressionSearch | NSCaseInsensitiveSearch | NSAnchoredSearch)].location == NSNotFound) requestedDestination = [@"http://" stringByAppendingString:requestedDestination]; NSURL* requestedURL = [NSURL URLWithString:requestedDestination]; if ([requestedURL.scheme isEqualToString:@"bundle"]) { NSBundle *frameworkBundle = [NSBundle bundleForClass:[WebViewController class]]; requestedURL = [NSURL __fileURLForBundleURL:requestedURL bundle:frameworkBundle]; [self.currentWebView loadFileURL:requestedURL allowingReadAccessToURL:frameworkBundle.resourceURL]; } [self.currentWebView loadRequest:[NSURLRequest requestWithURL:requestedURL]]; } - (IBAction)showTabs:(id)sender { [self presentViewController:self.tabViewController animated:YES completion:nil]; self.tabViewController.popoverPresentationController.barButtonItem = self.tabButton; } #pragma mark Public methods @dynamic currentWebView; - (WKWebView *)currentWebView { return _currentWebView; } - (void)setCurrentWebView:(WKWebView *)webView { [_currentWebView removeObserver:self forKeyPath:@"estimatedProgress" context:EstimatedProgressContext]; [_currentWebView removeObserver:self forKeyPath:@"URL" context:URLContext]; [_currentWebView removeFromSuperview]; _currentWebView = webView; [_currentWebView addObserver:self forKeyPath:@"estimatedProgress" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:EstimatedProgressContext]; [_currentWebView addObserver:self forKeyPath:@"URL" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:URLContext]; [self.webViewContainer addSubview:_currentWebView]; } - (void)selectWebViewAtIndex:(NSUInteger)index { if (index >= self.webViews.count) return; self.currentWebView = self.webViews[index]; } - (void)removeWebViewAtIndex:(NSUInteger)index { if (index >= self.webViews.count) return; [self removeWebView:self.webViews[index]]; } - (void)addWebView { self.currentWebView = [self createWebView]; } #pragma mark Internal methods - (WKWebView *)createWebView { WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init]; configuration.preferences._mockCaptureDevicesEnabled = YES; WKWebView *webView = [[WKWebView alloc] initWithFrame:self.webViewContainer.bounds configuration:configuration]; webView.navigationDelegate = self; webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:TitleContext]; [self.webViews addObject:webView]; [self.tabViewController.tableView reloadData]; return webView; } - (void)removeWebView:(WKWebView *)webView { NSUInteger index = [self.webViews indexOfObject:webView]; if (index != NSNotFound) { [self.webViews[index] removeObserver:self forKeyPath:@"title" context:TitleContext]; [self.webViews removeObjectAtIndex:index]; } if (!self.webViews.count) [self createWebView]; if (index >= self.webViews.count) index = self.webViews.count - 1; [self setCurrentWebView:self.webViews[index]]; [self.tabViewController.tableView reloadData]; } #pragma mark Navigation Delegate - (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error { [webView loadHTMLString:[error description] baseURL:nil]; } #pragma mark KVO - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (context == EstimatedProgressContext) { float value = [[change valueForKey:NSKeyValueChangeNewKey] floatValue]; [self.progressView setProgress:value animated:YES]; } else if (context == URLContext) { NSURL *newURLValue = [change valueForKey:NSKeyValueChangeNewKey]; if ([newURLValue isKindOfClass:[NSURL class]]) { if ([newURLValue.scheme isEqualToString:@"file"]) newURLValue = [NSURL __bundleURLForFileURL:newURLValue bundle:[NSBundle bundleForClass:[WebViewController class]]]; self.urlField.text = [newURLValue absoluteString]; } else if ([newURLValue isKindOfClass:[NSNull class]]) self.urlField.text = @""; } else if (context == TitleContext) [self.tabViewController.tableView reloadData]; else [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } @end