Skip to content

Commit 95b3a05

Browse files
Updated REAMDE file with "WebView consent synchronization" article.
1 parent 272eb8b commit 95b3a05

1 file changed

Lines changed: 155 additions & 2 deletions

File tree

README.md

Lines changed: 155 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -472,8 +472,6 @@ Analytics.setConsent(consentSettings)
472472

473473
# Delaying Google Mobile Ads display until ATT and User Consent
474474

475-
476-
477475
Sometimes you need to ensure that both Apple's App Tracking Transparency prompt and user consent decision have been recorded before initializing and loading Google Mobile Ads. To implement this flow:
478476

479477
1. **Wait for ATT authorization and CMP readiness**
@@ -533,3 +531,158 @@ private func tryStartAdsIfAllowed() {
533531
adsStarted = true
534532
}
535533
```
534+
535+
536+
# WebView consent synchronization
537+
538+
### Overview
539+
When an iOS app contains both native UI (built with Swift/Objective-C) and web content inside a WKWebView (for example, embedded pages or widgets), there is a risk the Consent Management Platform (CMP) dialog will appear twice — once from the native SDK and again inside the web page.
540+
541+
The `webViewLoadUrl(urlString:config:)` method helps synchronize consent between the native and web layers. It configures a WKWebView (injecting a small clickioSDK JS bridge and message handler), ensures the web page can read/write the same saved consent state, and returns a ready-to-use WebViewController for your integration.
542+
543+
It does not present the controller for you — **the host app is responsible for how to show and hide it** (present (modally or not), add as a child/overlay, or embed as a platform view in Flutter/React-Native) **and for cleaning it up when it’s no longer needed**.
544+
545+
#### Tip: use `webViewLoadUrl` whenever you need to display web content that must respect the user’s consent already stored natively.
546+
547+
```swift
548+
func webViewLoadUrl(
549+
urlString: String, // accepts your webView's URL
550+
config: WebViewConfig = WebViewConfig() // accepts config object that describes your WebView's parameters: backgroundColor, width, height, gravity.
551+
) -> WebViewController
552+
```
553+
554+
### Configuration
555+
```swift
556+
public struct WebViewConfig {
557+
public var backgroundColor: UIColor = .clear // default value - .clear
558+
public var width: CGFloat? // nil = full width
559+
public var height: CGFloat? // nil = full height
560+
public var gravity: WebViewGravity = .center // default value - .center
561+
}
562+
563+
public enum WebViewGravity {
564+
case top, center, bottom
565+
}
566+
```
567+
568+
### Intregration examples
569+
570+
#### UIKit example
571+
```swift
572+
// WebViewController set-up
573+
let config = WebViewConfig(
574+
backgroundColor: .cyan,
575+
width: 280,
576+
height: 280,
577+
gravity: .center
578+
)
579+
580+
let webVC = ClickioConsentSDK.shared.webViewLoadUrl(
581+
urlString: "https://example.com", // your's webView URL
582+
config: config
583+
)
584+
585+
// Embed webVC as a child view controller and insert its view into this controller's view hierarchy.
586+
// Call didMove(toParent:) to finish the containment handshake so the child is fully active.
587+
addChild(webVC)
588+
view.addSubview(webVC.view)
589+
webVC.didMove(toParent: self)
590+
591+
addChild(webVC)
592+
view.addSubview(webVC.view)
593+
webVC.didMove(toParent: self)
594+
595+
// Constraints set-up
596+
webVC.view.translatesAutoresizingMaskIntoConstraints = false
597+
NSLayoutConstraint.activate([
598+
webVC.view.widthAnchor.constraint(equalToConstant: 280),
599+
webVC.view.heightAnchor.constraint(equalToConstant: 280),
600+
webVC.view.centerXAnchor.constraint(equalTo: view.centerXAnchor),
601+
webVC.view.centerYAnchor.constraint(equalTo: view.centerYAnchor)
602+
])
603+
```
604+
605+
#### When removing, don't forget to call the appropriate methods and clean up delegates/handlers through SDK's `cleanup` method that safely releases webview resources and detach handlers:
606+
```
607+
webVC.willMove(toParent: nil)
608+
webVC.cleanup() // cleanup the resources
609+
webVC.view.removeFromSuperview()
610+
webVC.removeFromParent()
611+
```
612+
613+
#### SwiftUI example
614+
Create a `UIViewControllerRepresentable` wrapper that returns the WebViewController produced by `ClickioConsentSDK.shared.webViewLoadUrl(...)`.
615+
```swift
616+
// MARK: - UIViewControllerRepresentable wrapper
617+
struct CustomWebViewControllerRepresentable: UIViewControllerRepresentable {
618+
let urlString: String // your webView's URL
619+
let config: WebViewConfig
620+
621+
// returns the prepared WebViewController from webViewLoadUrl
622+
func makeUIViewController(context: Context) -> WebViewController {
623+
// Create WebViewController through the SDK
624+
let webVC = ClickioConsentSDK.shared.webViewLoadUrl(
625+
urlString: urlString,
626+
config: config
627+
)
628+
return webVC
629+
}
630+
631+
func updateUIViewController(_ uiViewController: WebViewController, context: Context) { }
632+
633+
static func dismantleUIViewController(_ uiViewController: WebViewController, coordinator: ()) {
634+
DispatchQueue.main.async {
635+
// Best practice: call a cleanup() method on the controller that cancels loads,
636+
// clears delegates and removes script message handlers.
637+
uiViewController.cleanup()
638+
}
639+
}
640+
}
641+
```
642+
#### Note: To close the overlay, either (a) add an on-screen close button that sets the flag to false, or (b) provide an SDK callback/registration so the web content can request closing (recommended if you need web-driven close).
643+
#### When removed, SwiftUI calls `dismantleUIViewController`, so call `webVC.cleanup()` there — stop loading, nil delegates, and remove the script message handler.
644+
645+
Use a @State flag (for example showCustomWeb) to show/hide the wrapper in your view tree (sheet, overlay, inline, etc.).
646+
```swift
647+
@State private var showCustomWeb = false
648+
```
649+
650+
Show / hide in SwiftUI (overlay example):
651+
```swift
652+
var body: some View {
653+
ZStack {
654+
// your main UI...
655+
656+
if showCustomWeb {
657+
ZStack(alignment: .topTrailing) {
658+
// The representable wrapper that embeds the native WebViewController.
659+
CustomWebViewControllerRepresentable(
660+
urlString: "https://example.com", // your webView's URL
661+
config: WebViewConfig(
662+
backgroundColor: UIColor.cyan,
663+
width: 280,
664+
height: 280,
665+
gravity: .center
666+
)
667+
)
668+
.frame(width: 280, height: 280)
669+
.cornerRadius(12)
670+
.shadow(radius: 6)
671+
672+
// OPTIONAL: Add a close button overlay that simply flips the SwiftUI state flag.
673+
// When `showCustomWeb` becomes false, SwiftUI removes the representable and calls dismantle().
674+
Button(action: { showCustomWeb = false }) {
675+
Image(systemName: "xmark.circle.fill")
676+
.resizable()
677+
.frame(width: 28, height: 28)
678+
.foregroundColor(.black)
679+
}
680+
.padding()
681+
.offset(x: 8, y: -8)
682+
}
683+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
684+
.zIndex(1)
685+
}
686+
}
687+
}
688+
```

0 commit comments

Comments
 (0)