2 * Copyright (C) 2016 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
26 #import "WebViewController.h"
28 #import "TabViewController.h"
29 #import <WebKit/WKNavigation.h>
30 #import <WebKit/WKNavigationDelegate.h>
31 #import <WebKit/WKWebView.h>
32 #import <WebKit/WKWebViewConfiguration.h>
34 @implementation NSURL (BundleURLMethods)
35 + (NSURL *)__bundleURLForFileURL:(NSURL *)url bundle:(NSBundle *)bundle
37 if (![url.scheme isEqualToString:@"file"])
39 NSString *resourcePath = [bundle.resourcePath stringByAppendingString:@"/"];
40 if (![url.path hasPrefix:resourcePath])
42 NSURLComponents *bundleComponents = [[NSURLComponents alloc] init];
43 bundleComponents.scheme = @"bundle";
44 bundleComponents.path = [url.path substringFromIndex:resourcePath.length];
45 return [bundleComponents.URL copy];
48 + (NSURL *)__fileURLForBundleURL:(NSURL *)url bundle:(NSBundle *)bundle
50 if (![url.scheme isEqualToString:@"bundle"])
52 return [bundle.resourceURL URLByAppendingPathComponent:url.path];
56 @interface WebViewController () <WKNavigationDelegate> {
57 WKWebView *_currentWebView;
59 - (WKWebView *)createWebView;
60 - (void)removeWebView:(WKWebView *)webView;
61 - (void)setCurrentWebView:(WKWebView *)webView;
64 void* EstimatedProgressContext = &EstimatedProgressContext;
65 void* TitleContext = &TitleContext;
66 void* URLContext = &URLContext;
68 @implementation WebViewController
73 self.webViews = [[NSMutableArray alloc] initWithCapacity:1];
74 self.tabViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"idTabViewController"];
75 self.tabViewController.parent = self;
76 self.tabViewController.modalPresentationStyle = UIModalPresentationPopover;
78 [self setCurrentWebView:[self createWebView]];
82 - (void)didReceiveMemoryWarning
84 [super didReceiveMemoryWarning];
89 - (IBAction)reload:(id)sender
91 [self.currentWebView reload];
94 - (IBAction)goBack:(id)sender
96 [self.currentWebView goBack];
99 - (IBAction)goForward:(id)sender
101 [self.currentWebView goForward];
104 - (IBAction)urlFieldEditingBegan:(id)sender
106 self.urlField.selectedTextRange = [self.urlField textRangeFromPosition:self.urlField.beginningOfDocument toPosition:self.urlField.endOfDocument];
109 - (IBAction)navigateTo:(id)sender
111 [self.urlField resignFirstResponder];
112 NSString* requestedDestination = self.urlField.text;
113 if ([requestedDestination rangeOfString:@"^[\\p{Alphabetic}]+:" options:(NSRegularExpressionSearch | NSCaseInsensitiveSearch | NSAnchoredSearch)].location == NSNotFound)
114 requestedDestination = [@"http://" stringByAppendingString:requestedDestination];
115 NSURL* requestedURL = [NSURL URLWithString:requestedDestination];
116 if ([requestedURL.scheme isEqualToString:@"bundle"]) {
117 NSBundle *frameworkBundle = [NSBundle bundleForClass:[WebViewController class]];
118 requestedURL = [NSURL __fileURLForBundleURL:requestedURL bundle:frameworkBundle];
119 [self.currentWebView loadFileURL:requestedURL allowingReadAccessToURL:frameworkBundle.resourceURL];
121 [self.currentWebView loadRequest:[NSURLRequest requestWithURL:requestedURL]];
124 - (IBAction)showTabs:(id)sender
126 [self presentViewController:self.tabViewController animated:YES completion:nil];
127 self.tabViewController.popoverPresentationController.barButtonItem = self.tabButton;
130 #pragma mark Public methods
132 @dynamic currentWebView;
134 - (WKWebView *)currentWebView
136 return _currentWebView;
139 - (void)setCurrentWebView:(WKWebView *)webView
141 [_currentWebView removeObserver:self forKeyPath:@"estimatedProgress" context:EstimatedProgressContext];
142 [_currentWebView removeObserver:self forKeyPath:@"URL" context:URLContext];
143 [_currentWebView removeFromSuperview];
145 _currentWebView = webView;
147 [_currentWebView addObserver:self forKeyPath:@"estimatedProgress" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:EstimatedProgressContext];
148 [_currentWebView addObserver:self forKeyPath:@"URL" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:URLContext];
149 [self.webViewContainer addSubview:_currentWebView];
152 - (void)selectWebViewAtIndex:(NSUInteger)index
154 if (index >= self.webViews.count)
156 self.currentWebView = self.webViews[index];
159 - (void)removeWebViewAtIndex:(NSUInteger)index
161 if (index >= self.webViews.count)
163 [self removeWebView:self.webViews[index]];
168 self.currentWebView = [self createWebView];
171 #pragma mark Internal methods
173 - (WKWebView *)createWebView
175 WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
176 WKWebView *webView = [[WKWebView alloc] initWithFrame:self.webViewContainer.bounds configuration:configuration];
177 webView.navigationDelegate = self;
178 webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
179 [webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:TitleContext];
180 [self.webViews addObject:webView];
182 [self.tabViewController.tableView reloadData];
187 - (void)removeWebView:(WKWebView *)webView
189 NSUInteger index = [self.webViews indexOfObject:webView];
190 if (index != NSNotFound) {
191 [self.webViews[index] removeObserver:self forKeyPath:@"title" context:TitleContext];
192 [self.webViews removeObjectAtIndex:index];
193 } if (!self.webViews.count)
194 [self createWebView];
195 if (index >= self.webViews.count)
196 index = self.webViews.count - 1;
197 [self setCurrentWebView:self.webViews[index]];
199 [self.tabViewController.tableView reloadData];
202 #pragma mark Navigation Delegate
204 - (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error
206 [webView loadHTMLString:[error description] baseURL:nil];
211 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
213 if (context == EstimatedProgressContext) {
214 float value = [[change valueForKey:NSKeyValueChangeNewKey] floatValue];
215 [self.progressView setProgress:value animated:YES];
216 } else if (context == URLContext) {
217 NSURL *newURLValue = [change valueForKey:NSKeyValueChangeNewKey];
218 if ([newURLValue isKindOfClass:[NSURL class]]) {
219 if ([newURLValue.scheme isEqualToString:@"file"])
220 newURLValue = [NSURL __bundleURLForFileURL:newURLValue bundle:[NSBundle bundleForClass:[WebViewController class]]];
221 self.urlField.text = [newURLValue absoluteString];
222 } else if ([newURLValue isKindOfClass:[NSNull class]])
223 self.urlField.text = @"";
224 } else if (context == TitleContext)
225 [self.tabViewController.tableView reloadData];
227 [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];