How to open a linked pdf with turbolinks-ios

587 views Asked by At

I'm wondering how to open a linked pdf file with the turbolinks-ios framework in iOS.

Currently, I'm experiencing the issue that when a turbolinks page links to a pdf or other file, then the link will open in safari rather than the embedded view.

Background

The turbolinks-5 library together with the turbolinks-ios framework provide a way to connect a web application to the native navigation controllers of the corresponding mobile app.

screenshot

The screenshot is taken from the turbolinks README.

Desired behavior

When clicking a link that refers to a pdf, a seaparate view controller should be pushed to the current navigation controller, such that the user can read the pdf and easily navigate back to the document index.

Observed behavior

The linked pdf is opened in safari rather than within the app. Unfortunately, safari asks for authentication, again. Furthermore, the user has to leave the application.

1

There are 1 answers

0
fiedl On

Intercept the click of the pdf link

For a link to a pdf file, the didProposeVisitToURL mechanism is not triggered for the session delegate. Thus, one can't decide from there how to handle the linked pdf.

Instead, one could intercept clicking the link by becoming turbolinks' web view's navigation delegate as shown in the README:

extension NavigationController: SessionDelegate {
  // ...

  func sessionDidLoadWebView(session: Session) {
    session.webView.navigationDelegate = self
  }
}

extension NavigationController: WKNavigationDelegate {
  func webView(webView: WKWebView, 
      decidePolicyForNavigationAction navigationAction: WKNavigationAction, 
      decisionHandler: (WKNavigationActionPolicy) -> ()) {

    // This method is called whenever the webView within the
    // visitableView attempts a navigation action. By default, the
    // navigation has to be cancelled, since when clicking a
    // turbolinks link, the content is shown in a **new**
    // visitableView.
    //
    // But there are exceptions: When clicking on a PDF, which
    // is not handled by turbolinks, we have to handle showing
    // the pdf manually.
    //
    // We can't just allow the navigation since this would not
    // create a new visitable controller, i.e. there would be
    // no back button to the documents index. Therefore, we have
    // to create a new view controller manually.

    let url = navigationAction.request.URL!

    if url.pathExtension == "pdf" {
      presentPdfViewController(url)
    }

    decisionHandler(WKNavigationActionPolicy.Cancel)    
  }
}

Present the pdf view controller

Similarly to presenting the visitable view as shown in the turbolinks-ios demo application, present the pdf view controller:

extension NavigationController {
  func presentPdfViewController(url: NSURL) {
    let pdfViewController = PdfViewController(URL: url)
    pushViewController(pdfViewController, animated: true)
  }
}

Or, if you'd like to show other file types as well, call it fileViewController rather than pdfViewController.

PdfViewController

The new view controller inherits from turbolinks' VisitableViewController to make use of the initialization by url.

class PdfViewController: FileViewController {
}

class FileViewController: Turbolinks.VisitableViewController {
  lazy var fileView: WKWebView = {
    return WKWebView(frame: CGRectZero)
  }()

  lazy var filename: String? = {
    return self.visitableURL?.pathComponents?.last
  }()

  override func viewDidLoad() {
    view.addSubview(fileView)
    fileView.bindFrameToSuperviewBounds()  // https://stackoverflow.com/a/32824659/2066546
    self.title = filename  // https://stackoverflow.com/a/39022302/2066546
    fileView.loadRequest(NSURLRequest(URL: visitableURL))
  }
}

To get the web view to the correct size, I used bindFrameToSuperviewBounds as shown in this stackoverflow answer, but I'm sure there are other methods.

Optional: Sharing cookies

If loading the pdf needs authentication, it's convenient to share the cookies with the turbolinks-ios webview as described in the README.

For example, create a webViewConfiguration which can be passed to the pdfViewController:

extension NavigationController {
  let webViewProcessPool = WKProcessPool()

  lazy var webViewConfiguration: WKWebViewConfiguration = {
    let configuration = WKWebViewConfiguration()
    configuration.processPool = self.webViewProcessPool
    // ...
    return configuration
  }()

  lazy var session: Session = {
    let session = Session(webViewConfiguration: self.webViewConfiguration)
    session.delegate = self
    return session
  }()
}

The same webViewConfiguration needs to be passed to the session (shown above) as well as to the new pdf view controller.

extension NavigationController {
  func presentPdfViewController(url: NSURL) {
    let pdfViewController = PdfViewController(URL: url)
    pdfViewController.webViewConfiguration = self.webViewConfiguration
    pushViewController(pdfViewController, animated: true)
  }
}

class FileViewController: Turbolinks.VisitableViewController {
  var webViewConfiguration: WKWebViewConfiguration

  lazy var fileView: WKWebView = {
    return WKWebView(frame: CGRectZero, configuration: self.webViewConfiguration)
  }()

  // ...
}

Demo

Screenshot