Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>

This commit is contained in:
Matt Bruce 2026-02-03 17:27:39 -06:00
parent ac35666208
commit 4537a40cea

View File

@ -8,6 +8,9 @@ A comprehensive, reusable guide for integrating RevenueCat into iOS apps with Sw
2. [Prerequisites](#prerequisites)
3. [SDK Installation](#sdk-installation)
4. [Configuration Architecture](#configuration-architecture)
- [Option A: Swift Secrets File](#option-a-swift-secrets-file-simple)
- [Option B: Separate Debug/Release Keys](#option-b-separate-debugrelease-keys-swift-files)
- [Option C: xcconfig + Info.plist (Recommended)](#option-c-xcconfig--infoplist-recommended-for-xcode-15-projects)
5. [PremiumManager Implementation](#premiummanager-implementation)
6. [Paywall Implementation](#paywall-implementation)
7. [Event Handling](#event-handling)
@ -123,7 +126,7 @@ Add to `.gitignore`:
**/Secrets.swift
```
#### Option B: Separate Debug/Release Keys (Recommended for Production)
#### Option B: Separate Debug/Release Keys (Swift Files)
Create two secrets files:
@ -143,6 +146,228 @@ enum Secrets {
Use build configurations or compiler flags to include the correct file.
#### Option C: xcconfig + Info.plist (Recommended for Xcode 15+ Projects)
This approach is the most robust for modern Xcode projects, especially those using `fileSystemSynchronizedGroups` (automatic file sync). It keeps secrets in xcconfig files and injects them into Info.plist at build time.
**Why use this approach:**
- Secrets stay in gitignored xcconfig files
- Build settings automatically switch between debug/release keys
- Works with Xcode's auto-generated Info.plist replacement
- No Swift code changes needed to switch keys
##### Step 1: Create xcconfig Structure
Create the following files in `YourApp/Configuration/`:
**`Base.xcconfig`** (committed to git):
```
// Base.xcconfig - Source of truth for all identifiers
COMPANY_IDENTIFIER = com.yourcompany
BUNDLE_ID_NAME = YourApp
PRODUCT_NAME = Your App
DEVELOPMENT_TEAM = YOUR_TEAM_ID
APP_BUNDLE_IDENTIFIER = $(COMPANY_IDENTIFIER).$(BUNDLE_ID_NAME)
```
**`Debug.xcconfig`** (committed to git):
```
// Debug.xcconfig
#include "Base.xcconfig"
#include "Secrets.debug.xcconfig"
```
**`Release.xcconfig`** (committed to git):
```
// Release.xcconfig
#include "Base.xcconfig"
#include "Secrets.release.xcconfig"
```
**`Secrets.debug.xcconfig`** (gitignored):
```
// Secrets.debug.xcconfig
// ⚠️ DO NOT COMMIT THIS FILE
// RevenueCat Test API Key (starts with "test_")
REVENUECAT_API_KEY = test_your_test_key_here
```
**`Secrets.release.xcconfig`** (gitignored):
```
// Secrets.release.xcconfig
// ⚠️ DO NOT COMMIT THIS FILE
// RevenueCat Production API Key (starts with "appl_")
REVENUECAT_API_KEY = appl_your_production_key_here
```
Add to `.gitignore`:
```
**/Secrets.debug.xcconfig
**/Secrets.release.xcconfig
```
##### Step 2: Create Info.plist at Project Root
**CRITICAL:** For projects using `fileSystemSynchronizedGroups`, the Info.plist MUST be placed at the **project root** (same level as the `.xcodeproj`), NOT inside the synced app folder. Otherwise Xcode will automatically add it to Copy Bundle Resources and cause build failures.
Create `Info.plist` at the project root (e.g., `YourApp/Info.plist`, NOT `YourApp/YourApp/Info.plist`):
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>CFBundleDisplayName</key>
<string>$(PRODUCT_NAME)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict/>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<!-- Add your usage descriptions -->
<key>NSCameraUsageDescription</key>
<string>Your camera usage description</string>
<!-- RevenueCat API Key - injected from xcconfig -->
<key>RevenueCatAPIKey</key>
<string>$(REVENUECAT_API_KEY)</string>
</dict>
</plist>
```
##### Step 3: Configure Xcode Project
1. **Set Build Configurations to use xcconfig files:**
- Project → Info → Configurations
- Set Debug to use `Debug.xcconfig`
- Set Release to use `Release.xcconfig`
2. **Update Build Settings for the app target:**
- Set `GENERATE_INFOPLIST_FILE = NO`
- Set `INFOPLIST_FILE = Info.plist` (path relative to project root)
##### Step 4: Update PremiumManager to Read from Info.plist
```swift
private static var apiKey: String {
let key = (Bundle.main.object(forInfoDictionaryKey: "RevenueCatAPIKey") as? String) ?? ""
let placeholders = [
"",
"YOUR_REVENUECAT_API_KEY_HERE",
"test_YOUR_TEST_KEY_HERE",
"appl_YOUR_PRODUCTION_KEY_HERE"
]
#if DEBUG
let prefix = key.split(separator: "_").first.map(String.init) ?? "missing"
let keyStatus = key.isEmpty ? "empty" : "present"
print(" [PremiumManager] RevenueCatAPIKey \(keyStatus), prefix=\(prefix)")
#endif
guard !placeholders.contains(key) else {
#if DEBUG
print("⚠️ [PremiumManager] RevenueCat API key not configured. Check Secrets.debug.xcconfig / Secrets.release.xcconfig")
#endif
return ""
}
return key
}
```
##### Common Pitfalls
**1. INFOPLIST_KEY_ prefix doesn't work for custom keys**
The `INFOPLIST_KEY_` build setting prefix (e.g., `INFOPLIST_KEY_RevenueCatAPIKey`) only works for Apple's predefined Info.plist keys. Custom keys like `RevenueCatAPIKey` will NOT be added to the generated Info.plist.
❌ **Does NOT work:**
```
// In xcconfig - custom keys are ignored
INFOPLIST_KEY_RevenueCatAPIKey = $(REVENUECAT_API_KEY)
```
**Works:** Use a custom Info.plist with build setting substitution:
```xml
<key>RevenueCatAPIKey</key>
<string>$(REVENUECAT_API_KEY)</string>
```
**2. Info.plist in synced folder causes "Multiple commands produce Info.plist"**
If your project uses `fileSystemSynchronizedGroups` (Xcode 15+ default for new projects), placing Info.plist inside the app folder causes Xcode to automatically add it to Copy Bundle Resources, resulting in a build error.
**Causes error:** `YourApp/YourApp/Info.plist`
**Correct location:** `YourApp/Info.plist` (at project root, outside the synced folder)
**3. Wrong xcconfig include path**
The `#include` path in xcconfig files is relative to the xcconfig file itself.
❌ **Wrong (if files are in same directory):**
```
#include "YourApp/Configuration/Secrets.debug.xcconfig"
```
✅ **Correct:**
```
#include "Secrets.debug.xcconfig"
```
**4. Verify the key is in the built app**
After building, verify the key was properly substituted:
```bash
plutil -p ~/Library/Developer/Xcode/DerivedData/YourApp-*/Build/Products/Debug-iphonesimulator/Your\ App.app/Info.plist | grep RevenueCat
```
Expected output:
```
"RevenueCatAPIKey" => "test_your_actual_key"
```
---
## PremiumManager Implementation
@ -970,6 +1195,66 @@ Use the test API key (`test_`):
2. Check bundle ID matches exactly
3. Wait 1-2 minutes for transactions to appear
### API Key Shows in Build Settings But Not in App (xcconfig)
**Symptom:** `xcodebuild -showBuildSettings` shows `REVENUECAT_API_KEY = test_...` but the app logs show "RevenueCatAPIKey empty".
**Cause:** The `INFOPLIST_KEY_` prefix only works for Apple's predefined keys, not custom keys.
**Solution:**
1. Create a custom `Info.plist` file at the project root (NOT inside the synced app folder)
2. Add the key with build setting substitution: `<string>$(REVENUECAT_API_KEY)</string>`
3. Set `GENERATE_INFOPLIST_FILE = NO` in build settings
4. Set `INFOPLIST_FILE = Info.plist`
See [Option C: xcconfig + Info.plist](#option-c-xcconfig--infoplist-recommended-for-xcode-15-projects) for complete instructions.
### "Multiple commands produce Info.plist" Build Error
**Cause:** The Info.plist file is inside a `fileSystemSynchronizedGroups` folder (Xcode 15+ default), causing Xcode to automatically add it to Copy Bundle Resources.
**Solution:** Move Info.plist to the project root directory (same level as `.xcodeproj`), NOT inside the app source folder.
```
YourProject/
├── YourProject.xcodeproj
├── Info.plist ← Correct location
└── YourProject/
└── (source files) ← NOT here
```
### xcconfig Include Path Errors
**Symptom:** Build settings show empty or placeholder values even though the Secrets xcconfig files exist.
**Cause:** The `#include` path is relative to the xcconfig file itself, not the project root.
**Fix:** If `Debug.xcconfig` and `Secrets.debug.xcconfig` are in the same directory:
```
// Wrong
#include "YourApp/Configuration/Secrets.debug.xcconfig"
// Correct
#include "Secrets.debug.xcconfig"
```
### Verifying xcconfig Setup
Run these commands to debug your xcconfig setup:
```bash
# Check if REVENUECAT_API_KEY is in build settings
xcodebuild -showBuildSettings -scheme "YourScheme" -configuration Debug | grep REVENUECAT
# Check if key is in the built Info.plist
plutil -p ~/Library/Developer/Xcode/DerivedData/YourApp-*/Build/Products/Debug-iphonesimulator/Your\ App.app/Info.plist | grep -i revenue
# Clean and rebuild after xcconfig changes
rm -rf ~/Library/Developer/Xcode/DerivedData/YourApp-*
xcodebuild clean build -scheme "YourScheme" -configuration Debug -destination "platform=iOS Simulator,name=iPhone 16 Pro"
```
---
## Resources