How can I develop an iOS app with capacitor and AdMob?

Summary

This postmortem addresses a common failure scenario when porting a web-based React application to native iOS using Capacitor, specifically integrating AdMob. The core issue is not code logic but dependency management and build environment configuration. The frustration stems from the intersection of a web-native hybrid framework (Capacitor), a native SDK (Google Mobile Ads), and a remote development environment (MacinCloud) on a Windows host. The successful resolution required strict version alignment between Node.js dependencies, CocoaPods, and Xcode settings.

Root Cause

The primary failure was CocoaPods dependency resolution failure due to version mismatches between the Capacitor iOS platform and the AdMob plugin.

  • Podspec Version Conflicts: The installed version of the Capacitor AdMob plugin required a specific version of the Google Mobile Ads SDK (e.g., ~> 10.0) that conflicted with other installed pods or the base Capacitor iOS version.
  • Missing capacitor.config.ts Entries: In Capacitor 5+, plugin configuration requires explicit definition in the configuration file, or the plugin will not initialize correctly during the iOS build phase.
  • Xcode Build Settings: The MacinCloud environment sometimes defaults to an older Xcode version (e.g., Xcode 14) which may lack support for specific Swift versions required by the latest AdMob SDKs.
  • CocoaPods Repo Update: On cloud VMs, the local CocoaPods repository spec is often outdated, causing pod install to fetch incompatible dependency graphs.

Why This Happens in Real Systems

In hybrid mobile development, the dependency tree is multi-layered. You are managing npm packages (JavaScript) and CocoaPods (Swift/Objective-C) simultaneously.

  1. Divergent Ecosystems: The npm ecosystem updates faster than the CocoaPods ecosystem. Running npm update can break a native build if the corresponding iOS plugin isn’t updated simultaneously.
  2. Environment Isolation: On a remote Mac (MacinCloud), the filesystem is ephemeral. If you do not run pod install immediately after cloning the repo or modifying plugins, the Xcode project will reference stale or missing .framework files.
  3. Hardware Constraints: Windows users developing for iOS rely on remote build agents (MacinCloud). Network latency or restricted access to the Terminal/CLI on these VMs can interrupt the pod installation process, leaving the project in a broken state.

Real-World Impact

  • Blocked CI/CD Pipelines: A local build failure often translates to a failure in automated deployment pipelines (e.g., GitHub Actions using macOS runners), halting release cycles.
  • Developer Productivity Loss: The “Windows Tax” for iOS development doubles the troubleshooting time. Developers spend hours debugging build logs rather than iterating on app features.
  • App Store Rejection: Incorrect AdMob integration (e.g., missing privacy manifests or Info.plist entries required by Apple) leads to immediate App Store rejection.

Example or Code

There is no executable code block required for the root cause configuration. However, the following files represent the critical configuration changes needed to resolve the build errors.

1. Updating capacitor.config.ts

Ensure the AdMob plugin is explicitly defined and configured.

import { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: 'com.your.app',
  appName: 'Your App',
  webDir: 'build',
  bundledWebRuntime: false,
  plugins: {
    AdMob: {
      appId: {
        ios: 'ca-app-pub-xxxxxxxx~xxxxxxxx', // Must match your AdMob Dashboard
      },
    },
  },
};

export default config;

2. Updating ios/App/Podfile

Force CocoaPods to use the latest specs and ensure the target version is compatible with modern Swift features.

platform :ios, '14.0' # Ensure this matches your Xcode deployment target
require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers'

system('npm install --save @capacitor/admob@latest') # Ensures npm and pod versions match

target 'App' do
  capacitor_pods
  # Add your Podfile dependencies here
  pod 'Google-Mobile-Ads-SDK', '~> 10.14.0' # Pinning to a specific version helps stability
end

How Senior Engineers Fix It

  1. Environment Standardization:

    • Ensure Xcode is updated to at least version 14+ (ideally 15+).
    • Run pod repo update before any npm install or npx cap add ios command to ensure the local spec repo is current.
  2. Clean Slate Approach:

    • Delete node_modules, package-lock.json, and the ios/ folder.
    • Run npm install followed by npx cap sync ios.
    • Crucial Step: Navigate to the ios/App directory and run pod install --repo-update.
  3. Xcode Configuration:

    • Open .xcworkspace (not .xcodeproj).
    • Check Build Settings -> Swift Language Version (set to Swift 5.0+).
    • Verify Deployment Target matches the Podfile platform setting.
    • Check Signing & Capabilities to ensure the Bundle ID matches the one registered in the Apple Developer Portal.
  4. Debugging Strategy:

    • Read the first error in the Xcode build log. Subsequent errors are often cascading effects of the first one.
    • Look for “No such module ‘GoogleMobileAds'”—this confirms the CocoaPods integration is broken.

Why Juniors Miss It

  • Ignoring the Console: Junior developers often look at the Xcode UI error indicator rather than the detailed build log (Cmd+9). The log contains the specific CocoaPods resolution error.
  • Mixing Commands: Running npx cap add ios without a preceding npx cap sync or pod install leads to mismatched file references.
  • Windows Assumptions: Assuming that because the Android build works (Gradle), the iOS build should behave identically. They fail to account for the separate dependency manager (CocoaPods) required by iOS.
  • Configuration Blindness: Overlooking the capacitor.config.ts file. In Capacitor 5+, plugins like AdMob will not load unless explicitly added to this config object, leading to runtime errors that look like build errors initially.