ARTICLE AD BOX
Trying to integrate adaptive admob for iOS part in my compose multiplatform project.
I followed these materials:
A guy from Medium: https://medium.com/@diegoturchi95/admob-integration-in-compose-multiplatform-kmp-65de75b2f67c
Google official doc: https://developers.google.com/admob/ios/banner
As a result, my implementation looks like this:
Swift codes:
ComposeView
import UIKit import SwiftUI import ComposeApp import GoogleMobileAds struct ComposeView: UIViewControllerRepresentable { //init() { // MainViewControllerKt.IOSBanner = { () -> UIViewController in //let width = UIScreen.main.bounds.width //let adSize = currentOrientationAnchoredAdaptiveBanner(width: width) //let bannerView = BannerAdView(adSize).frame(height: adSize.size.height) // let bannerView = BannerAdView() // return UIHostingController(rootView: bannerView) // } //} init() { MainViewControllerKt.IOSBanner = { let width = UIScreen.main.bounds.width // This is what your Kotlin code calls when it needs the banner let adSize = currentOrientationAnchoredAdaptiveBanner(width: width) //let bannerView = BannerAdView(adSize).frame(height: adSize.size.height) let bannerView = AdViewBanner() .frame(height: adSize.size.height) //.frame(height: 90) // Safe fallback - adaptive size will override return UIHostingController(rootView: bannerView) } } func makeUIViewController(context: Context) -> UIViewController { MainViewControllerKt.MainViewController() } func updateUIViewController(_ uiViewController: UIViewController, context: Context) { } } struct ContentView: View { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate var body: some View { ComposeView() .ignoresSafeArea() } }GoogleMobileAdsConsentManager:
import Foundation import GoogleMobileAds import UserMessagingPlatform @MainActor final class GoogleMobileAdsConsentManager: NSObject { static let shared = GoogleMobileAdsConsentManager() private var isMobileAdsStarted = false var canRequestAds: Bool { ConsentInformation.shared.canRequestAds } func gatherConsent(completion: @escaping (Error?) -> Void) async { let params = RequestParameters() let debugSettings = DebugSettings() // debugSettings.geography = .EEA // Uncomment to test consent form params.debugSettings = debugSettings do { try await ConsentInformation.shared.requestConsentInfoUpdate(with: params) try await ConsentForm.loadAndPresentIfRequired(from: nil) completion(nil) } catch { completion(error) } } func startGoogleMobileAdsSDK() { guard canRequestAds, !isMobileAdsStarted else { return } isMobileAdsStarted = true MobileAds.shared.start(completionHandler: nil) } }AdMobBanner:
// // AdMobBanner.swift // iosApp // import SwiftUI import UIKit import GoogleMobileAds import ComposeApp // Your generated KMP module // MARK: - Adaptive BannerAdView (SwiftUI wrapper) struct AdViewBanner: UIViewRepresentable { private let adUnitID = "ca-app-pub-3940256099942544/2435281174" // Test ID - change in production func makeUIView(context: Context) -> BannerView { let banner = BannerView() banner.translatesAutoresizingMaskIntoConstraints = false banner.adUnitID = adUnitID banner.delegate = context.coordinator // Required: set rootViewController so ads can present modals banner.rootViewController = UIApplication.shared.topMostViewController() // Use Google's recommended adaptive size let width = UIScreen.main.bounds.width let adaptiveSize = currentOrientationAnchoredAdaptiveBanner(width: width) banner.adSize = adaptiveSize banner.load(Request()) return banner } func updateUIView(_ uiView: BannerView, context: Context) { // Handle rotation properly let width = UIScreen.main.bounds.width let newSize = currentOrientationAnchoredAdaptiveBanner(width: width) if !isAdSizeEqualToSize(size1: uiView.adSize, size2: newSize) { uiView.adSize = newSize uiView.load(Request()) } } func makeCoordinator() -> Coordinator { Coordinator() } class Coordinator: NSObject, BannerViewDelegate { func bannerViewDidReceiveAd(_ bannerView: BannerView) { print("Banner ad loaded successfully") } func bannerView(_ bannerView: BannerView, didFailToReceiveAdWithError error: Error) { print("Banner ad failed: \(error.localizedDescription)") } } } // MARK: - UIApplication extension to get top VC extension UIApplication { func topMostViewController() -> UIViewController? { let keyWindow = connectedScenes .compactMap { $0 as? UIWindowScene } .flatMap { $0.windows } .first(where: { $0.isKeyWindow }) var top = keyWindow?.rootViewController while let presented = top?.presentedViewController { top = presented } return top ?? UIViewController() } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }iOSApp
import SwiftUI @main struct iOSApp: App { var body: some Scene { WindowGroup { ContentView() } } }AppDelegate
import SwiftUI import Foundation import GoogleMobileAds class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { Task { @MainActor in await GoogleMobileAdsConsentManager.shared.gatherConsent { error in if let error = error { print("Consent error: \(error.localizedDescription)") } GoogleMobileAdsConsentManager.shared.startGoogleMobileAdsSDK() } } return true } }Kotlin part:
lateinit var IOSBanner: () -> UIViewController fun generateIOSBanner(): UIViewController { return IOSBanner() } fun MainViewController(): UIViewController { return ComposeUIViewController { MainApp { UIKitView( modifier = Modifier, factory = { generateIOSBanner().view } ) } } }MainApp looks like:
@Composable fun MainApp(bannerContext: @Composable (String) -> Unit) { Platform.initDataStore() AppTheme(mainModel = mainModel) { var route by rememberSaveableString() val controller = rememberNavController { value -> route = value } Column( content = { Box(modifier = Modifier.weight(weight = 1f)) { MainGraph(controller = controller, mainModel = mainModel) } bannerContext.invoke(route) }, modifier = Modifier.animContentSize().background(color = background()) ) } } }I works, but not as expected. It looks like zoomed in:

I tried also to implement without adaptive size feature, which resulted a full screen ad, not like banner.
I expect that it would look like on Android (androidMain):

What is the problem and how can I fix it?
Thanks.
