7

How to use SFSafariViewController in SwiftUI

 1 year ago
source link: https://sarunw.com/posts/sfsafariviewcontroller-in-swiftui/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Currently (iOS 16), there is no native way to present SFSafariViewController in SwiftUI.

In this article, I will teach you what I think is the proper way to present SFSafariViewController in a SwiftUI app.

When we want to use a UIViewController in a SwiftUI app, we have two options.

For SFSafariViewController, I prefer the second method. Let's see why I preferred that.

What is SFSafariViewController

SFSafariViewController is a standalone view controller. That means it can, and should operate without our intervention.

It comes pre-equipped with a navigation bar and controls that are necessary for browsing.

Here is an example where we present SFSafariViewController in UIKit. It presents as a pushed animation even though the parent view controller doesn't have one.

SFSafariViewController

SFSafariViewController

Let's see what happens when we try to wrap this in a UIViewControllerRepresentable.

You can easily support sarunw.com by checking out this sponsor.

Translate your app In 1 click:

Sponsor sarunw.com and reach thousands of iOS developers.

Create UIViewControllerRepresentable of SFSafariViewController

We can easily create UIViewControllerRepresentable of SFSafariViewController like this.

import SwiftUI
import SafariServices

struct SafariWebView: UIViewControllerRepresentable {
let url: URL

func makeUIViewController(context: Context) -> SFSafariViewController {
return SFSafariViewController(url: url)
}

func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context) {

}
}

Let's see why I don't recommend this approach.

SFSafariViewController with NavigationLink

In this example, I use a NavigationLink to push a SafariWebView into a navigation stack.

struct ContentView: View {
var body: some View {
NavigationStack {
NavigationLink("Push") {
SafariWebView(url: URL(string: "https://sarunw.com")!)
.ignoresSafeArea()
}
}
}
}

The result is a disaster.

Since SFSafariViewController come pre-equipped with a navigation bar, using NavigationLink create a double navigation bar.

So, this isn't a way to go.

Double navigation bar.

Double navigation bar.

SFSafariViewController with fullScreenCover

We can choose to present SFSafariViewController using a full-screen cover presentation instead.

Presenting it this way won't cause a double navigation bar.

struct ContentView: View {
@State private var isPresentWebView = false

var body: some View {
Button("Present as full screen cover") {
isPresentWebView = true
}
.fullScreenCover(isPresented: $isPresentWebView) {
SafariWebView(url: URL(string: "https://sarunw.com")!)
.ignoresSafeArea()
}
}
}

If you want to use UIViewControllerRepresentable, I think presenting it using .fullScreenCover is a way to go.

SFSafariViewController is present without a problem a the full-screen cover.

SFSafariViewController is present without a problem a the full-screen cover.

You need .ignoresSafeArea(). Otherwise, SFSafariViewController will present inside the safe areas.

SFSafariViewController without .ignoresSafeArea()

SFSafariViewController without .ignoresSafeArea()

Caveat

Present SFSafariViewController as a full-screen cover might look OK, but if you try to rotate your app, it still has some UI glitches.

You will see a black screen when rotating SFSafariViewController.

You might get away with this approach if your app doesn't support rotation.

Small UI glitches when rotated.

Small UI glitches when rotated.

You can easily support sarunw.com by checking out this sponsor.

Translate your app In 1 click:

Sponsor sarunw.com and reach thousands of iOS developers.

Use SFSafariViewController without wrapping

I think the easiest way to use SFSafariViewController is to use it in a UIKit context.

In this case, I grab the root view controller and present SFSafariViewController from that root view controller.

struct ContentView: View {
var body: some View {
Button("Present SFSafariViewController") {
// 1
let vc = SFSafariViewController(url: URL(string: "https://sarunw.com")!)

// 2
UIApplication.shared.firstKeyWindow?.rootViewController?.present(vc, animated: true)
}
}
}

extension UIApplication {
// 3
var firstKeyWindow: UIWindow? {
return UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.filter { $0.activationState == .foregroundActive }
.first?.keyWindow
}
}

1 We create SFSafariViewController directly without wrapping.
2 Then, present it from the root view controller.
3 There are many ways to grab a root view controller in a SwiftUI app. I choose a quick and dirty one for this example.

You will get the same behavior as you use in UIKit.

Using SFSafariViewController in a UIKit context.

Using SFSafariViewController in a UIKit context.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK