PretzelJesus
PretzelJesus

Reputation: 899

Loading UIWebView From Background Fetch

I am using a UIWebView in my app to load the contents of a website. I then use javascript to log into the website, and then I parse the contents of the page after I've logged in to grab the data I need. I do this every 5 minutes, which works fine in the foreground. However, when I background the app, I make a call to the UIViewController that holds the UIWebView and I hit the method that loads the URL into the UIWebView, but nothing happens after that.

I am fairly sure that the UIViewController must be active to update the UIWebView, but is there a way around this? Checking this data in the background is really the entire point of the app.

EDIT

jakepeterson - Based on your code, I tried to subclass a UIWebView starting in Swift. This is as far as I got:

    class CustomWebView: UIWebView, UIWebViewDelegate
{
    var webView: UIWebView
    var requestURL: NSURL
    var urlRequest: NSURLRequest

    init()
    {
        webView = UIWebView()
        requestURL = NSURL(string: "http://www.google.com")
        urlRequest = NSURLRequest(URL: requestURL)

        super.init(frame: CGRectMake(0, 0, 100, 100))
        webView.delegate = self
        webView.addSubview(webView.window)
        webView.loadRequest(request)
    }

    func webViewDidFinishLoad(sender: UIWebViewDelegate)
    {
        //Do stuff
    }
}

I am still not hitting the webViewDidFinishLoad delegate method. I think it has to do possibly with this line:

webView.addSubview(webView.window)

In your code you have self.view but that doesn't show up as an available option.

UPDATE

Here is what I am doing now and the delegates are now getting triggered:

import UIKit

class CustomWebView: UIWebView
{
    var uiView = UIView()

    init(frame: CGRect)
    {
        super.init(frame: frame)
        self.delegate = self
    }

    convenience init(frame:CGRect, request:NSURLRequest)
    {
        self.init(frame: frame)
        uiView.addSubview(self)
        loadRequest(request)
    }    
}

extension CustomWebView: UIWebViewDelegate
{
    func webViewDidStartLoad(webView: UIWebView!)
    {
        println("webViewDidStartLoad")
    }

    func webViewDidFinishLoad(webView: UIWebView)
    {
        println("webViewDidFinishLoad")
    }

    func webView(webView: UIWebView!, didFailLoadWithError error: NSError!)
    {
        println("An error occurred while loading the webview")
    }
}

Upvotes: 2

Views: 4115

Answers (2)

PretzelJesus
PretzelJesus

Reputation: 899

It appears that the trick I found of adding the control to a UIView to get the delegates to fire only works in the foreground. So for anyone checking this question there currently does not seem to be a way to do this in the background.

Upvotes: 0

jakenberg
jakenberg

Reputation: 2113

Here's an example of how the UIWebView class can be used objectively with asynchronous logic:

- (id)init
{
    self = [super init];
    if (self)
    {
        self.webView = [[UIWebView alloc] init];
        self.webView.delegate = self;
        [self.view addSubview:self.webView];

        self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
        [self.view addSubview:self.activityIndicator];
    }

    return self;
}

- (id)initWithUrl:(NSURL *)url
{
    self = [self init];
    if (self)
    {
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        [self.webView loadRequest:request];
        [self.activityIndicator startAnimating];
    }

    return self;
}

#pragma mark - UIWebViewDelegate

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    [self.activityIndicator stopAnimating];
}

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
    [self.activityIndicator stopAnimating];

    NSLog(@"An error occurred while loading. (error: %@)", error);

    // Alert the user
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Oops" message:@"Dude... we messed up. This web page can't be loaded at this time." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
    [alertView show];
}

Why does this work?

Because you are depending on the delegate methods of UIWebViewDelegate to tell you when the UIWebView has finished loading. I included the error delegate method as a bonus.

The example here is using a UIActivityIndicatorView class, which is designed for use in asynchronous tasks. By following this same implementation style, you can replace the activityIndicator with virtually anything you plan to act on the results of the UIWebView pragmatically with.

Good luck

UPDATE

Swift is a whole other beast. Your swift class has a plethora of errors in it. This is how I would re-write what I originally gave you in ObjC in Swift:

import UIKit

class CustomWebView: UIWebView {

    var activityIndicator:UIActivityIndicatorView

    init(frame: CGRect) {
        self.activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
        super.init(frame: frame)
        self.delegate = self
    }

    convenience init(frame:CGRect, request:NSURLRequest) {
        self.init(frame: frame)
        loadRequest(request)
    }

}

extension CustomWebView: UIWebViewDelegate {

    func webViewDidFinishLoad(webView: UIWebView) {
        self.activityIndicator.stopAnimating()
    }

    func webView(webView: UIWebView!, didFailLoadWithError error: NSError!) {
        self.activityIndicator.stopAnimating()

        println("An error occurred while loading the webview")
    }

}

Upvotes: 2

Related Questions