Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
c449af3806
commit
1a5e17bc6f
178
Agents.md
178
Agents.md
@ -419,40 +419,182 @@ If you need different formats for different purposes:
|
||||
- Name constants semantically: `accent` not `pointSix`, `large` not `sixteen`.
|
||||
|
||||
|
||||
## App Identifiers
|
||||
## App Identifiers (xcconfig)
|
||||
|
||||
**Centralize all company-specific identifiers** in a single configuration file for easy migration.
|
||||
**Centralize all company-specific identifiers** using xcconfig files for true single-source configuration. This enables one-line migration between developer accounts.
|
||||
|
||||
### Structure
|
||||
### Why xcconfig?
|
||||
|
||||
- **Single source of truth**: Change one file, everything updates
|
||||
- **Build-time resolution**: Bundle IDs, entitlements, and Swift code all derive from same source
|
||||
- **No manual updates**: Entitlements use variable substitution
|
||||
- **Environment support**: Easy Debug/Release/Staging configurations
|
||||
|
||||
### Setup Instructions
|
||||
|
||||
#### Step 1: Create xcconfig Files
|
||||
|
||||
Create `Configuration/Base.xcconfig`:
|
||||
|
||||
```
|
||||
// Base.xcconfig - Source of truth for all identifiers
|
||||
// MIGRATION: Update COMPANY_IDENTIFIER and DEVELOPMENT_TEAM below
|
||||
|
||||
// =============================================================================
|
||||
// COMPANY IDENTIFIER - CHANGE THIS FOR MIGRATION
|
||||
// =============================================================================
|
||||
|
||||
COMPANY_IDENTIFIER = com.yourcompany
|
||||
APP_NAME = YourAppName
|
||||
DEVELOPMENT_TEAM = YOUR_TEAM_ID
|
||||
|
||||
// =============================================================================
|
||||
// DERIVED IDENTIFIERS - DO NOT EDIT
|
||||
// =============================================================================
|
||||
|
||||
APP_BUNDLE_IDENTIFIER = $(COMPANY_IDENTIFIER).$(APP_NAME)
|
||||
WATCH_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).watchkitapp
|
||||
APPCLIP_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).Clip
|
||||
TESTS_BUNDLE_IDENTIFIER = $(COMPANY_IDENTIFIER).$(APP_NAME)Tests
|
||||
UITESTS_BUNDLE_IDENTIFIER = $(COMPANY_IDENTIFIER).$(APP_NAME)UITests
|
||||
|
||||
APP_GROUP_IDENTIFIER = group.$(COMPANY_IDENTIFIER).$(APP_NAME)
|
||||
CLOUDKIT_CONTAINER_IDENTIFIER = iCloud.$(COMPANY_IDENTIFIER).$(APP_NAME)
|
||||
|
||||
APPCLIP_DOMAIN = yourapp.example.com
|
||||
```
|
||||
|
||||
Create `Configuration/Debug.xcconfig`:
|
||||
|
||||
```
|
||||
// Debug.xcconfig
|
||||
#include "Base.xcconfig"
|
||||
// Add debug-specific settings here
|
||||
```
|
||||
|
||||
Create `Configuration/Release.xcconfig`:
|
||||
|
||||
```
|
||||
// Release.xcconfig
|
||||
#include "Base.xcconfig"
|
||||
// Add release-specific settings here
|
||||
```
|
||||
|
||||
#### Step 2: Configure Xcode Project
|
||||
|
||||
In `project.pbxproj`, add file references and set `baseConfigurationReference` for each build configuration:
|
||||
|
||||
1. Add xcconfig file references to PBXFileReference section
|
||||
2. Set `baseConfigurationReference` on Debug/Release configurations
|
||||
3. Replace hardcoded values with variables:
|
||||
|
||||
```
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_IDENTIFIER)";
|
||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = "$(APP_BUNDLE_IDENTIFIER)";
|
||||
```
|
||||
|
||||
#### Step 3: Update Entitlements
|
||||
|
||||
Use variable substitution in `.entitlements` files:
|
||||
|
||||
```xml
|
||||
<key>com.apple.developer.icloud-container-identifiers</key>
|
||||
<array>
|
||||
<string>$(CLOUDKIT_CONTAINER_IDENTIFIER)</string>
|
||||
</array>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>$(APP_GROUP_IDENTIFIER)</string>
|
||||
</array>
|
||||
```
|
||||
|
||||
#### Step 4: Bridge to Swift via Info.plist
|
||||
|
||||
Add keys to `Info.plist` that bridge xcconfig values to Swift:
|
||||
|
||||
```xml
|
||||
<key>AppGroupIdentifier</key>
|
||||
<string>$(APP_GROUP_IDENTIFIER)</string>
|
||||
<key>CloudKitContainerIdentifier</key>
|
||||
<string>$(CLOUDKIT_CONTAINER_IDENTIFIER)</string>
|
||||
<key>AppClipDomain</key>
|
||||
<string>$(APPCLIP_DOMAIN)</string>
|
||||
```
|
||||
|
||||
#### Step 5: Create Swift Interface
|
||||
|
||||
Create `Configuration/AppIdentifiers.swift`:
|
||||
|
||||
```swift
|
||||
enum AppIdentifiers {
|
||||
// MARK: - Company Identifier (CHANGE THIS FOR MIGRATION)
|
||||
static let companyIdentifier = "com.yourcompany"
|
||||
import Foundation
|
||||
|
||||
enum AppIdentifiers {
|
||||
// Read from Info.plist (values come from xcconfig)
|
||||
static let appGroupIdentifier: String = {
|
||||
Bundle.main.object(forInfoDictionaryKey: "AppGroupIdentifier") as? String
|
||||
?? "group.com.yourcompany.AppName"
|
||||
}()
|
||||
|
||||
static let cloudKitContainerIdentifier: String = {
|
||||
Bundle.main.object(forInfoDictionaryKey: "CloudKitContainerIdentifier") as? String
|
||||
?? "iCloud.com.yourcompany.AppName"
|
||||
}()
|
||||
|
||||
static let appClipDomain: String = {
|
||||
Bundle.main.object(forInfoDictionaryKey: "AppClipDomain") as? String
|
||||
?? "yourapp.example.com"
|
||||
}()
|
||||
|
||||
// Derived from bundle identifier
|
||||
static var bundleIdentifier: String {
|
||||
Bundle.main.bundleIdentifier ?? "com.yourcompany.AppName"
|
||||
}
|
||||
|
||||
// MARK: - Derived Identifiers
|
||||
static var bundleIdentifier: String { "\(companyIdentifier).AppName" }
|
||||
static var watchBundleIdentifier: String { "\(bundleIdentifier).watchkitapp" }
|
||||
static var appClipBundleIdentifier: String { "\(bundleIdentifier).Clip" }
|
||||
static var appGroupIdentifier: String { "group.\(companyIdentifier).AppName" }
|
||||
static var cloudKitContainerIdentifier: String { "iCloud.\(companyIdentifier).AppName" }
|
||||
|
||||
static func appClipURL(recordName: String) -> URL? {
|
||||
URL(string: "https://\(appClipDomain)/appclip?id=\(recordName)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Usage
|
||||
### Data Flow
|
||||
|
||||
- Always use `AppIdentifiers.*` instead of hardcoding bundle IDs or container names.
|
||||
- `AppIdentifiers.companyIdentifier` is the single source of truth.
|
||||
- All other identifiers derive from it automatically.
|
||||
```
|
||||
Base.xcconfig (source of truth)
|
||||
↓
|
||||
project.pbxproj (baseConfigurationReference)
|
||||
↓
|
||||
Build Settings → Bundle IDs, Team ID, etc.
|
||||
↓
|
||||
Info.plist (bridges values via $(VARIABLE))
|
||||
↓
|
||||
AppIdentifiers.swift (Swift reads from Bundle.main)
|
||||
```
|
||||
|
||||
### Usage in Code
|
||||
|
||||
```swift
|
||||
// Always use AppIdentifiers instead of hardcoding
|
||||
FileManager.default.containerURL(
|
||||
forSecurityApplicationGroupIdentifier: AppIdentifiers.appGroupIdentifier
|
||||
)
|
||||
|
||||
CKContainer(identifier: AppIdentifiers.cloudKitContainerIdentifier)
|
||||
```
|
||||
|
||||
### Migration
|
||||
|
||||
When migrating to a new developer account:
|
||||
1. Change `companyIdentifier` in `AppIdentifiers.swift`
|
||||
2. Update entitlements files manually (cannot be tokenized)
|
||||
3. Update bundle IDs in Xcode project settings
|
||||
To migrate to a new developer account, edit **one file** (`Base.xcconfig`):
|
||||
|
||||
```
|
||||
COMPANY_IDENTIFIER = com.newcompany
|
||||
DEVELOPMENT_TEAM = NEW_TEAM_ID
|
||||
```
|
||||
|
||||
Then clean build (⇧⌘K) and rebuild. Everything updates automatically.
|
||||
|
||||
|
||||
## Dynamic Type Instructions
|
||||
|
||||
@ -54,6 +54,10 @@
|
||||
EA8379302F105F2800077F87 /* BusinessCardTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BusinessCardTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
EA83793A2F105F2800077F87 /* BusinessCardUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BusinessCardUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
EA837F982F11B16400077F87 /* BusinessCardWatch Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "BusinessCardWatch Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
EACONFIG0012F200000000001 /* Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Base.xcconfig; sourceTree = "<group>"; };
|
||||
EACONFIG0012F200000000002 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
||||
EACONFIG0012F200000000003 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
||||
EACONFIG0012F200000000004 /* Watch.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Watch.xcconfig; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
@ -387,6 +391,7 @@
|
||||
/* Begin XCBuildConfiguration section */
|
||||
EA8379422F105F2800077F87 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = EACONFIG0012F200000000002 /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
@ -420,7 +425,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
@ -451,6 +456,7 @@
|
||||
};
|
||||
EA8379432F105F2800077F87 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = EACONFIG0012F200000000003 /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
@ -484,7 +490,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
@ -514,7 +520,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = BusinessCard/BusinessCard.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = BusinessCard/Info.plist;
|
||||
@ -530,7 +536,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.BusinessCard;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
@ -550,7 +556,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = BusinessCard/BusinessCard.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = BusinessCard/Info.plist;
|
||||
@ -566,7 +572,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.BusinessCard;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
@ -584,11 +590,11 @@
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.BusinessCardTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(TESTS_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
@ -606,11 +612,11 @@
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.BusinessCardTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(TESTS_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
@ -627,10 +633,10 @@
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.BusinessCardUITests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(UITESTS_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
@ -647,10 +653,10 @@
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.BusinessCardUITests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(UITESTS_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
@ -670,18 +676,18 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "BusinessCardWatch Watch App/BusinessCardWatch.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = BusinessCardWatch;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.mbrucedogs.BusinessCard;
|
||||
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = "$(APP_BUNDLE_IDENTIFIER)";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.BusinessCard.watchkitapp;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(WATCH_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
@ -704,18 +710,18 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "BusinessCardWatch Watch App/BusinessCardWatch.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = BusinessCardWatch;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.mbrucedogs.BusinessCard;
|
||||
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = "$(APP_BUNDLE_IDENTIFIER)";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.BusinessCard.watchkitapp;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(WATCH_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
<key>BusinessCardWatch Watch App.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
<integer>2</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.icloud-container-identifiers</key>
|
||||
<array>
|
||||
<string>iCloud.com.mbrucedogs.BusinessCard</string>
|
||||
<string>$(CLOUDKIT_CONTAINER_IDENTIFIER)</string>
|
||||
</array>
|
||||
<key>com.apple.developer.icloud-services</key>
|
||||
<array>
|
||||
@ -14,7 +14,7 @@
|
||||
</array>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.mbrucedogs.BusinessCard</string>
|
||||
<string>$(APP_GROUP_IDENTIFIER)</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -1,31 +1,56 @@
|
||||
import Foundation
|
||||
|
||||
/// Centralized app identifiers for easy migration between developer accounts.
|
||||
/// Centralized app identifiers that read from Info.plist (which gets values from xcconfig).
|
||||
///
|
||||
/// The source of truth is `Configuration/Base.xcconfig`. Values flow:
|
||||
/// Base.xcconfig → Info.plist → AppIdentifiers (Swift)
|
||||
///
|
||||
/// When migrating to a new developer account:
|
||||
/// 1. Update `companyIdentifier` below
|
||||
/// 2. Update entitlements files manually (cannot be tokenized)
|
||||
/// 3. Update bundle IDs in Xcode project settings
|
||||
/// 4. See DevAccount-Migration.md for full checklist
|
||||
/// 1. Update `COMPANY_IDENTIFIER` and `DEVELOPMENT_TEAM` in Base.xcconfig
|
||||
/// 2. The entitlements, bundle IDs, and Swift code all update automatically
|
||||
/// 3. See DevAccount-Migration.md for complete checklist
|
||||
enum AppIdentifiers {
|
||||
|
||||
// MARK: - Company Identifier (CHANGE THIS FOR MIGRATION)
|
||||
// MARK: - Runtime Identifiers (read from Info.plist)
|
||||
|
||||
/// The company's reverse domain identifier.
|
||||
static let companyIdentifier = "com.mbrucedogs"
|
||||
/// App Group identifier for sharing data between app and extensions.
|
||||
static let appGroupIdentifier: String = {
|
||||
Bundle.main.object(forInfoDictionaryKey: "AppGroupIdentifier") as? String
|
||||
?? "group.com.mbrucedogs.BusinessCard"
|
||||
}()
|
||||
|
||||
/// CloudKit container identifier.
|
||||
static let cloudKitContainerIdentifier: String = {
|
||||
Bundle.main.object(forInfoDictionaryKey: "CloudKitContainerIdentifier") as? String
|
||||
?? "iCloud.com.mbrucedogs.BusinessCard"
|
||||
}()
|
||||
|
||||
/// App Clip domain for sharing URLs.
|
||||
static let appClipDomain: String = {
|
||||
Bundle.main.object(forInfoDictionaryKey: "AppClipDomain") as? String
|
||||
?? "cards.example.com"
|
||||
}()
|
||||
|
||||
// MARK: - Derived Identifiers
|
||||
|
||||
static var bundleIdentifier: String { "\(companyIdentifier).BusinessCard" }
|
||||
static var watchBundleIdentifier: String { "\(bundleIdentifier).watchkitapp" }
|
||||
static var appClipBundleIdentifier: String { "\(bundleIdentifier).Clip" }
|
||||
static var appGroupIdentifier: String { "group.\(companyIdentifier).BusinessCard" }
|
||||
static var cloudKitContainerIdentifier: String { "iCloud.\(companyIdentifier).BusinessCard" }
|
||||
/// Bundle identifier of the main app.
|
||||
static var bundleIdentifier: String {
|
||||
Bundle.main.bundleIdentifier ?? "com.mbrucedogs.BusinessCard"
|
||||
}
|
||||
|
||||
// MARK: - App Clip Configuration
|
||||
/// Watch app bundle identifier (derived from main app).
|
||||
static var watchBundleIdentifier: String {
|
||||
"\(bundleIdentifier).watchkitapp"
|
||||
}
|
||||
|
||||
static let appClipDomain = "cards.example.com"
|
||||
/// App Clip bundle identifier (derived from main app).
|
||||
static var appClipBundleIdentifier: String {
|
||||
"\(bundleIdentifier).Clip"
|
||||
}
|
||||
|
||||
// MARK: - App Clip URL Generation
|
||||
|
||||
/// Generates an App Clip invocation URL for a shared card record.
|
||||
static func appClipURL(recordName: String) -> URL? {
|
||||
URL(string: "https://\(appClipDomain)/appclip?id=\(recordName)")
|
||||
}
|
||||
|
||||
34
BusinessCard/Configuration/Base.xcconfig
Normal file
34
BusinessCard/Configuration/Base.xcconfig
Normal file
@ -0,0 +1,34 @@
|
||||
// Base.xcconfig
|
||||
// Shared identifiers for all targets and configurations
|
||||
//
|
||||
// MIGRATION: To change developer accounts, update COMPANY_IDENTIFIER below
|
||||
// and the DEVELOPMENT_TEAM. Then follow DevAccount-Migration.md checklist.
|
||||
|
||||
// =============================================================================
|
||||
// COMPANY IDENTIFIER - CHANGE THIS FOR MIGRATION
|
||||
// =============================================================================
|
||||
|
||||
COMPANY_IDENTIFIER = com.mbrucedogs
|
||||
APP_NAME = BusinessCard
|
||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ
|
||||
|
||||
// =============================================================================
|
||||
// DERIVED IDENTIFIERS - DO NOT EDIT (computed from above)
|
||||
// =============================================================================
|
||||
|
||||
// Bundle identifiers
|
||||
APP_BUNDLE_IDENTIFIER = $(COMPANY_IDENTIFIER).$(APP_NAME)
|
||||
WATCH_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).watchkitapp
|
||||
APPCLIP_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).Clip
|
||||
TESTS_BUNDLE_IDENTIFIER = $(COMPANY_IDENTIFIER).$(APP_NAME)Tests
|
||||
UITESTS_BUNDLE_IDENTIFIER = $(COMPANY_IDENTIFIER).$(APP_NAME)UITests
|
||||
|
||||
// Entitlement identifiers
|
||||
APP_GROUP_IDENTIFIER = group.$(COMPANY_IDENTIFIER).$(APP_NAME)
|
||||
CLOUDKIT_CONTAINER_IDENTIFIER = iCloud.$(COMPANY_IDENTIFIER).$(APP_NAME)
|
||||
|
||||
// =============================================================================
|
||||
// APP CLIP CONFIGURATION
|
||||
// =============================================================================
|
||||
|
||||
APPCLIP_DOMAIN = cards.example.com
|
||||
7
BusinessCard/Configuration/Debug.xcconfig
Normal file
7
BusinessCard/Configuration/Debug.xcconfig
Normal file
@ -0,0 +1,7 @@
|
||||
// Debug.xcconfig
|
||||
// Debug configuration settings
|
||||
|
||||
#include "Base.xcconfig"
|
||||
|
||||
// Debug-specific settings (if any)
|
||||
// Add environment-specific API keys, endpoints, etc. here
|
||||
7
BusinessCard/Configuration/Release.xcconfig
Normal file
7
BusinessCard/Configuration/Release.xcconfig
Normal file
@ -0,0 +1,7 @@
|
||||
// Release.xcconfig
|
||||
// Release configuration settings
|
||||
|
||||
#include "Base.xcconfig"
|
||||
|
||||
// Release-specific settings (if any)
|
||||
// Add production API keys, endpoints, etc. here
|
||||
@ -6,5 +6,11 @@
|
||||
<array>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>AppGroupIdentifier</key>
|
||||
<string>$(APP_GROUP_IDENTIFIER)</string>
|
||||
<key>CloudKitContainerIdentifier</key>
|
||||
<string>$(CLOUDKIT_CONTAINER_IDENTIFIER)</string>
|
||||
<key>AppClipDomain</key>
|
||||
<string>$(APPCLIP_DOMAIN)</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
7
BusinessCardWatch Watch App/Configuration/Watch.xcconfig
Normal file
7
BusinessCardWatch Watch App/Configuration/Watch.xcconfig
Normal file
@ -0,0 +1,7 @@
|
||||
// Watch.xcconfig
|
||||
// Watch app configuration - inherits from Base
|
||||
|
||||
#include "../BusinessCard/Configuration/Base.xcconfig"
|
||||
|
||||
// Watch-specific derived identifiers
|
||||
PRODUCT_BUNDLE_IDENTIFIER = $(WATCH_BUNDLE_IDENTIFIER)
|
||||
@ -2,6 +2,22 @@
|
||||
|
||||
This document provides a comprehensive guide for migrating the BusinessCard app from the personal `mbrucedogs` developer account to a new Apple Developer account.
|
||||
|
||||
## Quick Migration (xcconfig)
|
||||
|
||||
With the xcconfig setup, migration is now a **single file change**:
|
||||
|
||||
1. Open `BusinessCard/Configuration/Base.xcconfig`
|
||||
2. Update these two lines:
|
||||
```
|
||||
COMPANY_IDENTIFIER = com.newcompany
|
||||
DEVELOPMENT_TEAM = NEW_TEAM_ID
|
||||
```
|
||||
3. Clean build (⇧⌘K) and rebuild
|
||||
|
||||
That's it! All bundle IDs, entitlements, and Swift code will automatically use the new values.
|
||||
|
||||
---
|
||||
|
||||
## Current State Analysis
|
||||
|
||||
### Account Information
|
||||
|
||||
23
README.md
23
README.md
@ -148,7 +148,10 @@ BusinessCard/
|
||||
├── Assets.xcassets/
|
||||
│ └── SocialSymbols/ # Custom brand icons (LinkedIn, X, Instagram, etc.)
|
||||
├── Configuration/ # App identifiers and configuration
|
||||
│ └── AppIdentifiers.swift # Centralized company identifiers
|
||||
│ ├── Base.xcconfig # Source of truth for all identifiers
|
||||
│ ├── Debug.xcconfig # Debug configuration (imports Base)
|
||||
│ ├── Release.xcconfig # Release configuration (imports Base)
|
||||
│ └── AppIdentifiers.swift # Swift interface to configuration
|
||||
├── Design/ # Design constants (extends Bedrock)
|
||||
├── Localization/ # String helpers
|
||||
├── Models/
|
||||
@ -199,15 +202,21 @@ Without this, the iOS app installs but the watch app does NOT install on the pai
|
||||
|
||||
`iCloud.com.mbrucedogs.BusinessCard`
|
||||
|
||||
### App Identifiers
|
||||
### App Identifiers (xcconfig)
|
||||
|
||||
All company-specific identifiers are centralized in `Configuration/AppIdentifiers.swift`:
|
||||
All company-specific identifiers are centralized in `Configuration/Base.xcconfig`:
|
||||
|
||||
- Bundle IDs: `AppIdentifiers.bundleIdentifier`, `.watchBundleIdentifier`, `.appClipBundleIdentifier`
|
||||
- CloudKit: `AppIdentifiers.cloudKitContainerIdentifier`
|
||||
- App Group: `AppIdentifiers.appGroupIdentifier`
|
||||
```
|
||||
COMPANY_IDENTIFIER = com.mbrucedogs
|
||||
DEVELOPMENT_TEAM = 6R7KLBPBLZ
|
||||
```
|
||||
|
||||
See `DevAccount-Migration.md` for migration instructions.
|
||||
This flows through:
|
||||
- **Build settings**: Bundle IDs, Team ID (via xcconfig → project)
|
||||
- **Entitlements**: CloudKit container, App Group (via variable substitution)
|
||||
- **Swift code**: `AppIdentifiers.*` (via Info.plist → Bundle.main)
|
||||
|
||||
**To migrate to a new developer account**: Update `Base.xcconfig` only. See `DevAccount-Migration.md`.
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@ -55,14 +55,27 @@ App-specific extensions are in `Design/DesignConstants.swift`:
|
||||
|
||||
## Important Files
|
||||
|
||||
### Configuration
|
||||
### Configuration (xcconfig)
|
||||
|
||||
- `Configuration/AppIdentifiers.swift` — Centralized company identifiers:
|
||||
- `companyIdentifier` — Base identifier (e.g., "com.mbrucedogs")
|
||||
- Derived: `bundleIdentifier`, `watchBundleIdentifier`, `appClipBundleIdentifier`
|
||||
- Derived: `appGroupIdentifier`, `cloudKitContainerIdentifier`
|
||||
- App Clip: `appClipDomain`, `appClipURL(recordName:)`
|
||||
- **Migration**: Change `companyIdentifier` + update entitlements. See `DevAccount-Migration.md`.
|
||||
Company identifiers are centralized using xcconfig files for true single-source configuration:
|
||||
|
||||
- `Configuration/Base.xcconfig` — Source of truth for all identifiers:
|
||||
- `COMPANY_IDENTIFIER` — Base identifier (e.g., "com.mbrucedogs")
|
||||
- `DEVELOPMENT_TEAM` — Apple Developer Team ID
|
||||
- Derived: `APP_BUNDLE_IDENTIFIER`, `WATCH_BUNDLE_IDENTIFIER`, `TESTS_BUNDLE_IDENTIFIER`, etc.
|
||||
- Entitlements: `APP_GROUP_IDENTIFIER`, `CLOUDKIT_CONTAINER_IDENTIFIER`
|
||||
|
||||
- `Configuration/Debug.xcconfig` — Imports Base, adds debug-specific settings
|
||||
- `Configuration/Release.xcconfig` — Imports Base, adds release-specific settings
|
||||
|
||||
- `Configuration/AppIdentifiers.swift` — Swift interface reading from Info.plist:
|
||||
- `appGroupIdentifier`, `cloudKitContainerIdentifier`, `appClipDomain`
|
||||
- `bundleIdentifier`, `watchBundleIdentifier`, `appClipBundleIdentifier`
|
||||
- `appClipURL(recordName:)` — Generates App Clip invocation URLs
|
||||
|
||||
**Data flow**: `Base.xcconfig` → `project.pbxproj` → `Info.plist` → `AppIdentifiers.swift`
|
||||
|
||||
**Migration**: Change `COMPANY_IDENTIFIER` and `DEVELOPMENT_TEAM` in `Base.xcconfig`. Everything else updates automatically. See `DevAccount-Migration.md`.
|
||||
|
||||
### Models
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user