使用 List/ScrollView 与 SFSafariViewController 全屏冲突 sheet

Conflict using List/ScrollView with SFSafariViewController fullscreen sheet

我正在尝试制作一个项目列表,每个项目都应该在全屏模式下用 SFSafariViewController 打开 sheet。为了全屏显示 SFSafariViewController,我使用了 link: https://dev.to/uchcode/web-sheet-with-sfsafariviewcontroller-4nlc 中可用的代码。控制器工作得很好。但是,当我将项目放入 List 或 ScrollView 时,它不显示 sheet。当我删除 List/ScrollView 时它工作正常。

这是代码。

import SwiftUI
import SafariServices

struct RootView: View, Hostable {
    @EnvironmentObject private var hostedObject: HostingObject<Self>
    let address: String
    let title: String
    
    func present() {
        let config = SFSafariViewController.Configuration()
        config.entersReaderIfAvailable = true
        
        let safari = SFSafariViewController(url: URL(string: address)!, configuration: config)
        hostedObject.viewController?.present(safari, animated: true)
    }
    
    var body: some View {
        Button(title) {
            self.present()
        }
    }
}

struct ContentView: View {
    
    @State private var articlesList = [
    ArticlesList(id: 0, title: "Apple", link: "http://apple.com", lang: "en"),
    ArticlesList(id: 1, title: "Yahoo", link: "http://yahoo.com", lang: "en"),
    ArticlesList(id: 2, title: "microsoft", link: "http://microsoft.com", lang: "en"),
    ArticlesList(id: 3, title: "Google", link: "http://google.com", lang: "en")
    ]
    
    var body: some View {
        
        NavigationView{
            //Here is the problem when I add RootView inside a List or ScrollView it does not show Safari.
            ScrollView(.vertical, showsIndicators: false) {
                VStack{
                    ForEach(articlesList) {article in
                        RootView(address: article.link, title: article.title).hosting()
                        Spacer(minLength: 10)
                    }
                }
            }
            .navigationBarTitle("Articles")
        }
    }
    
}

struct ArticlesList: Identifiable, Codable {
    let id: Int
    let title: String
    let link: String
    let lang: String
}


struct UIViewControllerView: UIViewControllerRepresentable {
    final class ViewController: UIViewController {
        var didAppear: (UIViewController) -> Void = { _ in }
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
            didAppear(self)
        }
    }
    
    var didAppear: (UIViewController) -> Void
    
    func makeUIViewController(context: Context) -> UIViewController {
        let viewController = ViewController()
        viewController.didAppear = didAppear
        return viewController
    }
    
    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
        //
    }
}

struct UIViewControllerViewModifier: ViewModifier {
    var didAppear: (UIViewController) -> Void
    var viewControllerView: some View {
        UIViewControllerView(didAppear:didAppear).frame(width:0,height:0)
    }
    func body(content: Content) -> some View {
        content.background(viewControllerView)
    }
}

extension View {
    func uiViewController(didAppear: @escaping (UIViewController) -> ()) -> some View {
        modifier(UIViewControllerViewModifier(didAppear:didAppear))
    }
}

class HostingObject<Content: View>: ObservableObject {
    @Published var viewController: UIViewController? = nil
}

struct HostingObjectView<Content: View>: View {
    var rootView: Content
    let hostedObject = HostingObject<Content>()
    func getHost(viewController: UIViewController) {
        hostedObject.viewController = viewController.parent
    }
    var body: some View {
        rootView
            .uiViewController(didAppear: getHost(viewController:))
            .environmentObject(hostedObject)
    }
}

protocol Hostable: View {
    associatedtype Content: View
    func hosting() -> Content
}

extension Hostable {
    func hosting() -> some View {
        HostingObjectView(rootView: self)
    }
}

您的代码在 iOS 14 模拟器中有效!

我包装了 SFSafariViewController,如果我用以下代码替换您的 RootView,它可以在我的 iOS 13 设备上运行。然而,它并不是真正的 full-screen,而是 sheet。

struct RootView: View {
    @State private var isPresenting = false
    let address: String
    let title: String
    
    var body: some View {
        Button(self.title) {
            self.isPresenting.toggle()
        }
        .sheet(isPresented: self.$isPresenting) {
            SafariView(address: URL(string: self.address)!)
        }
    }
}

