iosswiftswiftuisfsafariviewcontroller

iOS SwiftUI SafariView Bug - Why is the first URL not loading properly?


I am building a SwiftUI application with a list of products, where each product has an associated URL. When the user clicks the “Info” button of a product, a SafariView should open to display the corresponding URL. However, I am encountering an issue where the SafariView fails to load the correct URL the first time a product is selected. After selecting a different product, the functionality starts working as expected for all subsequent selections.

My variables:

@StateObject private var dataProvider = DataProvider()
// For INFO-Button:
@State private var safariURL: URL? = nil
@State private var showSafariView: Bool = false
 ForEach(dataProvider.products) { product in
                            
                            VStack(alignment: .leading) {

// "Info" Button
                                    Button(action: {
                                        
                                        if let urlString = product.productUrl, let url = URL(string: urlString) {
                                            safariURL = url
                                            print("Safari URL set: \(url); \(String(describing: safariURL))")
                                            showSafariView = true
                                        } else {
                                            print("Err. 1: No valid URL available.")
                                            safariURL = nil
                                            showSafariView = false
                                        }
                                    }) {
                                        HStack {
                                            Text("INFO")
                                        }
                                        .frame(width: 120, height: 10)
                                        .padding()
                                    }
                                    .background(Color.blueGradient)
                                    .foregroundColor(.white)
                                    // SafariView Overlay
                                    .sheet(isPresented: $showSafariView) {
                                        // Check, whether the URL was set
                                        if let safariURL = safariURL {
                                            SafariView(url: safariURL)
                                        } else {
                                            Text("Err. 2: No valid URL to display.")
                                            Text("Product URL: \(String(describing: product.productUrl))")
                                            Text("safariURL: \(String(describing: safariURL))")
                                        }
                                    }
struct SafariView: View {
    var url: URL
    
    var body: some View {
        SafariViewController(url: url)
            .edgesIgnoringSafeArea(.all)
    }
}

struct SafariViewController: UIViewControllerRepresentable {
    var url: URL
    
    func makeUIViewController(context: Context) -> SFSafariViewController {
        let safariVC = SFSafariViewController(url: url)
        return safariVC
    }
    
    func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context) {
        // maybe I have to fix sth. here?
    }
}

It works perfectly in Preview (xCode 16.1). In the Simulator (and TestFlight on iPhone), however, the following happens:

Lets say I get 3 products listed:

  1. www.youtube.com
  2. no url
  3. www.google.com

I click on the first product (or any other product):

I click on the first/same product again (no matter how many times):

Now I click on another product, e.g., the third one:

If I now go back and click on the first product again:

If I click on a product without a URL (e.g., the second one):

So, this issue only happens with the very first product I select, regardless of which product it is. The same happens when I start with the 3rd product or the 2nd. It doesn’t seem to be related to the product or the URL itself; it just doesn’t seem to “get it” the first time.


I tried so many things, e.g. to force set the URL, I tried to put a delay in opening the SafariView,.... I asked chatGPT like 100 times...

I just want the safariView to open with the correct URL immediately. If you need further information or code related to my problem, just let me know.


Solution

  • This is a SwiftUI quirk. Because you're not using safariURL directly in the body of your view, SwiftUI doesn't re-evaluate the body when that variable changes. If you capture safariURL in the sheet closure, it will work as expected. Like this:

    .sheet(isPresented: $showSafariView) { [safariURL] in
      // your code
    }