BetterSafariView
A better way to present a SFSafariViewController or start a ASWebAuthenticationSession in SwiftUI.
Motivation
SwiftUI is a strong, intuitive way to build user interfaces, but was released with some part of existing elements missing. One example of those missing elements is the SFSafariViewController
.
Fortunately, Apple provides a way to wrap UIKit elements into SwiftUI views. A common approach to place the SFSafariViewController
inside SwiftUI is to create a simple view representing a SFSafariViewController
, then present it with a sheet(isPresented:onDismiss:content:)
modifier or a NavigationLink
button (See ContentView.swift
in the demo project).
However, there’s a problem in this approach: it can’t present the SFSafariViewController
with its default presentation style — a push transition covers full screen. A sheet modifier can present the view only in a modal sheet, and a navigation link shows the two navigation bars at the top so we have to deal with them. This comes down to the conclusion that there’s no option to present it the right way except for using present(_:animated:completion:)
method of a UIViewController
instance, but it is prohibited and not a good design to access the UIHostingController
directly from the SwiftUI view.
BetterSafariView
clearly achieves this goal by hosting a simple UIViewController
to present a SFSafariViewController
as a view’s background. In this way, a ASWebAuthenticationSession
is also able to be started without any issue in SwiftUI.
Usage
You can use it easily with the following modifiers in a similar way to presenting a sheet.
SafariView
Modifiers
.safariView(isPresented:onDismiss:content)
.safariView(item:onDismiss:content)
Example
import SwiftUI
import BetterSafariView
struct ContentView: View {
@State private var presentingSafariView = false
var body: some View {
Button(action: {
self.presentingSafariView = true
}) {
Text("Present SafariView")
}
.safariView(isPresented: $presentingSafariView) {
SafariView(
url: URL(string: "https://github.com/")!,
configuration: SafariView.Configuration(
entersReaderIfAvailable: false,
barCollapsingEnabled: true
)
)
.preferredBarAccentColor(.clear)
.preferredControlAccentColor(.accentColor)
.dismissButtonStyle(.done)
}
}
}
WebAuthenticationSession
Modifiers
.webAuthenticationSession(isPresented:content)
.webAuthenticationSession(item:content)
Example
import SwiftUI
import BetterSafariView
struct ContentView: View {
@State private var startingWebAuthenticationSession = false
var body: some View {
Button(action: {
self.startingWebAuthenticationSession = true
}) {
Text("Start WebAuthenticationSession")
}
.webAuthenticationSession(isPresented: $startingWebAuthenticationSession) {
WebAuthenticationSession(
url: URL(string: "https://github.com/login/oauth/authorize")!,
callbackURLScheme: "github"
) { callbackURL, error in
print(callbackURL, error)
}
.prefersEphemeralWebBrowserSession(false)
}
}
}
Known Issues
- In
.webAuthenticationSession(item:content:)
modifier, the functionality that replaces a session on theitem
's identity change is not implemented, as there is no non-hacky way to be notified when the session's dismissal animation is completed.
Installation
Swift Package Manager
Add the following line to the dependencies
in your Package.swift
file:
.package(url: "https://github.com/stleamist/BetterSafariView.git", .upToNextMajor(from: "2.0.0"))
Next, add BetterSafariView
as a dependency for your targets:
.target(name: "MyTarget", dependencies: ["BetterSafariView"])
Your completed description may look like this:
// swift-tools-version:5.1
import PackageDescription
let package = Package(
name: "MyPackage",
dependencies: [
.package(url: "https://github.com/stleamist/BetterSafariView.git", .upToNextMajor(from: "2.0.0"))
],
targets: [
.target(name: "MyTarget", dependencies: ["BetterSafariView"])
]
)
Xcode
Select File > Swift Packages > Add Package Dependency, then enter the following URL:
https://github.com/stleamist/BetterSafariView.git
For more details, see Adding Package Dependencies to Your App.
Demo
You can compare the behavior of BetterSafariView with the other ways above in the demo project. Check out the demo app by opening BetterSafariView.xcworkspace.