12

I was able to get my app to automatically load a url via the SFSafariViewController per this post, and it works great, the only drawback is the navbar.

The SFSafariViewController navbar is kind of useless when being used this way, as the url is read-only, and the 'done' link doesn't do anything but reload the page. As such, I would like to hide the navbar altogether.

Per the comments attached to accepted answer, it was suggested to set my root view controller to SFSafariViewController which I can't get working. The setup is simple as there is a single view controller with the code included in the aforementioned post.

How can I hide the navbar but still maintain the benefits of the SFSafariViewController? Or if I can't hide the navbar, at least hide the 'done' link?

Code snippet:

import UIKit
import SafariServices

class ViewController: UIViewController
{
    private var urlString:String = "https://example.com"

    override func viewDidLoad()
    {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func viewDidAppear(animated: Bool)
    {

        super.viewDidAppear(animated)

        let svc = SFSafariViewController(URL: NSURL(string: self.urlString)!)

        self.presentViewController(svc, animated: true, completion: nil)

        self.navigationItem.rightBarButtonItem = nil
    }

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

----- Works. Navbar is "hidden" -----

import UIKit
import SafariServices

class ViewController: UIViewController
{
    private var urlString:String = "https://example.com"

    override func viewDidLoad()
    {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        // This will remove the status (battery, time, etc) bar
        UIApplication.sharedApplication().statusBarHidden = true
    }

    override func viewDidAppear(animated: Bool) {

        super.viewDidAppear(animated)

        let svc = SFSafariViewController(URL: NSURL(string: self.urlString)!)

        // Kind of a hack, in that we really aren't removing the navbar
        //  Rather we are adjusting the starting point of the vpc object so it appears as the navbar is hidden
        self.presentViewController(svc, animated: true) {

            var frame = svc.view.frame
            let OffsetY: CGFloat = 42

            frame.origin = CGPoint(x: frame.origin.x, y: frame.origin.y - OffsetY)
            frame.size = CGSize(width: frame.size.width, height: frame.size.height + OffsetY)
            svc.view.frame = frame
        }
    }

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

    // For this to work be sure to set the following setting to OFF, in info.plist
    //  'View controller-based status bar appearance'
    override func prefersStatusBarHidden() -> Bool {
        return true
    }
}
Community
  • 1
  • 1
Mike Purcell
  • 19,847
  • 10
  • 52
  • 89
  • Can you subclass `SFSafariViewController` and hide the nav bar on `viewWillAppear:`? – JAL Nov 19 '15 at 19:54
  • 1
    I can try... I'm hella newb at ios dev so any code snippets would def help. – Mike Purcell Nov 19 '15 at 20:06
  • You can try something like: `self.navigationItem.rightBarButtonItem = nil` – Craig Otis Nov 19 '15 at 20:34
  • @CraigOtis: Doesn't appear to work, as the Done link is still rendering. I'd prefer to remove the entire nav bar. However, I wonder if in the context of SFSafariViewController, navbar refers to the bottom navigation menu. – Mike Purcell Nov 19 '15 at 20:53
  • It shouldn't - the bottom menu is the `toolbar`, I think a better approach might be to switch to `UIWebView` if you're needing a fullscreen solution. – Craig Otis Nov 19 '15 at 21:44
  • This is a really bad idea. The Nav bar may be "useless"... which Apple probably agrees with which is why they shrink it as the user scrolls down to read the content. Also whatever hack you pull off could easily break in a future version of iOS. – bpapa Jul 08 '16 at 19:21
  • Changing your SafariViewController, to hide areas could get your app rejected. Look at the Apple documentation for SafariViewController. Can't you use a WKWebView instead? – Morten Gustafsson Jul 05 '18 at 14:12
  • this post is almost 10 years old, but at this point WKWebView is probably a good way to go right now. – Dan Rosenstark Apr 25 '23 at 18:08

4 Answers4

8

Put this code in viewDidAppear:

let safariViewController = SFSafariViewController(URL: url)
presentViewController(safariViewController, animated: true) {
    var frame = safariViewController.view.frame
    let OffsetY: CGFloat  = 64
    frame.origin = CGPoint(x: frame.origin.x, y: frame.origin.y - OffsetY)
    frame.size = CGSize(width: frame.width, height: frame.height + OffsetY)
    safariViewController.view.frame = frame
}

To hide the status bar, set View controller-based status bar appearance to YES in your info.plist file and insert this in your view controller.

override func prefersStatusBarHidden() -> Bool {
    return true
}

Warning: I will suggest you against using SFSafariViewController for full screen view because reloading is not possible(since reload button is in UINavigationBar). In case request fails, it will render the application useless. Rather go for full screen WKWebView with custom toolbar instead.

Update: To avoid hiding the reload button, just add a view/imageView over the done button in your SFSafariViewController and render button invisible or at least untappable.

presentViewController(svc, animated: true) {
    let width: CGFloat = 66
    let x: CGFloat = self.view.frame.width - width

    // It can be any overlay. May be your logo image here inside an imageView.
    let overlay = UIView(frame: CGRect(x: x, y: 20, width: width, height: 44))
    overlay.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.5)
    svc.view.addSubview(overlay)
}

The problem with this approach is only that overlay stays on screen, but if you can find a nice image for it, you will be fine.

Borys Verebskyi
  • 4,160
  • 6
  • 28
  • 42
Sahil Kapoor
  • 11,183
  • 13
  • 64
  • 87
  • Ic, so the suggestion is to move the svc object up 64 pixels, thereby 'hiding' the navbar. It works. The only issue left is the `prefersStatusBarHidden` override doesn't seem to be working, as the status bar is still rendering. – Mike Purcell Nov 19 '15 at 21:00
  • Got the `prefersStatusBarHidden` to work. Thanks. But doesn't the SFSafariViewController offer more advantages such as cookie handling and better javascript rendering (via nitro)? The cookie handling is huge for us as we can now allow FB shares via mobile. – Mike Purcell Nov 19 '15 at 21:13
  • 1
    Yes, it definitely does. May be apple will give more choices in SafariServices in future. – Sahil Kapoor Nov 19 '15 at 21:22
  • I upvoted your answer b/c it solves the OP, however I am going to hold off on accepting in case someone has a better solution. Thanks again for your help. – Mike Purcell Nov 19 '15 at 21:22
  • I accepted your answer as there were no other responses, and what you posted did work as expected. Thanks again for the help. – Mike Purcell Nov 24 '15 at 20:49
  • 15
    What a terrible hack – Magoo Jan 16 '17 at 15:49
  • will Apple allow this? – swalkner Jul 19 '17 at 10:59
  • @swalkner Yes, I got an app approved. But you never know :) – Sahil Kapoor Aug 28 '17 at 16:13
  • @Magoo I am not proud of it but that's what question demands :/ – Sahil Kapoor Aug 28 '17 at 16:15
  • 3
    "To avoid hiding the reload button, just add a view/imageView over the done button in your SFSafariViewController and render button invisible or at least untappable." - I wouldn't recommend that due to the Apple documentation on SafariViewController. From Apple documentation: Important 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. – Morten Gustafsson Jul 05 '18 at 14:04
