Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't change UserAgent in UIWebview after loading page

Tags:

ios

uiwebview

My IOS app has several UIWebView's in it. From time-to-time, the user may needs to change from a mobile site to a desktop site of any given URL. I'm using spoofing to do this by changing the UserAgent:

 + (void) startSpoofingUserAgent
 {
     [[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"UserAgent" : @"Desktop" }];
     [[NSUserDefaults standardUserDefaults] synchronize];
 }

I also have a method that kills the spoofing:

 + (void)stopSpoofingUserAgent
 {
      NSDictionary *registeredDefaults = [[NSUserDefaults standardUserDefaults] volatileDomainForName:NSRegistrationDomain];
      if ([registeredDefaults objectForKey:@"UserAgent"] != nil)
      {
          NSMutableDictionary *mutableCopy = [NSMutableDictionary dictionaryWithDictionary:registeredDefaults];
          [mutableCopy removeObjectForKey:@"UserAgent"];
          [[NSUserDefaults standardUserDefaults] setVolatileDomain:mutableCopy forName:NSRegistrationDomain];
          [[NSUserDefaults standardUserDefaults] synchronize];
      }
}

I then give the user the ability to change from desktop-to-mobile and mobile-to-desktop with a button. I then reload the URL with this code:

        self.webView.delegate = self;
        self.webView.scalesPageToFit = YES;
        self.webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

        //Create a URL object.
        NSURL *url = [NSURL URLWithString:self.convertedURL];

        //URL Request Object
        if(self.defaultToDesktopSite)
        {
            [MINNetworkingUtils startSpoofingUserAgent];
        }
        else
        {
            [MINNetworkingUtils stopSpoofingUserAgent];
        }

        NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];

        //Load the request in the UIWebView.
        [self.webView loadRequest:requestObj];

I also buffer the current state the user has the UIWebView in so that it retains the setting across instances. The first time the app runs, it works great regardless of which state it's in (Desktop or Mobile).

THE PROBLEM: The problem is that I cannot change the state and have that reflected in the app AFTER the initial load.

Scenario 1:

  • User runs app
  • UIWebView is set to spoof UserAgent, thereby forcing the UIWebView to load the desktop site
  • WORKS GREAT!

Scenario 2:

  • User runs app
  • UIWebView is set to NOT spoof UserAgent, thereby letting the UIWebView load a mobile site if it's available
  • WORKS GREAT!

Scenario 3:

  • User runs app
  • UIWebView is set to spoof UserAgent
  • Site comes up with Desktop site (as expected)
  • User changes the state to "Mobile" by selecting button
  • The app reloads the UIWebView using the code above (which UN-spoofs the connection)
  • DOESN'T WORK
  • The UIWebView reloads but it's still using the "Desktop" site and NOT the mobile site
  • (THIS SCENARIO works the same way if reversed. In other words, if the UIWebView is supposed to come us as "Mobile" the first time. It works until I try to change it and reload it.)

Scenario 4:

  • Go through the same steps as Scenario 3 BUT unload the app from memory and reload the app
  • Since I journal the state, when the app comes up, the UIWebView thinks it's supposed to come up in "Mobile" mode (because the user changed it when they ran the app last time).
  • IT WORKS AGAIN! The UIWebView comes up in "Mobile" mode.

What seems to be happening, is that the UIWebView is buffering something somewhere and won't allow me to reload the UIWebView with a different spoofing. If the app is unloaded and reloaded, UIWebView works as expected.

I have tried to the following things to force the UIWebView to NOT buffer anything and have had no success:

 [[NSURLCache sharedURLCache] removeAllCachedResponses];
 ... reload URL

  self.convertedPageURL = [NSString stringWithFormat:@"%@?t=%@", self.convertedPageURL, randomString];
  ... reload URL

Like I said, I can't get the UIWebView to change from Desktop-to-Mobile or Mobile-to-Desktop WITHOUT unloading the app and reloading it.

HELP!!!

EDIT

This is the code that I was using to get the current value. Once I've called this line of code, I can no longer change the UserAgent:

         defaultUserAgentString = [webView stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"];
like image 763
JustLearningAgain Avatar asked Mar 29 '15 00:03

JustLearningAgain


1 Answers

The UserAgent user-defaults key is private API. It therefore has no guaranteed behaviour. All answers will be empirical.

That being said, it sounds like the correct solution is to kill and recreate your UIWebView following each change. UIView doesn't conform to NSCopying so you'll need to do something manual such as:

UIWebView *newWebView = [[UIWebView alloc] initWithFrame:self.webView.frame];
newWebView.delegate = self;
newWebView.scalesPageToFit = YES;
// ... and the autoreseizing mask, etc — no need to do this upon every load

[self.webView.superview insertSubview:newWebView aboveSubview:self.webView];
[self.webView removeFromSuperview];
self.webView = newWebView;

If that fails to work, you probably need to switch to a documented means of setting the user agent. I'd suggest implementing your own NSURLProtocol handler that accepts http and https requests it hasn't yet seen, modifies the header field and dispatches them a second time, that time declining to take them so that they drop down to the system. It'll be hassle but it'll allow you to change that, or any other header field, reliably and with immediate effect.

See kifi's engineering blog for an example.

like image 104
Tommy Avatar answered Oct 01 '22 21:10

Tommy