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/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