You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中@State变量更新后AdMob Banner视图未刷新问题

问题核心原因

UIViewControllerRepresentable的makeUIViewController方法仅会在视图首次创建时执行一次,原代码中updateUIViewController为空实现,所以绑定的广告ID更新后,不会同步修改GADBannerView的adUnitID属性,也不会重新触发广告请求。另外原代码中Coordinator直接强持有BannerViewController实例存在循环引用的内存泄漏风险,初始状态下用无效的占位字符串加载广告也会导致首次请求直接失败。

具体修改方案
  1. 重构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()
        }
    }
}
  1. 对应修改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
            }
        }
    }
}
  1. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 06:03:13