struct SafariView: UIViewControllerRepresentable {
    let address: URL
    
    func makeUIViewController(context: Context) -> SFSafariViewController {
        let config = SFSafariViewController.Configuration()
        config.entersReaderIfAvailable = true

        let safari = SFSafariViewController(url: self.address, configuration: config)
        return safari
    }
    
    func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context) {
        //
    }
}

下面是工作代码..

import SwiftUI
import SafariServices

struct ContentView: View {
    var body: some View{
        TabView {
            HomeView()
                .tabItem {
                    VStack {
                        Image(systemName: "house")
                        Text("Home")
                    }
            }.tag(0)
            
            ArticlesView().hosting()
                .tabItem{
                    VStack{
                        Image(systemName: "quote.bubble")
                        Text("Articles")
                    }
            }.tag(1)
        }
    }
}

struct HomeView: View {
    var body: some View{
        Text("This is home")
    }
}


struct ShareView: View{
    var body: some View{
        Text("Here the share")
    }
}

struct ArticlesView: View, Hostable {
    @EnvironmentObject private var hostedObject: HostingObject<Self>
    @State private var showShare = false
    
    @State private var articlesList = [
        ArticlesList(id: 0, title: "Apple", link: "http://apple.com", lang: "en"),
        ArticlesList(id: 1, title: "Yahoo", link: "http://yahoo.com", lang: "en"),
        ArticlesList(id: 2, title: "microsoft", link: "http://microsoft.com", lang: "en"),
        ArticlesList(id: 3, title: "Google", link: "http://google.com", lang: "en")
    ]
    
    
    func present(address: String) {
        let config = SFSafariViewController.Configuration()
        config.entersReaderIfAvailable = true
        
        let safari = SFSafariViewController(url: URL(string: address)!, configuration: config)
        hostedObject.viewController?.present(safari, animated: true)
    }
    
    var body: some View {
        NavigationView{
            ScrollView(.vertical, showsIndicators: false) {
                VStack(spacing: 40){
                    ForEach(articlesList) {article in
                        Button(article.title) {
                            self.present(address: article.link)
                        }
                    }
                }
                .sheet(isPresented: $showShare){
                    ShareView()
                }
                .navigationBarTitle("Articles")
                .navigationBarItems(leading:
                    Button(action: {
                        self.showShare.toggle()
                    })
                    {
                        Image(systemName: "plus")
                    }
                )
            }
        }
    }
}


struct ArticlesList: Identifiable, Codable {
    let id: Int
    let title: String
    let link: String
    let lang: String
}



struct UIViewControllerView: UIViewControllerRepresentable {
    final class ViewController: UIViewController {
        var didAppear: (UIViewController) -> Void = { _ in }
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
            didAppear(self)
        }
    }
    
    var didAppear: (UIViewController) -> Void
    
    func makeUIViewController(context: Context) -> UIViewController {
        let viewController = ViewController()
        viewController.didAppear = didAppear
        return viewController
    }
    
    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
        //
    }
}

struct UIViewControllerViewModifier: ViewModifier {
    var didAppear: (UIViewController) -> Void
    var viewControllerView: some View {
        UIViewControllerView(didAppear:didAppear).frame(width:0,height:0)
    }
    func body(content: Content) -> some View {
        content.background(viewControllerView)
    }
}

extension View {
    func uiViewController(didAppear: @escaping (UIViewController) -> ()) -> some View {
        modifier(UIViewControllerViewModifier(didAppear:didAppear))
    }
}

class HostingObject<Content: View>: ObservableObject {
    @Published var viewController: UIViewController? = nil
}

struct HostingObjectView<Content: View>: View {
    var rootView: Content
    let hostedObject = HostingObject<Content>()
    func getHost(viewController: UIViewController) {
        hostedObject.viewController = viewController.parent
    }
    var body: some View {
        rootView
            .uiViewController(didAppear: getHost(viewController:))
            .environmentObject(hostedObject)
    }
}

protocol Hostable: View {
    associatedtype Content: View
    func hosting() -> Content
}

extension Hostable {
    func hosting() -> some View {
        HostingObjectView(rootView: self)
    }
}