154 lines
4.7 KiB
Swift
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
|