Sometimes the answer is to just not use SwiftUI!
This is so well supported in UIKit that I just make an easy bridge to UIKit so I can call the SafariController in a single line from SwiftUI like so:
HSHosting.openSafari(url:URL(string: "https://hobbyistsoftware.com")!)
I just replace UIHostingController at the top level of my app with HSHostingController
(note - this class also allows you to control the presentation style of modals)
//HSHostingController.swift
import Foundation
import SwiftUI
import SafariServices
class HSHosting {
static var controller:UIViewController?
static var nextModalPresentationStyle:UIModalPresentationStyle?
static func openSafari(url:URL,tint:UIColor? = nil) {
guard let controller = controller else {
preconditionFailure("No controller present. Did you remember to use HSHostingController instead of UIHostingController in your SceneDelegate?")
}
let vc = SFSafariViewController(url: url)
vc.preferredBarTintColor = tint
//vc.delegate = self
controller.present(vc, animated: true)
}
}
class HSHostingController<Content> : UIHostingController<Content> where Content : View {
override init(rootView: Content) {
super.init(rootView: rootView)
HSHosting.controller = self
}
@objc required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
if let nextStyle = HSHosting.nextModalPresentationStyle {
viewControllerToPresent.modalPresentationStyle = nextStyle
HSHosting.nextModalPresentationStyle = nil
}
super.present(viewControllerToPresent, animated: flag, completion: completion)
}
}
use HSHostingController instead of UIHostingController in your scene delegate
like so:
// Use a HSHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
//This is the only change from the standard boilerplate
window.rootViewController = HSHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
then when you want to open SFSafariViewController, just call:
HSHosting.openSafari(url:URL(string: "https://hobbyistsoftware.com")!)
for example
Button(action: {
HSHosting.openSafari(url:URL(string: "https://hobbyistsoftware.com")!)
}) {
Text("Open Web")
}
update: see this gist for extended solution with additional capabilities