From 1f16788f2418d605108ecf22a996433ccba7a0be Mon Sep 17 00:00:00 2001 From: Sumeru Chatterjee Date: Wed, 14 Jan 2026 07:27:13 +0530 Subject: [PATCH 1/4] SDK-31 Fix full-position IAM safe area handling Apply safe area insets to WKWebView scrollView content for full-position in-app messages. This ensures content is not hidden behind notch, status bar, or home indicator while maintaining edge-to-edge background coverage. Co-Authored-By: Claude Opus 4.5 --- .../Internal/IterableHtmlMessageViewController.swift | 12 +++++++++++- swift-sdk/Internal/Utilities/WebViewProtocol.swift | 7 ++++++- tests/common/MockWebView.swift | 9 +++++++-- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/swift-sdk/Internal/IterableHtmlMessageViewController.swift b/swift-sdk/Internal/IterableHtmlMessageViewController.swift index 215214c9d..d9e3fb884 100644 --- a/swift-sdk/Internal/IterableHtmlMessageViewController.swift +++ b/swift-sdk/Internal/IterableHtmlMessageViewController.swift @@ -227,13 +227,23 @@ class IterableHtmlMessageViewController: UIViewController { let parentPosition = ViewPosition(width: view.bounds.width, height: view.bounds.height, center: view.center) + let safeAreaInsets = InAppCalculations.safeAreaInsets(for: view) IterableHtmlMessageViewController.calculateWebViewPosition(webView: webView, - safeAreaInsets: InAppCalculations.safeAreaInsets(for: view), + safeAreaInsets: safeAreaInsets, parentPosition: parentPosition, paddingLeft: CGFloat(parameters.padding.left), paddingRight: CGFloat(parameters.padding.right), location: location) .onSuccess { [weak self] position in + // Apply safe area insets to scrollView content for full position + // This keeps the webView covering the full screen (edge-to-edge background) + // while ensuring content is not hidden behind notch or home indicator + if self?.location == .full { + self?.webView.set(contentInset: safeAreaInsets) + } else { + self?.webView.set(contentInset: .zero) + } + if animate { self?.animateWhileEntering(position) } else { diff --git a/swift-sdk/Internal/Utilities/WebViewProtocol.swift b/swift-sdk/Internal/Utilities/WebViewProtocol.swift index 13cf7380a..e9d27ab61 100644 --- a/swift-sdk/Internal/Utilities/WebViewProtocol.swift +++ b/swift-sdk/Internal/Utilities/WebViewProtocol.swift @@ -17,6 +17,7 @@ protocol WebViewProtocol { @discardableResult func loadHTMLString(_ string: String, baseURL: URL?) -> WKNavigation? func set(position: ViewPosition) func set(navigationDelegate: WKNavigationDelegate?) + func set(contentInset: UIEdgeInsets) func layoutSubviews() func calculateHeight() -> Pending } @@ -39,7 +40,11 @@ extension WKWebView: WebViewProtocol { func set(navigationDelegate: WKNavigationDelegate?) { self.navigationDelegate = navigationDelegate } - + + func set(contentInset: UIEdgeInsets) { + scrollView.contentInset = contentInset + } + func calculateHeight() -> Pending { let fulfill = Fulfill() diff --git a/tests/common/MockWebView.swift b/tests/common/MockWebView.swift index b1bdc4013..a0912f7dd 100644 --- a/tests/common/MockWebView.swift +++ b/tests/common/MockWebView.swift @@ -22,7 +22,11 @@ class MockWebView: WebViewProtocol { } func set(navigationDelegate _: WKNavigationDelegate?) {} - + + func set(contentInset: UIEdgeInsets) { + self.contentInset = contentInset + } + func layoutSubviews() {} func calculateHeight() -> Pending { @@ -30,7 +34,8 @@ class MockWebView: WebViewProtocol { } var position: ViewPosition = ViewPosition() - + var contentInset: UIEdgeInsets = .zero + private var height: CGFloat init(height: CGFloat) { From cf5814f2c83328eab850bee0787352d4f3c10fa5 Mon Sep 17 00:00:00 2001 From: Sumeru Chatterjee Date: Fri, 16 Jan 2026 16:08:15 +0530 Subject: [PATCH 2/4] Fixes --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f1bd0f34c..715ddd6e7 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ CI.swift *.mdb .build/arm64-apple-macosx/debug/IterableSDK.build/output-file-map.json .build +.mcp.json From b0b883a4574b4098ed62f4fbec162f0841f4c5f8 Mon Sep 17 00:00:00 2001 From: Sumeru Chatterjee Date: Tue, 20 Jan 2026 07:07:38 +0530 Subject: [PATCH 3/4] Add support for integration app to test in app full --- .../App/InAppMessage/InAppMessageTestView.swift | 12 +++++++++++- tests/common/MockWebView.swift | 7 ++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/tests/business-critical-integration/integration-test-app/IterableSDK-Integration-Tester/App/InAppMessage/InAppMessageTestView.swift b/tests/business-critical-integration/integration-test-app/IterableSDK-Integration-Tester/App/InAppMessage/InAppMessageTestView.swift index 704c7ca16..dcc905b8c 100644 --- a/tests/business-critical-integration/integration-test-app/IterableSDK-Integration-Tester/App/InAppMessage/InAppMessageTestView.swift +++ b/tests/business-critical-integration/integration-test-app/IterableSDK-Integration-Tester/App/InAppMessage/InAppMessageTestView.swift @@ -111,7 +111,17 @@ struct InAppMessageTestView: View { } .accessibilityIdentifier("trigger-testview-in-app-button") .disabled(viewModel.isTriggeringCampaign) - + + ActionButton( + title: "Send Full Screen In-App (SDK-31 Test)", + backgroundColor: Color(.systemPurple), + isLoading: viewModel.isTriggeringCampaign + ) { + viewModel.triggerCampaign(16505358) + } + .accessibilityIdentifier("trigger-fullscreen-in-app-button") + .disabled(viewModel.isTriggeringCampaign) + ActionButton( title: "Send Silent Push (Campaign 14750476)", backgroundColor: Color(.brown), diff --git a/tests/common/MockWebView.swift b/tests/common/MockWebView.swift index a0912f7dd..cdc6e588c 100644 --- a/tests/common/MockWebView.swift +++ b/tests/common/MockWebView.swift @@ -20,7 +20,12 @@ class MockWebView: WebViewProtocol { view.frame.size.height = position.height view.center = position.center } - + + func set(frame: CGRect) { + view.frame = frame + self.position = ViewPosition(width: frame.width, height: frame.height, center: CGPoint(x: frame.midX, y: frame.midY)) + } + func set(navigationDelegate _: WKNavigationDelegate?) {} func set(contentInset: UIEdgeInsets) { From 521326eebad12ded7e6e01087cc90dc62ed84eaf Mon Sep 17 00:00:00 2001 From: Sumeru Chatterjee Date: Tue, 20 Jan 2026 07:36:29 +0530 Subject: [PATCH 4/4] Fixes --- .../IterableHtmlMessageViewController.swift | 23 +++++++++++-------- .../Internal/Utilities/WebViewProtocol.swift | 5 ---- tests/common/MockWebView.swift | 20 ++++------------ 3 files changed, 18 insertions(+), 30 deletions(-) diff --git a/swift-sdk/Internal/IterableHtmlMessageViewController.swift b/swift-sdk/Internal/IterableHtmlMessageViewController.swift index d9e3fb884..90bea797e 100644 --- a/swift-sdk/Internal/IterableHtmlMessageViewController.swift +++ b/swift-sdk/Internal/IterableHtmlMessageViewController.swift @@ -224,9 +224,21 @@ class IterableHtmlMessageViewController: UIViewController { /// Resizes the webview based upon the insetPadding, height etc private func resizeWebView(animate: Bool) { - let parentPosition = ViewPosition(width: view.bounds.width, + // For full position, use screen bounds to ensure edge-to-edge coverage + // including behind notch/Dynamic Island and home indicator + let parentPosition: ViewPosition + if location == .full { + let screenBounds = UIScreen.main.bounds + // Convert screen center to view's coordinate system + let screenCenterInView = view.convert(CGPoint(x: screenBounds.midX, y: screenBounds.midY), from: nil) + parentPosition = ViewPosition(width: screenBounds.width, + height: screenBounds.height, + center: screenCenterInView) + } else { + parentPosition = ViewPosition(width: view.bounds.width, height: view.bounds.height, center: view.center) + } let safeAreaInsets = InAppCalculations.safeAreaInsets(for: view) IterableHtmlMessageViewController.calculateWebViewPosition(webView: webView, safeAreaInsets: safeAreaInsets, @@ -235,15 +247,6 @@ class IterableHtmlMessageViewController: UIViewController { paddingRight: CGFloat(parameters.padding.right), location: location) .onSuccess { [weak self] position in - // Apply safe area insets to scrollView content for full position - // This keeps the webView covering the full screen (edge-to-edge background) - // while ensuring content is not hidden behind notch or home indicator - if self?.location == .full { - self?.webView.set(contentInset: safeAreaInsets) - } else { - self?.webView.set(contentInset: .zero) - } - if animate { self?.animateWhileEntering(position) } else { diff --git a/swift-sdk/Internal/Utilities/WebViewProtocol.swift b/swift-sdk/Internal/Utilities/WebViewProtocol.swift index e9d27ab61..06a0e5b3e 100644 --- a/swift-sdk/Internal/Utilities/WebViewProtocol.swift +++ b/swift-sdk/Internal/Utilities/WebViewProtocol.swift @@ -17,7 +17,6 @@ protocol WebViewProtocol { @discardableResult func loadHTMLString(_ string: String, baseURL: URL?) -> WKNavigation? func set(position: ViewPosition) func set(navigationDelegate: WKNavigationDelegate?) - func set(contentInset: UIEdgeInsets) func layoutSubviews() func calculateHeight() -> Pending } @@ -41,10 +40,6 @@ extension WKWebView: WebViewProtocol { self.navigationDelegate = navigationDelegate } - func set(contentInset: UIEdgeInsets) { - scrollView.contentInset = contentInset - } - func calculateHeight() -> Pending { let fulfill = Fulfill() diff --git a/tests/common/MockWebView.swift b/tests/common/MockWebView.swift index cdc6e588c..cf560832b 100644 --- a/tests/common/MockWebView.swift +++ b/tests/common/MockWebView.swift @@ -9,11 +9,11 @@ import WebKit class MockWebView: WebViewProtocol { let view: UIView = UIView() - + func loadHTMLString(_: String, baseURL _: URL?) -> WKNavigation? { nil } - + func set(position: ViewPosition) { self.position = position view.frame.size.width = position.width @@ -21,28 +21,18 @@ class MockWebView: WebViewProtocol { view.center = position.center } - func set(frame: CGRect) { - view.frame = frame - self.position = ViewPosition(width: frame.width, height: frame.height, center: CGPoint(x: frame.midX, y: frame.midY)) - } - func set(navigationDelegate _: WKNavigationDelegate?) {} - func set(contentInset: UIEdgeInsets) { - self.contentInset = contentInset - } - func layoutSubviews() {} - + func calculateHeight() -> Pending { Fulfill(value: height) } - + var position: ViewPosition = ViewPosition() - var contentInset: UIEdgeInsets = .zero private var height: CGFloat - + init(height: CGFloat) { self.height = height }