Solución:
Complementario a la publicación de Matteo Pacini, .presentation(Modal())
fue eliminado por el lanzamiento de iOS 13. Este código debería funcionar (probado en Xcode 11.3, iOS 13.0 – 13.3):
import SwiftUI
import SafariServices
struct ContentView: View {
// whether or not to show the Safari ViewController
@State var showSafari = false
// initial URL string
@State var urlString = "https://duckduckgo.com"
var body: some View {
Button(action: {
// update the URL if you'd like to
self.urlString = "https://duckduckgo.com"
// tell the app that we want to show the Safari VC
self.showSafari = true
}) {
Text("Present Safari")
}
// summon the Safari sheet
.sheet(isPresented: $showSafari) {
SafariView(url:URL(string: self.urlString)!)
}
}
}
struct SafariView: UIViewControllerRepresentable {
let url: URL
func makeUIViewController(context: UIViewControllerRepresentableContext<SafariView>) -> SFSafariViewController {
return SFSafariViewController(url: url)
}
func updateUIViewController(_ uiViewController: SFSafariViewController, context: UIViewControllerRepresentableContext<SafariView>) {
}
}
SFSafariViewController
es un UIKit
componente, por lo tanto, debe hacerlo UIViewControllerRepresentable
.
Vea el video Integración de SwiftUI WWDC 19 para obtener más detalles sobre cómo hacer un puente UIKit
componentes a SwiftUI
.
struct SafariView: UIViewControllerRepresentable {
let url: URL
func makeUIViewController(context: UIViewControllerRepresentableContext<SafariView>) -> SFSafariViewController {
return SFSafariViewController(url: url)
}
func updateUIViewController(_ uiViewController: SFSafariViewController,
context: UIViewControllerRepresentableContext<SafariView>) {
}
}
Una nota de advertencia: SFSafariViewController
está destinado a presentarse en la parte superior de otro controlador de vista, no en una pila de navegación.
También tiene una barra de navegación, lo que significa que si presiona el controlador de vista, verá dos barras de navegación.
Parece funcionar, aunque tiene problemas, si se presenta de forma modal.
struct ContentView : View {
let url = URL(string: "https://www.google.com")!
var body: some View {
EmptyView()
.presentation(Modal(SafariView(url:url)))
}
}
Se parece a esto:
Sugiero portar WKWebView
para SwiftUI
mediante el UIViewRepresentable
protocolo, y utilícelo en su lugar.
¡A veces la respuesta es simplemente no usar SwiftUI! Esto está tan bien soportado en UIKit que simplemente hago un puente fácil a UIKit para poder llamar al SafariController en una sola línea desde SwiftUI así:
HSHosting.openSafari(url:URL(string: "https://hobbyistsoftware.com")!)
Acabo de reemplazar UIHostingController en el nivel superior de mi aplicación con HSHostingController
(nota: esta clase también le permite controlar el estilo de presentación de los modales)
//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 en lugar de UIHostingController en su delegado de escena así:
// 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()
}
luego, cuando desee abrir SFSafariViewController, simplemente llame:
HSHosting.openSafari(url:URL(string: "https://hobbyistsoftware.com")!)
por ejemplo
Button(action: {
HSHosting.openSafari(url:URL(string: "https://hobbyistsoftware.com")!)
}) {
Text("Open Web")
}
actualización: consulte esta esencia para obtener una solución extendida con capacidades adicionales