Files
FamilyMealPlanner/MealMood/Components/AdBannerView.swift
T
2026-02-26 10:27:09 +01:00

154 lines
4.7 KiB
Swift

import SwiftUI
import UIKit
#if canImport(GoogleMobileAds)
import GoogleMobileAds
struct AdBannerView: View {
var adUnitId: String = AdMobConfig.resolvedBannerHomeUnitId
@State private var state: BannerState = .loading
var body: some View {
VStack(spacing: 0) {
Rectangle()
.fill(Color.mealMoodTextSecondary.opacity(0.22))
.frame(height: 1)
ZStack {
Color.mealMoodSurface
BannerBridge(adUnitId: adUnitId, state: $state)
if state != .loaded {
HStack(spacing: 6) {
Image(systemName: "megaphone")
Text(state == .loading ? "ads_loading" : "ads_unavailable")
.font(.mealMoodCaption)
}
.foregroundColor(.mealMoodTextSecondary)
}
}
.frame(height: 56)
.clipped()
}
.frame(maxWidth: .infinity)
.background(Color.mealMoodSurface)
}
}
private enum BannerState {
case loading
case loaded
case failed
}
private struct BannerBridge: UIViewRepresentable {
let adUnitId: String
@Binding var state: BannerState
func makeCoordinator() -> Coordinator {
Coordinator(state: $state)
}
func makeUIView(context: Context) -> UIView {
let container = UIView()
container.backgroundColor = UIColor(Color.mealMoodSurface)
container.clipsToBounds = true
let width = max(UIScreen.main.bounds.width, 320)
let adSize = GADCurrentOrientationAnchoredAdaptiveBannerAdSizeWithWidth(width)
let banner = GADBannerView(adSize: adSize)
banner.adUnitID = adUnitId
banner.delegate = context.coordinator
banner.rootViewController = rootViewController()
banner.translatesAutoresizingMaskIntoConstraints = false
banner.backgroundColor = .clear
context.coordinator.bannerView = banner
container.addSubview(banner)
NSLayoutConstraint.activate([
banner.centerXAnchor.constraint(equalTo: container.centerXAnchor),
banner.centerYAnchor.constraint(equalTo: container.centerYAnchor),
banner.widthAnchor.constraint(equalToConstant: adSize.size.width),
banner.heightAnchor.constraint(equalToConstant: adSize.size.height)
])
context.coordinator.loadBannerIfPossible()
return container
}
func updateUIView(_ uiView: UIView, context: Context) {
context.coordinator.bannerView?.rootViewController = rootViewController()
context.coordinator.loadBannerIfPossible()
}
private func rootViewController() -> UIViewController? {
UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap(\.windows)
.first(where: \.isKeyWindow)?
.rootViewController
}
final class Coordinator: NSObject, GADBannerViewDelegate {
@Binding var state: BannerState
weak var bannerView: GADBannerView?
private var didRequestLoad = false
private var retryCount = 0
init(state: Binding<BannerState>) {
self._state = state
}
func loadBannerIfPossible() {
guard let bannerView, bannerView.rootViewController != nil else { return }
guard !didRequestLoad else { return }
didRequestLoad = true
state = .loading
bannerView.load(GADRequest())
}
func bannerViewDidReceiveAd(_ bannerView: GADBannerView) {
state = .loaded
retryCount = 0
}
func bannerView(_ bannerView: GADBannerView, didFailToReceiveAdWithError error: any Error) {
state = .failed
didRequestLoad = false
if retryCount < 2 {
retryCount += 1
loadBannerIfPossible()
}
#if DEBUG
print("AdMob banner failed (\(retryCount)): \(error.localizedDescription)")
#endif
}
}
}
#else
struct AdBannerView: View {
var adUnitId: String = AdMobConfig.resolvedBannerHomeUnitId
var body: some View {
VStack(spacing: 0) {
Rectangle()
.fill(Color.mealMoodTextSecondary.opacity(0.22))
.frame(height: 1)
HStack(spacing: 6) {
Image(systemName: "megaphone")
Text("ads_unavailable")
.font(.mealMoodCaption)
}
.foregroundColor(.mealMoodTextSecondary)
.frame(maxWidth: .infinity, minHeight: 56)
.background(Color.mealMoodSurface)
}
.frame(maxWidth: .infinity)
.background(Color.mealMoodSurface)
}
}
#endif