45

I'm presenting a SFSafariViewController by calling presentViewController:animated:completion: on a UIViewController instance.

The result is that it gets pushed on (slides in from the right), as if I called pushViewController:animated: on a UINavigationController instance. I've verified that this is all happening on the main queue. And the presenting view controller is not a modal itself (which shouldn't matter anyways, but just in case, we can rule that out).

If I substitute the SFSafariViewController with a UIViewController, it works as expected, it presents modally.

weakSelf.oAuthViewController = [[SFSafariViewController alloc] initWithURL:url];
[viewController presentViewController:weakSelf.oAuthViewController animated:YES completion:nil];

Any idea why or how to work around this?

abc123
  • 8,043
  • 7
  • 49
  • 80

4 Answers4

67

Here's a simple way to obtain a vertical modal presentation of a SFSafariViewController:

let safari = SFSafariViewController(URL: url)
safari.modalPresentationStyle = .overFullScreen
presentViewController(safari, animated: true, completion: nil)
JaredH
  • 2,338
  • 1
  • 30
  • 40
jamesk
  • 3,807
  • 21
  • 38
  • While it works better when navigating back, this is not modal. – Ivan Ičin Aug 20 '16 at 23:42
  • 5
    I found that `.overCurrentContext` works better since it also prevents a bug on iPhone X when status bar is hidden (transparent status bar area). – tadija Aug 06 '18 at 20:41
26

I just had the same issue. Also, the done button work even if you don't set up the delegate. Not sure why it happens. However, I found a workaround: wrapping the safari controller in a navigation controller and hiding the navigation bar.

func openURL(url:NSURL) {

    if #available(iOS 9.0, *) {
        let safariController = SFSafariViewController(url: url)
        safariController.delegate = self
        let navigationController = UINavigationController(rootViewController: safariController)
        navigationController.setNavigationBarHidden(true, animated: false)
        self.present(navigationController, animated: true, completion: nil)
    } else {
        UIApplication.sharedApplication().openURL(url)
    }
}
Chris Slowik
  • 2,859
  • 1
  • 14
  • 27
iGerms
  • 289
  • 2
  • 5
  • 1
    This is the only method that works if you're using a custom transitioning delegate. If presenting the `SFSafariViewController` directly, then the transitionContext's from view will always be nil, even with other `modalPresentationStyle`s. – John Scalo Apr 18 '19 at 20:27
14

To use the default modal transition style, you can simply set the transitioning delegate equal to self.

let svc = SFSafariViewController(url: url)
svc.transitioningDelegate = self //use default modal presentation instead of push
present(svc, animated: true, completion: nil)

You'll need to adopt the UIViewControllerTransitioningDelegate protocol in your view controller, but there are no required functions to implement.

This was mentioned in Session 225 at WWDC, What's New in Safari View Controller.

Jordan H
  • 52,571
  • 37
  • 201
  • 351
8

Objective-C version of iGerms answer:

-(void)openURL:(NSURL *)url {
   SFSafariViewController *safariController = [[SFSafariViewController alloc]initWithURL:url];
   safariController.delegate = self;
   UINavigationController *navigationController = [[UINavigationController alloc]initWithRootViewController:safariController];
   [navigationController setNavigationBarHidden:YES animated:NO];
   [self presentViewController:navigationController animated:YES completion:nil];
}
Mark Bourke
  • 9,806
  • 7
  • 26
  • 30