Hi everyone,
I’m struggling to get StoreKit 2 to fetch products in my SwiftUI app while using a sandbox user. I think I’ve followed all necessary setup steps in Xcode, App Store Connect, and my physical test device, but Product.products(for:)
always returns an empty array. I’d appreciate any insights!
What I’ve Done
- Local App Setup (Xcode 16.2)
- Created a blank SwiftUI Xcode project.
- Enabled In-App Purchase capability under Signing & Capabilities.
- Implemented minimal StoreKit 2 code to fetch available products (see below).
- Using the correct bundle identifier, which matches App Store Connect.
- App Store Connect Configuration
- Registered the app with the same bundle identifier.
- Created an Auto-Renewable Subscription with:
- Product ID: v1 (matches my code).
- All fields filled (pricing, localization, etc.).
- Status: Ready for Review.
- Linked the subscription to the latest app version in App Store Connect.
- Sandbox User & Testing Setup
- Created a sandbox tester account.
- Logged in with the sandbox user under Settings → Developer → Sandbox Apple ID. This was on my physical device (iOS 18.2).
- Installed and ran the app directly from Xcode (⌘+R).
- Issue: StoreKit Returns No Products
- Product.products(for:) does not return any products.
- There are no errors thrown, just an empty array.
- I confirmed that StoreKit Configuration is set to None in Xcode.
- No StoreKit-related logs appear in the Console.
Code Snippets
//StoreKitManager.swift
import StoreKit
import SwiftUI
@MainActor
class StoreKitManager: ObservableObject {
@Published var products: [Product] = []
@Published var errorMessage: String?
func fetchProducts() async {
do {
let productIDs: Set<String> = ["v1"] // Matches App Store Connect
let fetchedProducts = try await Product.products(for: productIDs)
print(fetchedProducts) // Debug output
DispatchQueue.main.async {
self.products = fetchedProducts
}
} catch {
DispatchQueue.main.async {
self.errorMessage = "Failed to fetch products: \(error.localizedDescription)"
}
}
}
}
//ContentView.swift
import SwiftUI
struct ContentView: View {
@StateObject private var storeKitManager = StoreKitManager()
var body: some View {
VStack {
if let errorMessage = storeKitManager.errorMessage {
Text(errorMessage).foregroundColor(.red)
} else if storeKitManager.products.isEmpty {
Text("No products available")
} else {
List(storeKitManager.products, id: \.id) { product in
VStack(alignment: .leading) {
Text(product.displayName).font(.headline)
Text(product.description).font(.subheadline)
Text("\(product.price.formatted(.currency(code: product.priceFormatStyle.currencyCode ?? "USD")))")
.bold()
}
}
}
Button("Fetch Products") {
Task {
await storeKitManager.fetchProducts()
}
}
}
.padding()
.onAppear {
Task {
await storeKitManager.fetchProducts()
}
}
}
}
#Preview {
ContentView()
}
Additional Information
- iOS Version: 18.2
- Xcode Version: 16.2
- macOS Version: 15.3.1
- Device: Physical iPhone (not simulator)
- TestFlight Build: Not used (app is run directly from Xcode)
- StoreKit Configuration: Set to None