iosbuttonswiftuisfsafariviewcontroller

SFSafariViewController Done Button Tappable Area Too Small


There is another post on SO regarding this issue but it's from a while back and only deals with UIKit, not SwiftUI. I have SFSafariViewController working perfectly, and am using it via a programmatic NavigationLink.

The issue is that the tappable area of the Done button is too small. I can only tap on the "ne" of the word "Done" and only at a certain angle. It's very strange and I haven't been able to solve the issue. The rest of the buttons all work perfectly.

If I change to using it modally via a sheet, the Done button works perfectly, but I need to use it via a NavigationLink.

I just need to solve the problem with the tappable area of the Done button.

Thanks!

Here's my SFSafariViewController:

import SwiftUI
import SafariServices

struct SafariView: UIViewControllerRepresentable {
    @Environment(\.dismiss) var dismiss

    let url: URL

    func makeUIViewController(context: Context) -> SFSafariViewController {
        let vc = SFSafariViewController(url: url)
        vc.preferredControlTintColor = .tintColor
        vc.delegate = context.coordinator
        return vc
    }

    func updateUIViewController(_ vc: SFSafariViewController, context: Context) {}

    class Coordinator: NSObject, SFSafariViewControllerDelegate {
        var dismissAction: DismissAction?

        func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
            dismissAction?()
        }
    }

    func makeCoordinator() -> Coordinator {
        let coordinator = Coordinator()
        coordinator.dismissAction = dismiss
        return coordinator
    }
}

Here's my view:

import SwiftUI

struct HomeView: View {

    @State private var isBtnActive: Bool = false

    var body: some View {

        NavigationLink(
            destination: SafariView(url: URL(string: "https://www.example.com")!)
                .navigationBarHidden(true),
            isActive: $isBtnActive,
            label: {
                EmptyView()
            }
        )
        .accessibilityHidden(true)

        VStack(spacing: 0) {
            Spacer()
            Button("Show Website") {
                isBtnActive = true
            }
            Spacer()
        }
    }
}

Solution

  • I have found a solution in SwiftUI regarding this topic.

    This code embeds a SafariViewController into a NavigationView and the "Done" button is working. The only thing that doesn't work is the swipe back gesture.

    import SwiftUI
    import SafariServices
    
    struct SafariWebView: UIViewControllerRepresentable {
      let url: URL
      @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
      
      func makeCoordinator() -> Coordinator {
        Coordinator(self)
      }
      
      func makeUIViewController(context: Context) -> UINavigationController {
        let safariVC = SFSafariViewController(url: url)
        safariVC.delegate = context.coordinator
        
        let navController = UINavigationController(rootViewController: safariVC)
        navController.setNavigationBarHidden(true, animated: false)
        return navController
      }
      
      func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {}
      
      class Coordinator: NSObject, SFSafariViewControllerDelegate {
        var parent: SafariWebView
        
        init(_ parent: SafariWebView) {
          self.parent = parent
        }
        
        func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
          parent.presentationMode.wrappedValue.dismiss()
        }
      }
    }
    
    struct AppLink: View {
      var url: String
      
      var body: some View {
        SafariWebView(url: URL(string: url)!)
          .ignoresSafeArea()
          .navigationBarBackButtonHidden(true)
      }
    }
    
    #Preview {
      AppLink(url: "https://www.example.com")
    }
    

    And then you can use it with a NavigationLink:

    NavigationStack {
      NavigationLink("Link App", destination: AppLink(url: "https://www.example.com"))
    }
    

    Image:
    SafariViewController in NavigationView