From 9400e2b445bf1a3a9f9a7d3b2c4e84113c070a29 Mon Sep 17 00:00:00 2001 From: narjes Date: Wed, 6 Aug 2025 10:33:19 +0200 Subject: [PATCH 1/4] IOS-6487 Add topMostVC for presenter --- .../UIWindow+topMostViewController.swift | 26 +++++++++++++++++++ .../Manager/PresentSurveyManager.swift | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 Sources/FormbricksSDK/Extension/UIWindow+topMostViewController.swift diff --git a/Sources/FormbricksSDK/Extension/UIWindow+topMostViewController.swift b/Sources/FormbricksSDK/Extension/UIWindow+topMostViewController.swift new file mode 100644 index 00000000..d72e44c9 --- /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 3cd731f9..c8bc908a 100644 --- a/Sources/FormbricksSDK/Manager/PresentSurveyManager.swift +++ b/Sources/FormbricksSDK/Manager/PresentSurveyManager.swift @@ -25,7 +25,7 @@ final class PresentSurveyManager { presentationController.detents = [.large()] } self.viewController = vc - window.rootViewController?.present(vc, animated: true, completion: nil) + window.topMostViewController()?.present(vc, animated: true, completion: nil) } } } From ceab083672b7d7773d6a628b811ac0e83fd52fba Mon Sep 17 00:00:00 2001 From: Narjes Date: Mon, 11 Aug 2025 16:40:15 +0200 Subject: [PATCH 2/4] Update bg color --- Sources/FormbricksSDK/Manager/PresentSurveyManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/FormbricksSDK/Manager/PresentSurveyManager.swift b/Sources/FormbricksSDK/Manager/PresentSurveyManager.swift index c8bc908a..4cbc30c0 100644 --- a/Sources/FormbricksSDK/Manager/PresentSurveyManager.swift +++ b/Sources/FormbricksSDK/Manager/PresentSurveyManager.swift @@ -20,7 +20,7 @@ final class PresentSurveyManager { let view = FormbricksView(viewModel: FormbricksViewModel(environmentResponse: environmentResponse, surveyId: id)) let vc = UIHostingController(rootView: view) vc.modalPresentationStyle = .overCurrentContext - vc.view.backgroundColor = UIColor.gray.withAlphaComponent(0.6) + vc.view.backgroundColor = UIColor(red: 0x1E/255.0, green: 0x1E/255.0, blue: 0x1E/255.0, alpha: 0.2) if let presentationController = vc.presentationController as? UISheetPresentationController { presentationController.detents = [.large()] } From 70ddb6ff8a6c34e400552f137efa3abb58b28d68 Mon Sep 17 00:00:00 2001 From: Narjes Date: Mon, 11 Aug 2025 16:41:01 +0200 Subject: [PATCH 3/4] Update FormbricksView fix gap --- Sources/FormbricksSDK/WebView/FormbricksView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/FormbricksSDK/WebView/FormbricksView.swift b/Sources/FormbricksSDK/WebView/FormbricksView.swift index c1ee78c5..e5c482e0 100644 --- a/Sources/FormbricksSDK/WebView/FormbricksView.swift +++ b/Sources/FormbricksSDK/WebView/FormbricksView.swift @@ -7,6 +7,7 @@ struct FormbricksView: View { var body: some View { if let htmlString = viewModel.htmlString { SurveyWebView(surveyId: viewModel.surveyId, htmlString: htmlString) + .ignoresSafeArea() } } } From b7a6bebb378364965f468b419535433cb2180841 Mon Sep 17 00:00:00 2001 From: "Hassaan F. Ahmed" Date: Wed, 18 Feb 2026 08:53:42 +0400 Subject: [PATCH 4/4] Issue fixed where the survey would not be presented when there is a viewcontroller already presented --- FormbricksSDK.podspec | 2 +- .../Manager/PresentSurveyManager.swift | 58 ++++++++++++++----- 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/FormbricksSDK.podspec b/FormbricksSDK.podspec index e66f8e22..362fb522 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/Manager/PresentSurveyManager.swift b/Sources/FormbricksSDK/Manager/PresentSurveyManager.swift index 5875cf84..7b8f5f11 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) + }) } }