SwiftUI UIViewControllerRepresentable in package is always internal

Hi,

I've this code, what ever I try it will be "internal" ('SafariView' initializer is inaccessible due to 'internal' protection level)

@available(iOS 14.0, *)
public struct SafariView: UIViewControllerRepresentable {
    public typealias UIViewControllerType = SFSafariViewController

    @Binding public var urlString: String

    public func makeUIViewController(
        context: UIViewControllerRepresentableContext<SafariView>
    ) -> SFSafariViewController {
        guard let url = URL(string: urlString) else {
            fatalError("Invalid urlString: \(urlString)")
        }

        let safariViewController = SFSafariViewController(url: url)
        safariViewController.preferredControlTintColor = UIColor(Color.accentColor)
        safariViewController.dismissButtonStyle = .close

        return safariViewController
    }

    public func updateUIViewController(
        _ safariViewController: SFSafariViewController,
        context: UIViewControllerRepresentableContext<SafariView>
        ) {
        return
    }
}

if I try to add a initializer (public init() { }), this should not work.
I get this error: Return from initializer without initializing all stored properties

if I try to add with parameters: public init(s: String) { urlString = s }
then I get this error 'self' used before all stored properties are initialized

How to get this working?

Try
ˋˋˋ
public init(urlString: Binding) {
self._urlString = urlString
}
ˋˋ

1 Like

Hey @wdg, two things:

Firstly, it doesn't seem like you need a @Binding like you have now — bindings are really meant for if you need to give another view (or simply part of your code) the ability to modify, not just read, a piece of view state.

Since reading is all you're doing here, you can change

@Binding public var urlString: String

to regular @State:

@State public let urlString: String

Secondly (as you've noticed) if you want your initializer to be publicly available you'll need to define one explicitly;

public init(urlString: String) {
    self.urlString = urlString
}

should do the trick.

1 Like

Thanks!

    public init(urlString: Binding<String>) {
        _urlString = urlString
    }

did the trick!

Hello @mattcurtis,

Thanks, I've tried this option, but it wasn't usable in the way how i present the screen.

I present this screen using:

.popover(isPresented: $showSafari, content: {
            SafariView(urlString: $urlString)
        }
)

So I really need the binding, otherwise I cannot load a different URL (except if I make a x amount of .popover which isn't really smart.

Maybe I use the wrong implementation, it's my first SwiftUI app.

Without knowing more about how you've implemented the popover's presentation, there doesn't seem to be any reason you should need a Binding here specifically. You should be able to use regular State. (There's nothing in your SafariView implementation right now that takes advantage of a Binding anyway — it won't react to urlString changing, or change it itself.)

It sounds like you may be wanting to change the URL loaded by an existing and already presented SFSafariViewController. If that's the case you can't do that with SFSafariViewController, it's designed so that you can only decide what's initially loaded, not change it later. If you want to load a different URL you'll need to essentially destroy the existing SFSafariViewController and create a new one.

If that is what you want though, the answer may be to tell SwiftUI "destroy and recreate this view controller if urlString changes" by using something like:

SafariView(urlString: urlString)
   .id(urlString)

I'm assuming that .popover is compatible with this kind of view change, it may not be, in which case you may need to adjust how you're presenting your .popover().

1 Like