diff --git a/CHANGELOG.md b/CHANGELOG.md
index 09908f3..5f3cd81 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
# Changelog
+## [v2.4.0](https://github.com/stleamist/BetterSafariView/releases/tag/v2.4.0) (2020-09-25)
+### Changed
+- `SafariViewPresenter` and `WebAuthenticationPresenter` now conforms to `UIViewRepresentable`, instead of `UIViewControllerRepresentable`.
+
+### Fixed
+- Fixed an issue where the `SafariView` is not presented on the multi-layered modal sheets (#20). Thanks @twodayslate!
+
## [v2.3.1](https://github.com/stleamist/BetterSafariView/releases/tag/v2.3.1) (2020-01-20)
### Fixed
- Fixed an issue where the `SafariView` is not presented on the modal sheets (#9). Thanks @boherna!
diff --git a/Demo/BetterSafariViewDemo.xcodeproj/xcshareddata/xcschemes/BetterSafariViewDemo (watchOS).xcscheme b/Demo/BetterSafariViewDemo.xcodeproj/xcshareddata/xcschemes/BetterSafariViewDemo (watchOS).xcscheme
index 3023fee..475c138 100644
--- a/Demo/BetterSafariViewDemo.xcodeproj/xcshareddata/xcschemes/BetterSafariViewDemo (watchOS).xcscheme
+++ b/Demo/BetterSafariViewDemo.xcodeproj/xcshareddata/xcschemes/BetterSafariViewDemo (watchOS).xcscheme
@@ -40,10 +40,8 @@
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
-
+
-
+
-
+
-
-
-
-
-
+
diff --git a/README.md b/README.md
index 8bd990f..ab22bb1 100644
--- a/README.md
+++ b/README.md
@@ -285,7 +285,7 @@ func prefersEphemeralWebBrowserSession(_ prefersEphemeralWebBrowserSession: Bool
Add the following line to the `dependencies` in your [`Package.swift`](https://developer.apple.com/documentation/swift_packages/package) file:
```swift
-.package(url: "https://github.com/stleamist/BetterSafariView.git", .upToNextMajor(from: "2.3.1"))
+.package(url: "https://github.com/stleamist/BetterSafariView.git", .upToNextMajor(from: "2.4.0"))
```
Next, add `BetterSafariView` as a dependency for your targets:
@@ -304,7 +304,7 @@ import PackageDescription
let package = Package(
name: "MyPackage",
dependencies: [
- .package(url: "https://github.com/stleamist/BetterSafariView.git", .upToNextMajor(from: "2.3.1"))
+ .package(url: "https://github.com/stleamist/BetterSafariView.git", .upToNextMajor(from: "2.4.0"))
],
targets: [
.target(name: "MyTarget", dependencies: ["BetterSafariView"])
diff --git a/Sources/BetterSafariView/SafariView/SafariViewPresenter.swift b/Sources/BetterSafariView/SafariView/SafariViewPresenter.swift
index 8de0043..ec98958 100644
--- a/Sources/BetterSafariView/SafariView/SafariViewPresenter.swift
+++ b/Sources/BetterSafariView/SafariView/SafariViewPresenter.swift
@@ -3,7 +3,7 @@
import SwiftUI
import SafariServices
-struct SafariViewPresenter: UIViewControllerRepresentable {
+struct SafariViewPresenter: UIViewRepresentable {
// MARK: Representation
@@ -11,18 +11,17 @@ struct SafariViewPresenter: UIViewControllerRepresentable {
var onDismiss: (() -> Void)? = nil
var representationBuilder: (Item) -> SafariView
- // MARK: UIViewControllerRepresentable
+ // MARK: UIViewRepresentable
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self)
}
- func makeUIViewController(context: Context) -> UIViewController {
- return context.coordinator.uiViewController
+ func makeUIView(context: Context) -> UIView {
+ return context.coordinator.uiView
}
- func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
-
+ func updateUIView(_ uiView: UIView, context: Context) {
// Keep the coordinator updated with a new presenter struct.
context.coordinator.parent = self
context.coordinator.item = item
@@ -43,7 +42,8 @@ extension SafariViewPresenter {
// MARK: View Controller Holding
- let uiViewController = UIViewController()
+ let uiView = UIView()
+ private weak var safariViewController: SFSafariViewController?
// MARK: Item Handling
@@ -75,25 +75,28 @@ extension SafariViewPresenter {
// MARK: Presentation Handlers
private func presentSafariViewController(with item: Item) {
- guard uiViewController.presentedViewController == nil else {
- return
- }
-
let representation = parent.representationBuilder(item)
let safariViewController = SFSafariViewController(url: representation.url, configuration: representation.configuration)
safariViewController.delegate = self
representation.applyModification(to: safariViewController)
- // There is a problem that page loading and parallel push animation are not working when a modifier is attached to the view in a `List`.
- // As a workaround, use a `rootViewController` of the `window` for presenting.
- // (Unlike the other view controllers, a view controller hosted by a cell doesn't have a parent, but has the same window.)
- var presentingViewController = uiViewController.view.window?.rootViewController
- presentingViewController = presentingViewController?.presentedViewController ?? presentingViewController ?? uiViewController
- presentingViewController?.present(safariViewController, animated: true)
+ // Present a Safari view controller from the `viewController` of `UIViewRepresentable`, instead of `UIViewControllerRepresentable`.
+ // This fixes an issue where the Safari view controller is not presented properly
+ // when the `UIViewControllerRepresentable` is detached from the root view controller (e.g. `UIViewController` contained in `UITableViewCell`)
+ // while allowing it to be presented even on the modal sheets.
+ // Thanks to: Bohdan Hernandez Navia (@boherna)
+ guard let presentingViewController = uiView.viewController else {
+ self.resetItemBinding()
+ return
+ }
+
+ presentingViewController.present(safariViewController, animated: true)
+
+ self.safariViewController = safariViewController
}
private func updateSafariViewController(with item: Item) {
- guard let safariViewController = uiViewController.presentedViewController as? SFSafariViewController else {
+ guard let safariViewController = safariViewController else {
return
}
let representation = parent.representationBuilder(item)
@@ -101,28 +104,25 @@ extension SafariViewPresenter {
}
private func dismissSafariViewController(completion: (() -> Void)? = nil) {
- let dismissCompletion: () -> Void = {
- self.handleDismissalWithoutResettingItemBinding()
- completion?()
- }
-
- guard uiViewController.presentedViewController != nil else {
- dismissCompletion()
+ guard let safariViewController = safariViewController else {
return
}
- // Check if the `uiViewController` is a instance of the `SFSafariViewController`
- // to prevent other controllers presented by the container view from being dismissed unintentionally.
- guard let safariViewController = uiViewController.presentedViewController as? SFSafariViewController else {
- return
+ safariViewController.dismiss(animated: true) {
+ self.handleDismissal()
+ completion?()
}
- safariViewController.dismiss(animated: true, completion: dismissCompletion)
}
// MARK: Dismissal Handlers
+ // Used when the `viewController` of `uiView` does not exist during the preparation of presentation.
+ private func resetItemBinding() {
+ parent.item = nil
+ }
+
// Used when the Safari view controller is finished by an item change during view update.
- private func handleDismissalWithoutResettingItemBinding() {
+ private func handleDismissal() {
parent.onDismiss?()
}
diff --git a/Sources/BetterSafariView/Shared.swift b/Sources/BetterSafariView/Shared/Identifiables.swift
similarity index 100%
rename from Sources/BetterSafariView/Shared.swift
rename to Sources/BetterSafariView/Shared/Identifiables.swift
diff --git a/Sources/BetterSafariView/Shared/UIView+viewController.swift b/Sources/BetterSafariView/Shared/UIView+viewController.swift
new file mode 100644
index 0000000..80aa67c
--- /dev/null
+++ b/Sources/BetterSafariView/Shared/UIView+viewController.swift
@@ -0,0 +1,21 @@
+#if os(iOS)
+
+import UIKit
+
+extension UIView {
+
+ /// The receiver’s view controller, or `nil` if it has none.
+ ///
+ /// This property is `nil` if the view has not yet been added to a view controller.
+ var viewController: UIViewController? {
+ if let nextResponder = self.next as? UIViewController {
+ return nextResponder
+ } else if let nextResponder = self.next as? UIView {
+ return nextResponder.viewController
+ } else {
+ return nil
+ }
+ }
+}
+
+#endif
diff --git a/Sources/BetterSafariView/WebAuthenticationSession/WebAuthenticationPresenter.swift b/Sources/BetterSafariView/WebAuthenticationSession/WebAuthenticationPresenter.swift
index 1975e33..ec664c6 100644
--- a/Sources/BetterSafariView/WebAuthenticationSession/WebAuthenticationPresenter.swift
+++ b/Sources/BetterSafariView/WebAuthenticationSession/WebAuthenticationPresenter.swift
@@ -7,29 +7,26 @@ import SafariServices
#endif
#if os(iOS)
-typealias ConcreteViewController = UIViewController
-typealias ViewController = UIViewController
-typealias ViewControllerRepresentable = UIViewControllerRepresentable
+typealias ViewType = UIView
+typealias ViewRepresentableType = UIViewRepresentable
#elseif os(macOS)
-typealias ConcreteViewController = NSTabViewController
-typealias ViewController = NSViewController
-typealias ViewControllerRepresentable = NSViewControllerRepresentable
+typealias ViewType = NSView
+typealias ViewRepresentableType = NSViewRepresentable
#elseif os(watchOS)
// Use `WKInterfaceInlineMovie` as a concrete interface objct type,
// since there is no public initializer for `WKInterfaceObject`.
-typealias ConcreteViewController = WKInterfaceInlineMovie
-typealias ViewController = WKInterfaceObject
-typealias ViewControllerRepresentable = WKInterfaceObjectRepresentable
+typealias ViewType = WKInterfaceInlineMovie
+typealias ViewRepresentableType = WKInterfaceObjectRepresentable
#endif
-struct WebAuthenticationPresenter: ViewControllerRepresentable {
+struct WebAuthenticationPresenter: ViewRepresentableType {
// MARK: Representation
@Binding var item: Item?
var representationBuilder: (Item) -> WebAuthenticationSession
- // MARK: ViewControllerRepresentable
+ // MARK: ViewRepresentable
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self)
@@ -37,16 +34,16 @@ struct WebAuthenticationPresenter: ViewControllerRepresentab
#if os(iOS)
- func makeUIViewController(context: Context) -> ViewController {
- return makeViewController(context: context)
+ func makeUIView(context: Context) -> ViewType {
+ return makeView(context: context)
}
- func updateUIViewController(_ uiViewController: ViewController, context: Context) {
+ func updateUIView(_ uiView: ViewType, context: Context) {
- updateViewController(uiViewController, context: context)
+ updateView(uiView, context: context)
// To set a delegate for the presentation controller of an `SFAuthenticationViewController` as soon as possible,
- // check the view controller presented by `uiViewController` then set it as a delegate on every view updates.
+ // check the view controller presented by `view.viewController` then set it as a delegate on every view updates.
// INFO: `SFAuthenticationViewController` is a private subclass of `SFSafariViewController`.
guard #available(iOS 14.0, *) else {
context.coordinator.setInteractiveDismissalDelegateIfPossible()
@@ -56,31 +53,31 @@ struct WebAuthenticationPresenter: ViewControllerRepresentab
#elseif os(macOS)
- func makeNSViewController(context: Context) -> ViewController {
- return makeViewController(context: context)
+ func makeNSView(context: Context) -> ViewType {
+ return makeView(context: context)
}
- func updateNSViewController(_ nsViewController: ViewController, context: Context) {
- updateViewController(nsViewController, context: context)
+ func updateNSView(_ nsView: ViewType, context: Context) {
+ updateView(nsView, context: context)
}
#elseif os(watchOS)
- func makeWKInterfaceObject(context: Context) -> ViewController {
- return makeViewController(context: context)
+ func makeWKInterfaceObject(context: Context) -> ViewType {
+ return makeView(context: context)
}
- func updateWKInterfaceObject(_ wkInterfaceObject: ViewController, context: Context) {
- updateViewController(wkInterfaceObject, context: context)
+ func updateWKInterfaceObject(_ wkInterfaceObject: ViewType, context: Context) {
+ updateView(wkInterfaceObject, context: context)
}
#endif
- private func makeViewController(context: Context) -> ViewController {
- return context.coordinator.viewController
+ private func makeView(context: Context) -> ViewType {
+ return context.coordinator.view
}
- private func updateViewController(_ viewController: ViewController, context: Context) {
+ private func updateView(_ view: ViewType, context: Context) {
// Keep the coordinator updated with a new presenter struct.
context.coordinator.parent = self
context.coordinator.item = item
@@ -101,8 +98,8 @@ extension WebAuthenticationPresenter {
// MARK: View Controller Holding
- let viewController = ConcreteViewController()
- private var session: ASWebAuthenticationSession?
+ let view = ViewType()
+ private weak var session: ASWebAuthenticationSession?
// MARK: Item Handling
@@ -146,13 +143,13 @@ extension WebAuthenticationPresenter {
representation.applyModification(to: session)
- self.session = session
session.start()
+
+ self.session = session
}
private func cancelWebAuthenticationSession() {
session?.cancel()
- session = nil
}
// MARK: Dismissal Handlers
@@ -173,7 +170,7 @@ extension WebAuthenticationPresenter {
class PresentationContextProvider: NSObject, ASWebAuthenticationPresentationContextProviding {
- weak var coordinator: WebAuthenticationPresenter.Coordinator?
+ unowned var coordinator: WebAuthenticationPresenter.Coordinator
init(coordinator: WebAuthenticationPresenter.Coordinator) {
self.coordinator = coordinator
@@ -182,7 +179,7 @@ extension WebAuthenticationPresenter {
// MARK: ASWebAuthenticationPresentationContextProviding
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
- return coordinator!.viewController.view.window!
+ return coordinator.view.window!
}
}
@@ -202,7 +199,7 @@ extension WebAuthenticationPresenter {
@available(iOS, introduced: 13.0, deprecated: 14.0)
func setInteractiveDismissalDelegateIfPossible() {
- guard let safariViewController = viewController.presentedViewController as? SFSafariViewController else {
+ guard let safariViewController = view.viewController?.presentedViewController as? SFSafariViewController else {
return
}
safariViewController.presentationController?.delegate = interactiveDismissalDelegate