diff --git a/CHANGELOG.md b/CHANGELOG.md index ee5e1a8..ead4d9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [v2.4.2](https://github.com/stleamist/BetterSafariView/releases/tag/v2.4.2) (2023-10-30) +### Fixed +- Fixed an issue where the `SafariViewPresenter` fails to find a view controller to presented from (#41 & #46). Thanks, @Tunous and @SongJiyeon! + ## [v2.4.1](https://github.com/stleamist/BetterSafariView/releases/tag/v2.4.1) (2023-01-15) ### Fixed - Fixed an issue where the `WebAuthenticationPresenter` fails to find its presentation anchor (#22). Thanks, @kevvdevv, @exentrich, and @ldstreet! diff --git a/Demo/iOS/Views/RootView.swift b/Demo/iOS/Views/RootView.swift index 7650652..a687ce1 100644 --- a/Demo/iOS/Views/RootView.swift +++ b/Demo/iOS/Views/RootView.swift @@ -73,7 +73,7 @@ struct RootView: View { WebAuthenticationSessionOptionsForm(options: $webAuthenticationSessionOptions) } } - + Section(header: Text("NaiveSafariView" + "\n" + "(Just for comparison. Do not use in practice.)").textCase(nil)) { Button(action: { showingNaiveSafariViewSheet = true }) { HStack { diff --git a/README.md b/README.md index 1f1b3f1..2813d90 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.4.1")) +.package(url: "https://github.com/stleamist/BetterSafariView.git", .upToNextMajor(from: "2.4.2")) ``` 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.4.1")) + .package(url: "https://github.com/stleamist/BetterSafariView.git", .upToNextMajor(from: "2.4.2")) ], targets: [ .target(name: "MyTarget", dependencies: ["BetterSafariView"]) diff --git a/Sources/BetterSafariView/SafariView/SafariViewPresenter.swift b/Sources/BetterSafariView/SafariView/SafariViewPresenter.swift index ec98958..bc61799 100644 --- a/Sources/BetterSafariView/SafariView/SafariViewPresenter.swift +++ b/Sources/BetterSafariView/SafariView/SafariViewPresenter.swift @@ -3,6 +3,10 @@ import SwiftUI import SafariServices +// `SafariViewPresenter` conforms `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`). struct SafariViewPresenter: UIViewRepresentable { // MARK: Representation @@ -79,13 +83,14 @@ extension SafariViewPresenter { let safariViewController = SFSafariViewController(url: representation.url, configuration: representation.configuration) safariViewController.delegate = self representation.applyModification(to: safariViewController) - - // 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 { + + // Presents a Safari view controller from the farthest `presentedViewController` of `UIWindow`. + // (same approach when presenting `UIAlertController`) + guard let presentingViewController = uiView.window?.farthestPresentedViewController else { + assertionFailure( + "Cannot find the view controller to present from." + + " This happens when a 'SafariViewPresenter' is detached from the window, or the window doesn't have 'rootViewController.'" + ) self.resetItemBinding() return } diff --git a/Sources/BetterSafariView/Shared/UIView+viewController.swift b/Sources/BetterSafariView/Shared/UIView+viewController.swift deleted file mode 100644 index 80aa67c..0000000 --- a/Sources/BetterSafariView/Shared/UIView+viewController.swift +++ /dev/null @@ -1,21 +0,0 @@ -#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/Shared/UIWindow+farthestPresentedViewController.swift b/Sources/BetterSafariView/Shared/UIWindow+farthestPresentedViewController.swift new file mode 100644 index 0000000..3b179ae --- /dev/null +++ b/Sources/BetterSafariView/Shared/UIWindow+farthestPresentedViewController.swift @@ -0,0 +1,14 @@ +#if os(iOS) + +import UIKit + +extension UIWindow { + + /// The view controller that was presented modally on top of the window. + var farthestPresentedViewController: UIViewController? { + guard let rootViewController = rootViewController else { return nil } + return Array(sequence(first: rootViewController, next: \.presentedViewController)).last + } +} + +#endif diff --git a/Sources/BetterSafariView/WebAuthenticationSession/WebAuthenticationPresenter.swift b/Sources/BetterSafariView/WebAuthenticationSession/WebAuthenticationPresenter.swift index bbe7552..90f87bd 100644 --- a/Sources/BetterSafariView/WebAuthenticationSession/WebAuthenticationPresenter.swift +++ b/Sources/BetterSafariView/WebAuthenticationSession/WebAuthenticationPresenter.swift @@ -199,7 +199,7 @@ extension WebAuthenticationPresenter { @available(iOS, introduced: 13.0, deprecated: 14.0) func setInteractiveDismissalDelegateIfPossible() { - guard let safariViewController = view.viewController?.presentedViewController as? SFSafariViewController else { + guard let safariViewController = view.window?.farthestPresentedViewController as? SFSafariViewController else { return } safariViewController.presentationController?.delegate = interactiveDismissalDelegate