20

I can create Safari View Controller without problem:

let svc = SFSafariViewController(URL: NSURL(string: remote_url)!, entersReaderIfAvailable: true)
self.presentViewController(svc, animated: true, completion: nil)

Is there any way I can preload the URL before I present the view controller to the user?

For example, I can preload the URL (web content) in the background first, and after the user clicks on something, I can show the Safari View Controller with the content right away. The user will feel the page loading is faster or instant.

P.S. Workarounds/hacks are also acceptable. For example, using cache or starting the view controller in background, etc.

EDIT: please consider SFSafariViewController only.

Joe Huang
  • 6,296
  • 7
  • 48
  • 81
  • SFSafariViewController may not support for preloading. you can use NSURLCache and preloading URL using NSURLRequest and NSURLConnection. Then whenever you want to load a url into SFSafariViewController it uses the cached requests per your cache policy. – Ketan P Feb 01 '16 at 13:19
  • @KetanP If SFSafariViewController will use the cached requests, this kind of preloading is also good enough. is there any further info about how to do it? – Joe Huang Feb 01 '16 at 15:04
  • @KetanP I can't find a way to achieve what you said. Are you sure `SFSafariViewController` will use the cache if preloaded in some way or it's just an assumption? – Joe Huang Feb 03 '16 at 02:35
  • I am not checked in 'SFSafariViewController ' It is working web view. So I assume that it may work for 'SFSafariViewController'. – Ketan P Feb 03 '16 at 06:29
  • @KetanP Do you have further information or reference about how to do it for web view? – Joe Huang Feb 03 '16 at 06:50
  • 1
    I will send you same after some time. I busy with some urgent task. – Ketan P Feb 03 '16 at 06:54
  • For reference, if someone is still wondering: Safari view controller does not use the app's cache. It loads and renders its content in a helper process, which cannot access any of the app's sandbox files, including URL cache. – Léo Natan Dec 16 '21 at 23:44

5 Answers5

12

Here is a solution. Obviously, if you click on the button right away you'll see the loading. But basically, I load the Browser and put the view behind another one and I put a button in this other view.

When you press the button, the browser is bring to the front, already loaded. The only problem here is that I'm not using any transition but that's one solution at least.

import UIKit
import SafariServices

class ViewController: UIViewController {
  var svc = SFSafariViewController(URL: NSURL(string: "https://microsoft.com/")!, entersReaderIfAvailable: true)
  var safariView:UIView?
  let containerView = UIView()
  let btn = UIButton()

  override func viewDidLoad() {
    super.viewDidLoad()
    //let tmpView = svc.view
    addChildViewController(svc)
    svc.didMoveToParentViewController(self)
    svc.view.frame = view.frame
    containerView.frame = view.frame
    containerView.backgroundColor = UIColor.redColor()
    safariView = svc.view
    view.addSubview(safariView!)
    view.addSubview(containerView)

    btn.setTitle("Webizer", forState: UIControlState.Normal)
    btn.titleLabel!.textColor = UIColor.blackColor()
    btn.addTarget(self, action: "buttonTouched:", forControlEvents: .TouchUpInside)
    btn.frame = CGRectMake(20, 50, 100, 100)
    containerView.addSubview(btn)

    view.sendSubviewToBack(safariView!)

    // Do any additional setup after loading the view, typically from a nib.
  }

  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
  }

  @IBAction func buttonTouched(sender: AnyObject) {
    view.bringSubviewToFront(safariView!)
    //self.presentViewController(svc, animated: true, completion: nil)
  }


}
Mikael
  • 2,355
  • 1
  • 21
  • 45
  • I tested your method and it works well except... the app will crash after 30+ tries. Please take a look at this SO question I asked for this issue, please help if you can: http://stackoverflow.com/questions/35598449/how-to-remove-sfsafariviewcontroller-as-a-child-view-controller-correctly – Joe Huang Feb 28 '16 at 04:17
4

Sadly this behaviour is not supported with the current implementation of SFSafariViewController. I would encourage filing a radar with Apple to add support for this behaviour but like others have suggested your best bet is to use WKWebView and start loading before its added to the hierarchy.

I came across a lovely radar from Twitter that actually mentions exactly what you're asking for. I think you might find the following requests useful:

High Priority: - Ability to warm the SFSafariViewController before actually presenting it with a URL, URL request, HTML data or file on disk - Currently, are investing heavily into warming the shared URL cache for high priority Tweets so that if the user hits that Tweet we will open UIWebView (sadly not WKWebView) with that pre-cached web page. If we could just warm an SFSafariViewController with the desired link, this would eliminate an enormous amount of effort on our end.

You can see in their implementation they simply cache responses using UIWebView since WKWebView seems to obfuscate the caching semantics a bit. The only risk is that UIWebView is a likely candidate for deprecation as you see in their docs "In apps that run in iOS 8 and later, use the WKWebView class instead of using UIWebView."

So unfortunately it seems that their are many hoops you need to jump through to get this all going so your best bet for now is to just pester Apple and dupe Twitters radar.

Daniel Galasko
  • 23,617
  • 8
  • 77
  • 97
  • I seriously doubt Apple would ever support this. Twitter has no way of knowing whether or not a user will actually want to tap a "high priority tweet", so they're just wasting everybody's battery and data. – bpapa Mar 10 '16 at 19:00
2

You could try using a http cache, but I don't think it would work as the Safari View Controller is working as a separate process (probably the same as Safari), so that's why it e.g. circumvents ATS.

The only way I can think of this working is to somehow force the user's Safari to load it? openURL: or adding to Reading List maybe? This doesn't sound like a viable solution.

You can always experiment with custom presentation of the view controller, attach it the view hierarchy, trigger appearance events, but set its frame to CGRectMake(0,0,1,1) or attach it somewhere off-screen, then wait a while and represent it with a correct frame.

Alistra
  • 5,177
  • 2
  • 30
  • 42
  • I have thought about the last option (present it in the background somehow), but I can't figure out a way. – Joe Huang Feb 09 '16 at 22:20
  • Try using `- (void)beginAppearanceTransition:(BOOL)isAppearing animated:(BOOL)animated` and actually presenting after some time. – Alistra Feb 09 '16 at 22:35
  • Also try a custom `transitionDelegate` like here http://www.teehanlax.com/blog/custom-uiviewcontroller-transitions/ that will present it but somewhere off screen, and the represent it normally after a few secs – Alistra Feb 09 '16 at 22:39
1

you can download the web page using the following code . and represent it with the help of svc let data:NSData? do { let weatherData = try NSData(contentsOfURL: NSURL(string: remote_url)!, options: NSDataReadingOptions()) data = weatherData print(weatherData) } catch { print(error) }

and load it when you needed in the svc

Sucharu Hasija
  • 1,096
  • 11
  • 23
  • 2
    This doesn't work. For one, you can't just "load" the HTML into an SFSafariViewController, you have to present it with a URL. Secondly, SFSafariViewController only accepts HTTP or HTTPS protocol URLs, so it can't read local files or data. – brimstone Feb 13 '16 at 04:55
  • This would kill all interactive JS based aspects of the page – Alistra Feb 13 '16 at 22:48
0

While it's technically possible to use the solution above to achieve what you're asking, this may not pass App Store review. Per the SFSafariViewController docs:

In accordance with App Store Review Guidelines, this view controller must be used to visibly present information to users; the controller may not be hidden or obscured by other views or layers. Additionally, an app may not use SFSafariViewController to track users without their knowledge and consent.

Jake Stoeffler
  • 2,662
  • 24
  • 27