A lightweight Swift SDK for cross-promoting your iOS apps within your app portfolio.
CrossPromoKit enables seamless cross-promotion between your iOS apps using a remote JSON catalog. It provides a ready-to-use SwiftUI view that displays your other apps with native App Store integration via SKOverlay.
- SwiftUI Native: Drop-in
MoreAppsViewcomponent for your settings screen - SKOverlay Integration: In-app App Store overlay for frictionless discovery
- Remote Configuration: JSON-based app catalog hosted anywhere (GitHub, CDN, etc.)
- Three-Tier Fallback: Network β Cache β Empty State for reliability
- Analytics Ready: Delegate-based event tracking for impressions and taps
- Promo Rules: Control which apps promote which with customizable rules
- Localization: Built-in support for localized taglines
- Swift 6.0: Full strict concurrency compliance with Sendable types
A demo app is included in the Example/CrossPromoDemo directory to showcase all features.
To run the demo:
- Open
Example/CrossPromoDemo/CrossPromoDemo.xcodeproj - Select a simulator and run
The demo includes:
- Live preview of MoreAppsView with sample apps
- UI state controls (loaded, loading, empty, error)
- Event logging for impressions and taps
- iOS 17.0+
- Swift 6.0+
- Xcode 16.0+
Add CrossPromoKit to your project via SPM:
dependencies: [
.package(url: "https://github.com/user/CrossPromoKit.git", from: "1.0.0")
]Or in Xcode: File β Add Package Dependencies β Enter the repository URL.
Create a JSON file and host it (e.g., GitHub raw URL):
{
"apps": [
{
"id": "myapp1",
"name": "My App 1",
"appStoreID": "123456789",
"iconURL": "https://example.com/icon1.png",
"category": "Productivity",
"tagline": {
"en": "Your productivity companion",
"ko": "λΉμ μ μμ°μ± λλ°μ"
}
},
{
"id": "myapp2",
"name": "My App 2",
"appStoreID": "987654321",
"iconURL": "https://example.com/icon2.png",
"category": "Finance",
"tagline": {
"en": "Manage your finances",
"ko": "μ¬μ μ κ΄λ¦¬νμΈμ"
}
}
]
}import SwiftUI
import CrossPromoKit
struct SettingsView: View {
var body: some View {
List {
// Your other settings...
Section("More Apps") {
MoreAppsView(currentAppID: "myapp1")
}
}
}
}That's it! The current app is automatically excluded from the list.
import CrossPromoKit
let config = PromoConfig(
jsonURL: URL(string: "https://your-domain.com/apps.json")!,
currentAppID: "myapp1"
)
MoreAppsView(config: config)Track user interactions with the delegate:
class AnalyticsHandler: PromoEventDelegate {
func promoService(_ service: PromoService, didEmit event: PromoEvent) {
switch event {
case .impression(let appID):
// Track impression in your analytics
Analytics.log("promo_impression", ["app_id": appID])
case .tap(let appID):
// Track tap in your analytics
Analytics.log("promo_tap", ["app_id": appID])
}
}
}
// Usage
let handler = AnalyticsHandler()
MoreAppsView(config: config, eventDelegate: handler)Control which apps can promote which apps:
{
"apps": [...],
"promoRules": {
"myapp1": ["myapp2", "myapp3"],
"myapp2": ["myapp1"]
}
}In this example:
myapp1will only showmyapp2andmyapp3myapp2will only showmyapp1- Apps without rules show all other apps
The main SwiftUI view for displaying promotable apps.
// Simple initialization
MoreAppsView(currentAppID: String)
// With custom config
MoreAppsView(config: PromoConfig)
// With analytics
MoreAppsView(config: PromoConfig, eventDelegate: PromoEventDelegate?)Configuration for the promotion service.
struct PromoConfig {
let jsonURL: URL // Remote JSON endpoint
let currentAppID: String // Your app's ID (excluded from list)
}The core service managing app loading and interactions.
@Observable
class PromoService {
var apps: [PromoApp] // Current filtered apps
var isLoading: Bool // Loading state
var error: Error? // Error state
func loadApps() async // Load with fallback
func forceRefresh() async // Bypass cache
func handleAppTap(_ app: PromoApp) // Trigger overlay
func handleAppImpression(_ app: PromoApp) // Track impression
}Analytics events emitted by the service.
enum PromoEvent {
case impression(appID: String) // App row appeared
case tap(appID: String) // User tapped row
}{
"apps": [
{
"id": "string", // Unique identifier
"name": "string", // Display name
"appStoreID": "string", // App Store numeric ID
"iconURL": "string", // HTTPS URL to icon
"category": "string", // Category label
"tagline": { // Localized descriptions
"en": "string",
"ko": "string"
}
}
],
"promoRules": { // Optional
"appId": ["allowed", "app", "ids"]
}
}iconURLmust be HTTPStaglinefalls back to English if user's locale isn't available- Apps are displayed in JSON array order
- The current app is always excluded automatically
MIT License - see LICENSE for details.
Made with care for the iOS developer community.
