一种在 SwiftUI 中呈现 SFSafariViewController 或启动 ASWebAuthenticationSession 的更好方式。
SwiftUI 是一种强大且直观的用户界面构建方式,但在发布时缺少一些现有元素。其中一个缺失的元素是 SFSafariViewController
。
幸运的是,Apple 提供了一种将 UIKit 元素包装到 SwiftUI 视图中的方法。将 SFSafariViewController
放入 SwiftUI 中的常用方法是创建一个代表 SFSafariViewController
的简单视图,然后使用 sheet(isPresented:onDismiss:content:)
修饰符或 NavigationLink
按钮来呈现它(请参阅演示项目中的 RootView.swift
)。
然而,这种方法存在一个问题:它无法以默认的呈现样式(全屏推送过渡)呈现 SFSafariViewController
。sheet 修饰符只能以模态 sheet 呈现视图,而 navigation link 会在顶部显示两个导航栏,因此我们必须处理它们。这归结为以下结论:除非使用 present(_:animated:completion:)
方法的 UIViewController
实例,否则无法以正确的方式呈现它,但是直接从 SwiftUI 视图访问 UIHostingController
是被禁止的,也不是一个好的设计。
BetterSafariView
通过托管一个简单的 UIViewController
来作为视图的背景呈现 SFSafariViewController
,从而清晰地实现了这个目标。 通过这种方式,ASWebAuthenticationSession
也能够在 SwiftUI 中无任何问题地启动。
使用以下修饰符,您可以像呈现 sheet 一样使用它。
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)
}
}
}
safariView(isPresented:onDismiss:content:)
/// Presents a Safari view when a given condition is true.
func safariView(
isPresented: Binding<Bool>,
onDismiss: (() -> Void)? = nil,
content: @escaping () -> SafariView
) -> some View
safariView(item:onDismiss:content:)
/// Presents a Safari view using the given item as a data source for the `SafariView` to present.
func safariView<Item: Identifiable>(
item: Binding<Item?>,
onDismiss: (() -> Void)? = nil,
content: @escaping (Item) -> SafariView
) -> some View
init(url:)
/// Creates a Safari view that loads the specified URL.
init(url: URL)
init(url:configuration:)
/// Creates and configures a Safari view that loads the specified URL.
init(url: URL, configuration: SafariView.Configuration)
preferredBarAccentColor(_:)
/// Sets the accent color for the background of the navigation bar and the toolbar.
func preferredBarAccentColor(_ color: Color?) -> SafariView
preferredControlAccentColor(_:)
/// Sets the accent color for the control buttons on the navigation bar and the toolbar.
func preferredControlAccentColor(_ color: Color?) -> SafariView
dismissButtonStyle(_:)
/// Sets the style of dismiss button to use in the navigation bar to close `SafariView`.
func dismissButtonStyle(_ style: SafariView.DismissButtonStyle) -> SafariView
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)
}
}
}
webAuthenticationSession(isPresented:content:)
/// Starts a web authentication session when a given condition is true.
func webAuthenticationSession(
isPresented: Binding<Bool>,
content: @escaping () -> WebAuthenticationSession
) -> some View
webAuthenticationSession(item:content:)
/// Starts a web authentication session using the given item as a data source for the `WebAuthenticationSession` to start.
func webAuthenticationSession<Item: Identifiable>(
item: Binding<Item?>,
content: @escaping (Item) -> WebAuthenticationSession
) -> some View
init(url:callbackURLScheme:completionHandler:)
/// Creates a web authentication session instance.
init(
url: URL,
callbackURLScheme: String?,
completionHandler: @escaping (URL?, Error?) -> Void
)
init(url:callbackURLScheme:onCompletion:)
/// Creates a web authentication session instance.
init(
url: URL,
callbackURLScheme: String?,
onCompletion: @escaping (Result<URL, Error>) -> Void
)
prefersEphemeralWebBrowserSession(_:)
/// Configures whether the session should ask the browser for a private authentication session.
func prefersEphemeralWebBrowserSession(_ prefersEphemeralWebBrowserSession: Bool) -> WebAuthenticationSession
.webAuthenticationSession(item:content:)
修饰符中,当 item
的 identity 更改时替换会话的功能未实现,因为在会话的 dismissal 动画完成时没有非 hacky 的方式来获得通知。将以下行添加到您的 Package.swift
文件中的 dependencies
中
.package(url: "https://github.com/stleamist/BetterSafariView.git", .upToNextMajor(from: "2.4.2"))
接下来,将 BetterSafariView
添加为您的目标的依赖项
.target(name: "MyTarget", dependencies: ["BetterSafariView"])
您完成的描述可能如下所示
// swift-tools-version:5.1
import PackageDescription
let package = Package(
name: "MyPackage",
dependencies: [
.package(url: "https://github.com/stleamist/BetterSafariView.git", .upToNextMajor(from: "2.4.2"))
],
targets: [
.target(name: "MyTarget", dependencies: ["BetterSafariView"])
]
)
选择 File > Swift Packages > Add Package Dependency,然后输入以下 URL
https://github.com/stleamist/BetterSafariView.git
有关更多详细信息,请参阅 Adding Package Dependencies to Your App。
您可以在演示项目中查看它在每个平台上的工作方式,并将其与其他朴素的实现进行比较。通过打开 BetterSafariView.xcworkspace 查看演示应用程序。
注意: 此演示项目适用于 iOS 14.0+、macOS 11.0+ 和 watchOS 7.0+,而该软件包与 iOS 13.0+、macOS 10.15+ 和 watchOS 6.2+ 兼容。
BetterSafariView 在 MIT 许可证下发布。 有关详细信息,请参阅 LICENSE。