UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Conflict using List/ScrollView with SFSafariViewController fullscreen sheet

Forums > SwiftUI

I am trying to make a list of items each item should open a sheet with SFSafariViewController in full screen. To show SFSafariViewController in full screen, I used the code available in this link: https://dev.to/uchcode/web-sheet-with-sfsafariviewcontroller-4nlc. The controller works very fine. However, when I put the items inside a List or ScrollView, it does not show the sheet. When I remove List/ScrollView it works fine.

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)
    }
}

2      

I was doing it wrong, here is the right way in case somone is interested.

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)
    }
}

2      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.