SwiftUI中@State变量更新后AdMob Banner视图未刷新问题
问题核心原因
UIViewControllerRepresentable的makeUIViewController方法仅会在视图首次创建时执行一次,原代码中updateUIViewController为空实现,所以绑定的广告ID更新后,不会同步修改GADBannerView的adUnitID属性,也不会重新触发广告请求。另外原代码中Coordinator直接强持有BannerViewController实例存在循环引用的内存泄漏风险,初始状态下用无效的占位字符串加载广告也会导致首次请求直接失败。
具体修改方案
- 重构
BannerViewController,将GADBannerView的实例存在Coordinator中,在updateUIViewController方法内监听广告ID变化,更新ID后重新发起广告请求,同时修正循环引用问题:
final class BannerViewController: UIViewControllerRepresentable { @Binding var adUnitID: String let adSize: GADAdSize let onAdLoadSuccess: () -> Void let onAdLoadFailed: () -> Void init(adUnitID: Binding<String>, adSize: GADAdSize, onAdLoadSuccess: @escaping () -> Void, onAdLoadFailed: @escaping () -> Void) { self._adUnitID = adUnitID self.adSize = adSize self.onAdLoadSuccess = onAdLoadSuccess self.onAdLoadFailed = onAdLoadFailed } func makeCoordinator() -> Coordinator { Coordinator(onSuccess: onAdLoadSuccess, onFailed: onAdLoadFailed) } func makeUIViewController(context: Context) -> UIViewController { let viewController = UIViewController() let bannerView = GADBannerView(adSize: adSize) bannerView.delegate = context.coordinator bannerView.rootViewController = viewController context.coordinator.bannerView = bannerView viewController.view.addSubview(bannerView) viewController.view.frame = CGRect(origin: .zero, size: adSize.size) return viewController } func updateUIViewController(_ uiViewController: UIViewController, context: Context) { guard let bannerView = context.coordinator.bannerView else { return } // 广告ID变更且为有效值时重新加载 if bannerView.adUnitID != adUnitID, !adUnitID.isEmpty { bannerView.adUnitID = adUnitID bannerView.load(GADRequest()) } } class Coordinator: NSObject, GADBannerViewDelegate { let onSuccess: () -> Void let onFailed: () -> Void var bannerView: GADBannerView? init(onSuccess: @escaping () -> Void, onFailed: @escaping () -> Void) { self.onSuccess = onSuccess self.onFailed = onFailed } func adView(_ bannerView: GADBannerView, didFailToReceiveAdWithError error: Error) { onFailed() } func adViewDidReceiveAd(_ bannerView: GADBannerView) { onSuccess() } } }
- 对应修改
BannerAdView,传入状态回调,监听广告ID变化重置加载状态:
struct BannerAdView: View { @Binding var adUnit: String let adFormat: AdFormat @State var adStatus: AdStatus = .loading var body: some View { HStack { if adStatus != .failure { ZStack{ Text("Ad") .font(.title) .opacity(0.5) BannerViewController( adUnitID: $adUnit, adSize: adFormat.adSize, onAdLoadSuccess: { adStatus = .success }, onAdLoadFailed: { adStatus = .failure } ) .frame(width: adFormat.size.width, height: adFormat.size.height) } .frame(width: adFormat.size.width, height: adFormat.size.height) } } .frame(maxWidth: .infinity) .background(Color(.secondarySystemFill)) .onChange(of: adUnit) { newValue in if !newValue.isEmpty { adStatus = .loading } } } }
- 修改ContentView中广告ID的初始值,去掉不必要的人为延迟:
struct ContentView: View { // 初始值设为空,避免用无效占位字符串触发无效请求 @State var theStringAd: String = "" var body: some View { NavigationView{ ScrollView{ VStack{ make_ad_view(coolString: $theStringAd) Text("The String: \(theStringAd)") } } }.onAppear{ loadAd_Chart_() } } func loadAd_Chart_() { Firestore.firestore().collection("chartData").document("vqzVQOEzsbbOW1j11Oc6").getDocument { (docs, error) in // Firestore回调默认在主线程,不需要额外加2秒asyncAfter延迟 if let error = error { print("Firestore fetch error: \(error)") return } if let documentSnapshot = docs, documentSnapshot.exists { print("loading") let ad_one_database = documentSnapshot.get("ad_one") as? String ?? "" theStringAd = ad_one_database print(theStringAd) } } } }
额外说明
- 原代码中单独封装的
make_ad_view不需要修改,只要传入的绑定值正确即可正常工作,也可以直接在VStack中调用BannerAdView减少冗余视图层级。 - 广告加载失败后如果需要重试,可以给BannerAdView加重试按钮,点击后重置adStatus为loading,重新调用
bannerView.load(GADRequest())即可。
内容的提问来源于stack exchange,提问作者Joe
相关产品推荐
相关产品推荐

