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:
- Using a separate window (Solution 1) where the target VC is the root, so it's always in a valid hierarchy
- 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