4

Customising SafariViewController may not be a good idea.

The Apple Guidelines clearly says

SafariViewController 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 SafariViewController to track users without their knowledge and consent.

Refer:-https://developer.apple.com/app-store/review/guidelines/

arango_86
  • 4,236
  • 4
  • 40
  • 46
2
import Foundation
import UIKit
import SafariServices

class MySafariFullScreenViewController: UIViewController  {

    override func viewDidLoad() {
        super.viewDidLoad()
        //WONT WORK read only you need to override it in this VC or in SFSafVC using extension - see bottom of this code
        //self.prefersStatusBarHidden = true

    }

    override func viewDidAppear(_ animated: Bool){
        let urlString = "https://......"
        //if a log screen - i think SFSafariViewController can handle this
        //let urlString = "https://<domain>login?redirect=https:<homescreen>"


        if let url: URL = URL(string: urlString) {
            let safariViewController = SFSafariViewController(url: url)
            present(safariViewController, animated: true) {

                var frame = safariViewController.view.frame
                //if status bar not hidden
                l//et OffsetY: CGFloat  = 64
                //if status bar hidden
                let OffsetY: CGFloat  = 44

                frame.origin = CGPoint(x: frame.origin.x, y: frame.origin.y - OffsetY)
                frame.size = CGSize(width: frame.width, height: frame.height + OffsetY)
                safariViewController.view.frame = frame


            }
        }else{
            //url error
        }



    }
    //this is for this vc - but for SFSafariVC you need override using extension
    override var prefersStatusBarHidden: Bool{
        get{
            return true
        }
    }

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

extension SFSafariViewController{
     override open var prefersStatusBarHidden: Bool{
        get{
            return true
        }
    }
}
brian.clear
  • 5,277
  • 2
  • 41
  • 62
1

Just present it using presentViewController:animated:completion:

https://stackoverflow.com/a/40460594/842655

AlKozin
  • 904
  • 8
  • 25