diff --git a/FormbricksSDK.podspec b/FormbricksSDK.podspec index e66f8e2..362fb52 100644 --- a/FormbricksSDK.podspec +++ b/FormbricksSDK.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "FormbricksSDK" - s.version = "1.1.0" + s.version = "1.2.0" s.summary = "iOS SDK for Formbricks" s.homepage = "https://github.com/formbricks/ios" s.license = { :type => "MIT", :file => "LICENSE" } diff --git a/Sources/FormbricksSDK/Extension/UIWindow+topMostViewController.swift b/Sources/FormbricksSDK/Extension/UIWindow+topMostViewController.swift new file mode 100644 index 0000000..d72e44c --- /dev/null +++ b/Sources/FormbricksSDK/Extension/UIWindow+topMostViewController.swift @@ -0,0 +1,26 @@ +import UIKit + +extension UIWindow { + func topMostViewController() -> UIViewController? { + if let rootViewController: UIViewController = self.rootViewController { + return UIWindow.topMostViewControllerFrom(rootViewController) + } + return nil + } + + static func topMostViewControllerFrom(_ viewController: UIViewController) -> UIViewController { + if let navigationController = viewController as? UINavigationController, + let visibleController = navigationController.visibleViewController { + return topMostViewControllerFrom(visibleController) + } else if let tabBarController = viewController as? UITabBarController, + let selectedTabController = tabBarController.selectedViewController { + return topMostViewControllerFrom(selectedTabController) + } else { + if let presentedViewController = viewController.presentedViewController { + return topMostViewControllerFrom(presentedViewController) + } else { + return viewController + } + } + } +} diff --git a/Sources/FormbricksSDK/Manager/PresentSurveyManager.swift b/Sources/FormbricksSDK/Manager/PresentSurveyManager.swift index 5875cf8..7b8f5f1 100644 --- a/Sources/FormbricksSDK/Manager/PresentSurveyManager.swift +++ b/Sources/FormbricksSDK/Manager/PresentSurveyManager.swift @@ -11,28 +11,60 @@ final class PresentSurveyManager { /// The view controller that will present the survey window. private weak var viewController: UIViewController? - + + /// Finds the topmost view controller in the hierarchy to present from + private func topViewController(from viewController: UIViewController) -> UIViewController { + if let presented = viewController.presentedViewController { + return topViewController(from: presented) + } + if let navigation = viewController as? UINavigationController { + return topViewController(from: navigation.visibleViewController ?? navigation) + } + if let tabBar = viewController as? UITabBarController { + return topViewController(from: tabBar.selectedViewController ?? tabBar) + } + return viewController + } + /// Present the webview /// The native background is always `.clear` — overlay rendering is handled /// entirely by the JS survey library inside the WebView to avoid double-overlay artifacts. func present(environmentResponse: EnvironmentResponse, id: String, completion: ((Bool) -> Void)? = nil) { DispatchQueue.main.async { [weak self] in guard let self = self else { return } - if let window = UIApplication.safeKeyWindow { - let view = FormbricksView(viewModel: FormbricksViewModel(environmentResponse: environmentResponse, surveyId: id)) - let vc = UIHostingController(rootView: view) - vc.modalPresentationStyle = .overCurrentContext - vc.view.backgroundColor = .clear - if let presentationController = vc.presentationController as? UISheetPresentationController { - presentationController.detents = [.large()] - } - self.viewController = vc - window.rootViewController?.present(vc, animated: true, completion: { - completion?(true) - }) + guard let window = UIApplication.safeKeyWindow, + let rootVC = window.rootViewController else { + completion?(false) + return + } + + // Determine the presenter: use root if available, otherwise find topmost + let presenter: UIViewController + if rootVC.presentedViewController == nil { + // Root is free, use it directly (simple path) + presenter = rootVC } else { + // Root is already presenting, find the topmost view controller + presenter = self.topViewController(from: rootVC) + } + + // Check if presenter is already presenting + guard presenter.presentedViewController == nil else { completion?(false) + return + } + + let view = FormbricksView(viewModel: FormbricksViewModel(environmentResponse: environmentResponse, surveyId: id)) + let vc = UIHostingController(rootView: view) + vc.modalPresentationStyle = .overCurrentContext + vc.view.backgroundColor = .clear + if let presentationController = vc.presentationController as? UISheetPresentationController { + presentationController.detents = [.large()] } + self.viewController = vc + presenter.present(vc, animated: true, completion: { + completion?(true) + }) } }