Wkwebview: How to Preload Multiple Urls

WKWebView: Is it possible to preload multiple URLs?

I ended up just using NSURLCache and preloading all URLs using NSURLRequest and NSURLConnection. Then whenever I load a url into WKWebView it uses the cached requests per my cache policy.

Preload multiple local WebViews

Firstly, you should switch to WKWebView,UIWebView is no longer recommended to be used by Apple.

Secondly, you can create a pool of web views that get created and asked to load when the app starts. This way by the time the user switches to the web interface the web view might've got a chance to fully load.

For this you can use a class like this:

/// Keeps a cache of webviews and starts loading them the first time they are queried
class WebViewPreloader {
var webviews = [URL: WKWebView]()

/// Registers a web view for preloading. If an webview for that URL already
/// exists, the web view reloads the request
///
/// - Parameter url: the URL to preload
func preload(url: URL) {
webview(for: url).load(URLRequest(url: url))
}

/// Creates or returns an already cached webview for the given URL.
/// If the webview doesn't exist, it gets created and asked to load the URL
///
/// - Parameter url: the URL to prefecth
/// - Returns: a new or existing web view
func webview(for url: URL) -> WKWebView {
if let cachedWebView = webviews[url] { return cachedWebView }

let webview = WKWebView(frame: .zero)
webview.load(URLRequest(url: url))
webviews[url] = webview
return webview
}
}

and ask it to preload the url sometimes during the app startup:

// extension added for convenience, as we'll use the index url in at least
// two places
extension Bundle {
var indexURL: URL { return self.url(forResource: "index", withExtension: "html")! }
}

webviewPreloader.preload(url: Bundle.main.indexURL)

Thirdly, you might need to use a container view instead of the actual web view, in your controller:

@IBOutlet weak var webviewContainer: UIView!

What remains is to add the preloaded web view to the container when needed:

func loadWebview() {
// retrieve the preloaded web view, add it to the container
let webview = webviewPreloader.webview(for: Bundle.main.indexURL)
webview.frame = webviewContainer.bounds
webview.translatesAutoresizingMaskIntoConstraints = true
webview.autoresizingMask = [.flexibleWidth, .flexibleHeight]
webviewContainer.addSubview(webview)
}

And not lastly, be aware that keeping alive instances of web views, might carry performance penalties - memory and CPU-wise.

How to preload WKWebviews from the WKWebviews in swift5

I think I found a solution. But there's a problem. I can still see the animate image of the indicator. I don't think the search is complete.

First WKWebView

    var openSecondScreen : SecondWebViewController!
var preloadCheck = false
...
func openSecondScreen(){
let storyboard = UIStoryboard(name: "Main", bundle: nil)
openSecondScreen = storyboard.instantiateViewController(withIdentifier: "SecondWebViewController") as! SecondWebViewController
openSecondScreen.delegate = self
openSecondScreen.loadViewIfNeeded()
openSecondScreen.secondWKWebView.uiDelegate = self
openSecondScreen.secondWKWebView.navigationDelegate = self
preloadCheck = true
}
....
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
if preloadCheck {
self.navigationController?.pushViewController(openSecondScreen, animated: true)
preloadCheck = false
}
}

Second WKWebView

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
indicatorImage.isHidden = true
indicatorImage.stopAnimating()
}

So I solved the problem by concealing an indicator image when the screen was shown.

Second WKWebView

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
indicatorImage.isHidden = true
indicatorImage.stopAnimating()
}

When you press the button to open the secondWebview screen, indicator images are displayed on the firstWebview screen, indicator images disappear when navigation is complete, and the secondWebview screen appears preloaded.

(Xcode, Swift) Is it possible to load multiple WKWebViews simultaneously if they are on different viewControllers?

Nearly all of the delay that you're trying to avoid is due to downloading the data; instantiating a web view once the data is available is fast enough that users won't notice any delay. Given that, what you should really be looking to do is to download the data, not instantiate more web views than you need. The question that BJHStudios suggested in a comment provides a couple options for this, but you should look at NSURLSession rather than NSURLConnection; NSURLSession is very easy to use and offers a lot of flexibility. For example, you can configure a session to download data when the app is inactive.

If your app makes use of a data model, you could incorporate the web resources required by each of the four view controllers into the model. When your app starts up, it would naturally instantiate the model, and the model would then retrieve the necessary resources so that they'd be immediately available for display when needed.

How to preload WKWebView before pushing its viewcontroller?

As @matt's answer was incomplete, here my solution to wait the webview loading before pushing the next view controller:

class TableViewController: UITableViewController, WKNavigationDelegate, WKUIDelegate {

var webviewToLoad:WKWebView!
var destinationController:ViewController!
var alert:UIAlertController!

override func viewDidLoad() {
super.viewDidLoad()
}

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

alert = UIAlertController(title: "Alert", message: "Loading", preferredStyle: .alert)

destinationController = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "ViewController") as? ViewController

destinationController!.loadViewIfNeeded()
let webView:WKWebView = destinationController!.webView
webView.navigationDelegate = self
webView.uiDelegate = self

self.present(alert, animated: true, completion: nil)

let htmlString = "my html code"
webView.loadHTMLString(htmlString, baseURL: Bundle.main.resourceURL)
}

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {

alert.dismiss(animated: false, completion: nil)
self.navigationController?.pushViewController(destinationController!, animated: true)
}

}

And the UIViewController with the webview inside:

