Saltar al contenido

¿Cómo uso SFSafariViewController con SwiftUI?

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.

ingrese la descripción de la imagen aquí

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:

ingrese la descripción de la imagen aquí

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

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *