Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Sources/FormbricksSDK/Helpers/FormbricksEnvironment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@

/// Returns the full environment‐fetch URL as a String for the given ID
static var getEnvironmentRequestEndpoint: String {
return ["api", "v2", "client", "{environmentId}", "environment"].joined(separator: "/")
let path = ["api", "v2", "client", "{environmentId}", "environment"].joined(separator: "/")

Check warning on line 23 in Sources/FormbricksSDK/Helpers/FormbricksEnvironment.swift

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this hard-coded path-delimiter.

See more on https://sonarcloud.io/project/issues?id=formbricks_ios&issues=AZ4GXUyOAtf65Lj1XQDU&open=AZ4GXUyOAtf65Lj1XQDU&pullRequest=45
return path
}

/// Returns the full post-user URL as a String for the given ID
Expand Down
73 changes: 52 additions & 21 deletions Sources/FormbricksSDK/Manager/PresentSurveyManager.swift
Original file line number Diff line number Diff line change
@@ -1,46 +1,77 @@
import SwiftUI

/// Presents a survey webview to the window's root
/// Presents a survey webview from the top-most view controller in the key window.
final class PresentSurveyManager {
init() {
/*
This empty initializer prevents external instantiation of the PresentSurveyManager class.
The class serves as a namespace for the present method, so instance creation is not needed and should be restricted.
*/
}

/// The view controller that will present the survey window.
private weak var viewController: UIViewController?

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

/// Walks the active presentation/navigation/tab hierarchy and returns the leaf VC.
/// Mirrors UIKit's own `presentedViewController` traversal so a single walker is enough.
private func topMostViewController(from viewController: UIViewController) -> UIViewController {
if let presented = viewController.presentedViewController,
!presented.isBeingDismissed {
return topMostViewController(from: presented)
}
if let navigation = viewController as? UINavigationController,
let visible = navigation.visibleViewController {
return topMostViewController(from: visible)
}
if let tabBar = viewController as? UITabBarController,
let selected = tabBar.selectedViewController {
return topMostViewController(from: selected)
}
return viewController
}
Comment thread
pandeymangg marked this conversation as resolved.

/// Present the webview as a page sheet over the current top-most view controller.
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)
})
} else {

guard let window = UIApplication.safeKeyWindow,
let rootVC = window.rootViewController else {
Formbricks.logger?.error("Survey present aborted: no key window or root view controller available.")
completion?(false)
return
}

let presenter = self.topMostViewController(from: rootVC)

// UIAlertController/action-sheets/popovers cannot host a modal sheet — presenting on them either
// crops the survey to the alert frame or is rejected by UIKit. Bail with a clear log so the host
// app can dismiss the alert before triggering the survey.
if presenter is UIAlertController {
Formbricks.logger?.warning("Survey present aborted: top-most VC is a UIAlertController. Dismiss it before triggering the survey.")
completion?(false)
return
}

let view = FormbricksView(viewModel: FormbricksViewModel(environmentResponse: environmentResponse, surveyId: id))
let vc = UIHostingController(rootView: view)
vc.modalPresentationStyle = .pageSheet
vc.view.backgroundColor = .clear
if let sheet = vc.sheetPresentationController {
sheet.detents = [.large()]
}
self.viewController = vc
presenter.present(vc, animated: true, completion: {
completion?(true)
})
}
}

/// Dismiss the webview
func dismissView() {
viewController?.dismiss(animated: true)
}

deinit {
Formbricks.logger?.debug("Deinitializing \(self)")
}
Expand Down
Loading