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

iOS如何在屏幕层级特定位置呈现UIViewController以符合AdMob政策?

Hey there, let's tackle this AdMob policy + UI hierarchy issue you're facing! I've dealt with similar AdMob compliance hoops before, so let's break down the problem and fix it step by step.

Core Problem Recap

  • AdMob requires the next screen to be fully ready and visible immediately after an interstitial ad closes, but your current flow only loads the screen after the ad dismisses
  • Trying to pre-load the screen early throws the classic Warning: Attempt to present on whose view is not in the window hierarchy! error
  • Your test code's attempt to add a VC directly to the root's child controllers doesn't work because you're not integrating it into the view hierarchy properly

Solution 1: Pre-Load with a Hidden Window (Best for Full-Screen Flows)

This approach creates a second UIWindow to hold your target screen, keeps it hidden while the ad is showing, then switches to it instantly when the ad closes. This perfectly meets AdMob's requirement of having the next screen ready to go.

Example Implementation

// Add this property to your ad-handling class (e.g., ViewController3)
private var targetWindow: UIWindow?

// Call this BEFORE showing the interstitial ad
func prepareTargetScreen() {
    let vc2 = ViewController2()
    let vc2Nav = UINavigationController(rootViewController: vc2)
    
    // Initialize the hidden window
    targetWindow = UIWindow(frame: UIScreen.main.bounds)
    targetWindow?.rootViewController = vc2Nav
    targetWindow?.isHidden = true
    
    // For iOS 13+, attach to the active window scene
    if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
        targetWindow?.windowScene = scene
    }
    
    // Force the view to load immediately so it's fully ready
    vc2Nav.loadViewIfNeeded()
}

// AdMob dismiss callback
func interstitialDidDismissScreen(_ ad: GADInterstitial) {
    // Switch to the pre-loaded window instantly
    targetWindow?.isHidden = false
    targetWindow?.makeKeyAndVisible()
    
    // Optional: Hide the original window if needed
    UIApplication.shared.keyWindow?.isHidden = true
}

Solution 2: Container VC Pre-Load (Best for In-Navigation Flows)

If you need to keep the existing view hierarchy intact (e.g., staying within a navigation stack), use a container view to pre-load your target screen in the background, then reveal it once the ad closes.

Modified ViewController3 Code

class ViewController3: UIViewController {
    private var containerView: UIView!
    private var preloadedVC: ViewController2?

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .blue
        
        // Setup container view to hold the pre-loaded screen
        containerView = UIView(frame: self.view.bounds)
        containerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        self.view.addSubview(containerView)

        let preloadBtn = UIBarButtonItem(title: "Preload VC2", style: .plain, target: self, action: #selector(preloadTargetVC))
        self.navigationItem.leftBarButtonItem = preloadBtn
        
        let showBtn = UIBarButtonItem(title: "Show VC2 (Post-Ad)", style: .plain, target: self, action: #selector(revealPreloadedVC))
        self.navigationItem.rightBarButtonItem = showBtn
    }

    @objc func preloadTargetVC() {
        // Pre-load the target VC and its view (no visible UI yet)
        preloadedVC = ViewController2()
        preloadedVC?.loadViewIfNeeded() // Ensures all subviews are ready
    }

    @objc func revealPreloadedVC() {
        guard let targetVC = preloadedVC else { return }
        
        // Add the pre-loaded VC to the container view
        addChild(targetVC)
        targetVC.view.frame = containerView.bounds
        containerView.addSubview(targetVC.view)
        targetVC.didMove(toParent: self)
        
        // Optional: Smooth fade-in animation
        targetVC.view.alpha = 0
        UIView.animate(withDuration: 0.25) {
            targetVC.view.alpha = 1
        }
    }
}

Fixing the "View Not in Window Hierarchy" Warning

This error happens because you're trying to present a VC from a controller whose view isn't attached to the window. The solutions above avoid this by:

  1. Using a separate window (Solution 1) where the target VC is the root, so it's always in a valid hierarchy
  2. Pre-loading the VC without presenting it, then adding it directly to a container view (Solution 2)

Quick Fix for Your Test Code

If you just want to make your existing addVc2 method work (though it won't solve the AdMob pre-load requirement), you need to properly integrate the VC into the view hierarchy:

@objc func addVc2() {
    let vc2 = ViewController2()
    let vc2navcon = UINavigationController(rootViewController: vc2)
    
    // Correct way to add a child VC
    self.addChild(vc2navcon)
    vc2navcon.view.frame = self.view.bounds
    self.view.addSubview(vc2navcon.view)
    vc2navcon.didMove(toParent: self)
}

内容的提问来源于stack exchange,提问作者Glenn Posadas

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 09:44:51