| /* |
| * 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 <WebKit/WKNavigation.h> |
| #import <WebKit/WKNavigationDelegate.h> |
| #import <WebKit/WKWebView.h> |
| #import <WebKit/WKWebViewConfiguration.h> |
| |
| @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 () <WKNavigationDelegate> { |
| 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]; |
| 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 |