class ViewController: UIViewController, WKUIDelegate, WKNavigationDelegate {

var webView: WKWebView!
@IBOutlet var viewForWebview: UIView!
@IBOutlet var heightWebviewConstraint: NSLayoutConstraint!

override func viewDidLoad() {
super.viewDidLoad()

}

override func loadView() {

super.loadView()

webView = WKWebView()

if (webView != nil) {
webView!.frame = viewForWebview.frame

webView.translatesAutoresizingMaskIntoConstraints = false

viewForWebview.addSubview(webView!)

NSLayoutConstraint.activate([
webView.topAnchor.constraint(equalTo: viewForWebview.topAnchor),
webView.bottomAnchor.constraint(equalTo: viewForWebview.bottomAnchor),
webView.leadingAnchor.constraint(equalTo: viewForWebview.leadingAnchor),
webView.trailingAnchor.constraint(equalTo: viewForWebview.trailingAnchor)
])
}

}

override func viewDidLayoutSubviews() {

super.viewDidLayoutSubviews()

heightWebviewConstraint.constant = self.webView.scrollView.contentSize.height
self.view.setNeedsLayout()
self.view.setNeedsUpdateConstraints()

}
}

This code displays an UIAlertController during the webview loading, then dismiss the alert controller, and pushing the next controller, with the webview already loaded.

is it possible to preload html before displaying on webview

You can fetch the html file by a HTTP request at the pre-view and save it to cache, and check if it's in cache when you really entry it.

=================================================

4.24 updated
there are two factors

firstly, I don't know how the 'articles' array generated, and if it contain the src url.

secondly, I have reviewed the STWebArchiver code and I found it:

- (void)archiveHTMLData:(NSData *)aData
textEncoding:(NSString *)anEncoding
baseURL:(NSURL *)anURL
completionBlock:(void (^)(NSData *))completion {
htmlDocPtr doc = htmlParseDoc((xmlChar *)[aData bytes], [anEncoding UTF8String]);
NSArray *pathsForImagesAndScripts = [self valueForAttributeName:@"src" withEvaluatingXPath:@"//script[@src]|//img[@src]" inDocument:doc];
NSArray *pathsForStylesheets = [self valueForAttributeName:@"href" withEvaluatingXPath:@"//link[@rel='stylesheet'][@href]" inDocument:doc];
NSArray *resourcesPaths = [pathsForImagesAndScripts arrayByAddingObjectsFromArray:pathsForStylesheets];
NSArray *resourceUrls = [self absoluteURLsForPaths:resourcesPaths baseURL:anURL];
dispatch_async(dispatch_queue_create("Downloads", 0), ^{
NSMutableDictionary *resources = [NSMutableDictionary dictionary];
dispatch_apply([resourceUrls count], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^(size_t i) {
NSURL *url = [resourceUrls objectAtIndex:i];
NSString *urlString = [url absoluteString];
BOOL unfetched = NO;
@synchronized (resources) {
unfetched = ![resources objectForKey:urlString];
if (unfetched) {
[resources setObject:[NSNull null] forKey:urlString];
}
}
if (unfetched) {
NSURLResponse *response;
NSError *error;
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
NSMutableDictionary *resourceArchive = [NSMutableDictionary dictionaryWithObjectsAndKeys:
urlString, @"WebResourceURL",
[response MIMEType], @"WebResourceMIMEType",
data, @"WebResourceData", nil];
if ([response textEncodingName]) {
[resourceArchive setObject:[response textEncodingName] forKey:@"WebResourceTextEncodingName"];
}
@synchronized (resources) {
[resources setObject:resourceArchive forKey:urlString];
}
}
});
NSMutableDictionary *archiveSource = [NSMutableDictionary dictionaryWithObject:[resources allValues] forKey:@"WebSubresources"];
NSMutableDictionary *mainResource = [NSMutableDictionary dictionary];
[mainResource setObject:aData forKey:@"WebResourceData"];
[mainResource setObject:@"" forKey:@"WebResourceFrameName"];
[mainResource setObject:@"text/html" forKey:@"WebResourceMIMEType"];
[mainResource setObject:anEncoding forKey:@"WebResourceTextEncodingName"];
[mainResource setObject:[anURL absoluteString] forKey:@"WebResourceURL"];
[archiveSource setObject:mainResource forKey:@"WebMainResource"];
NSData *webArchive = [NSPropertyListSerialization dataFromPropertyList:archiveSource
format:NSPropertyListBinaryFormat_v1_0
errorDescription:NULL];
completion(webArchive);
});
xmlFreeDoc(doc);
}

which means that STWebArchiver cache not only the pure HTML file but also the resource requested.

PreLoad UIWebView on a not yet displayed UIViewController

I had your same problem for UIWebView inside a UIPageViewController and I found a better solution.

If you are in FirstViewController and you have your webView in your SecondViewController put the loadHTMLString or loadRequest method in the viewDidLoad of your SecondViewController and call [secondViewController.view layoutSubviews] for start loading in your FirstViewController.

Ex:

FirstViewController

-(void)viewDidLoad{
[super viewDidLoad];
// _secondViewController -> An instance of SecondVieController
[_secondViewController.view layoutSubviews];
}

SecondViewController

-(void)viewDidLoad{
[super viewDidLoad];
NSURL *url =[NSURL URLWithString:@"http://www.google.it"];
NSURLRequest *request =[NSURLRequest requestWithURL:url];
[self.webView loadRequest:request];
}


Related Topics



Leave a reply



Submit