├── README.md ├── StoreKit Example.storekit ├── Assets │ └── 9F541BE7.png └── Configuration.storekit ├── StoreKit-Examples.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata ├── xcshareddata │ └── xcschemes │ │ └── StoreKit-Examples.xcscheme └── xcuserdata │ └── jordibruin.xcuserdatad │ └── xcdebugger │ └── Breakpoints_v2.xcbkptlist └── StoreKit-Examples ├── Assets.xcassets ├── AccentColor.colorset │ └── Contents.json ├── AppIcon.appiconset │ ├── Contents.json │ ├── appstore1024.png │ ├── ipad152.png │ ├── ipad76.png │ ├── ipadNotification20.png │ ├── ipadNotification40.png │ ├── ipadPro167.png │ ├── ipadSettings29.png │ ├── ipadSettings58.png │ ├── ipadSpotlight40.png │ ├── ipadSpotlight80.png │ ├── iphone120.png │ ├── iphone180.png │ ├── mac1024.png │ ├── mac128.png │ ├── mac16.png │ ├── mac256.png │ ├── mac32.png │ ├── mac512.png │ ├── mac64.png │ ├── notification40.png │ ├── notification60.png │ ├── settings58.png │ ├── settings87.png │ ├── spotlight120.png │ └── spotlight80.png └── Contents.json ├── ContentView.swift ├── GridView.swift ├── Managers ├── Constants.swift └── StoreKitManager.swift ├── Model ├── MarketingViewMode.swift ├── StreamingPassLevel.swift └── SubscriptionStoreViewOption.swift ├── Preview Content └── Preview Assets.xcassets │ └── Contents.json ├── README.md ├── StoreKit_Examples.entitlements ├── StoreKit_ExamplesApp.swift └── Views ├── Custom Views ├── BadgedPickerControlStyle.swift └── CustomConfirmationButton.swift ├── Editor ├── ColorEditorView.swift ├── EditorView.swift └── StoreButtonEditorView.swift ├── Grouped Views ├── BasicSubscriptionStoreView.swift ├── ControlStyleView.swift ├── GroupedSubscriptionStoreView.swift ├── PeriodGroupStoreView.swift ├── ProductsView.swift └── TrialStoreView.swift └── MarketingViews └── BlinkistMarketingView.swift /README.md: -------------------------------------------------------------------------------- 1 | # StoreKit Store View Examples 2 | This repo aims to provide sample code for the StoreKit Store and Subscription Store views. You can use them as inspiration for your own paywalls. The goal is to make each option in the StoreView customisable so you can see the effects they have. The repo also aims to recreate common marketing content based on 'proven to be effective' paywalls. 3 | 4 | ## Demo Videos 5 | 6 | ### StoreViews 7 | image 8 | 9 | ### Control Styles 10 | image 11 | 12 | ### Colors 13 | image 14 | 15 | ## Benefits of using StoreViews 16 | - [x] Easy to implement 17 | - [x] Localized 18 | - [x] Accessible 19 | - [x] No dependencies 20 | 21 | image 22 | 23 | ## Todo 24 | - [ ] Add support for all things that can be customised 25 | - [ ] iOS support 26 | - [ ] iPadOS support 27 | - [ ] macOS support 28 | - [ ] visionOS support 29 | - [ ] Figure out a way to make SubscriptionStoreControlStyle customisable 30 | 31 | Long unordered list of todos: 32 | 33 | [Store Buttons & Labels] 34 | 35 | - [x] Allow toggling visibility of Store Buttons (e.g., cancellation, restore, redeem code, policies) 36 | - [x] Support for custom button styling via SwiftUI modifiers 37 | - [x] Add support for color tinting using `.tint()` on StoreKit views and buttons 38 | - [ ] Allow toggling of Store Label styles (.automatic, .action, .prominent, .inline) 39 | 40 | [Marketing & Features] 41 | - [ ] Add support for different Marketing Material views (image, video, carousel, text) 42 | - [ ] Display Feature Highlights section using `.storeProductViewStyle(.features)` 43 | - [ ] Allow toggling of feature visibility and layout 44 | - [ ] Include option to show or hide marketing header 45 | 46 | [Subscription Options & Groups] 47 | - [x] View to display Subscription Option Group Set 48 | - [x] Toggle between different Subscription Groups (e.g., Streaming Pass vs Streaming Pass Plus) 49 | - [ ] Support for Link-style subscription optio 50 | - [ ] Show examples with one option, two options, three options 51 | - [ ] Display subscription pricing formats (monthly, yearly, family plan, etc.) 52 | - [ ] Add modifiers to detect start and success of purchases 53 | 54 | [Offers & Promotions] 55 | - [ ] Include examples with Free Trials 56 | - [ ] Include examples with Promotional Offers 57 | - [ ] Show discounted intro offers vs standard pricing 58 | - [ ] Toggle between offer eligibility states (e.g., eligible, not eligible) 59 | 60 | [Visible Relationships & State] 61 | - [ ] Toggle visibility of all `VisibleStoreProduct` relationships (like upgrades, crossgrades) 62 | - [ ] Display how relationship affects UI (e.g., upsell vs neutral) 63 | - [ ] Allow mocking of subscription status (subscribed/unsubscribed) 64 | - [ ] Include environment overrides for locale and storefront 65 | 66 | [Multi-View Support] 67 | - [x] Allow switching between multiple StoreKit Views using sidebar toggles 68 | - [x] Group previews with identifiers for easy comparison 69 | - [ ] Support previewing different StoreView variants (.compact, .expanded) 70 | 71 | [Debugging & Utilities] 72 | - [ ] Log StoreKit events (e.g., button taps, subscription started, error) 73 | - [ ] Add diagnostics panel to inspect subscription status, product info, transaction history 74 | 75 | [Preview & Testing] 76 | - [ ] Allow preview with test products via StoreKit Configuration File 77 | - [ ] Support dynamic product loading via `Product.subscription` 78 | -------------------------------------------------------------------------------- /StoreKit Example.storekit/Assets/9F541BE7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit Example.storekit/Assets/9F541BE7.png -------------------------------------------------------------------------------- /StoreKit Example.storekit/Configuration.storekit: -------------------------------------------------------------------------------- 1 | { 2 | "appPolicies" : { 3 | "eula" : "", 4 | "policies" : [ 5 | { 6 | "locale" : "en_US", 7 | "policyText" : "", 8 | "policyURL" : "" 9 | } 10 | ] 11 | }, 12 | "identifier" : "BB22C26F", 13 | "nonRenewingSubscriptions" : [ 14 | 15 | ], 16 | "products" : [ 17 | 18 | ], 19 | "settings" : { 20 | "_failTransactionsEnabled" : false, 21 | "_storeKitErrors" : [ 22 | { 23 | "current" : null, 24 | "enabled" : false, 25 | "name" : "Load Products" 26 | }, 27 | { 28 | "current" : null, 29 | "enabled" : false, 30 | "name" : "Purchase" 31 | }, 32 | { 33 | "current" : null, 34 | "enabled" : false, 35 | "name" : "Verification" 36 | }, 37 | { 38 | "current" : null, 39 | "enabled" : false, 40 | "name" : "App Store Sync" 41 | }, 42 | { 43 | "current" : null, 44 | "enabled" : false, 45 | "name" : "Subscription Status" 46 | }, 47 | { 48 | "current" : null, 49 | "enabled" : false, 50 | "name" : "App Transaction" 51 | }, 52 | { 53 | "current" : null, 54 | "enabled" : false, 55 | "name" : "Manage Subscriptions Sheet" 56 | }, 57 | { 58 | "current" : null, 59 | "enabled" : false, 60 | "name" : "Refund Request Sheet" 61 | }, 62 | { 63 | "current" : null, 64 | "enabled" : false, 65 | "name" : "Offer Code Redeem Sheet" 66 | } 67 | ] 68 | }, 69 | "subscriptionGroups" : [ 70 | { 71 | "id" : "80040CF5", 72 | "localizations" : [ 73 | 74 | ], 75 | "name" : "Auto-Renewable-Subscription-Group", 76 | "subscriptions" : [ 77 | { 78 | "adHocOffers" : [ 79 | 80 | ], 81 | "codeOffers" : [ 82 | 83 | ], 84 | "displayPrice" : "0.99", 85 | "familyShareable" : false, 86 | "groupNumber" : 1, 87 | "internalID" : "40A2448E", 88 | "introductoryOffer" : null, 89 | "localizations" : [ 90 | { 91 | "description" : "Unlock all features", 92 | "displayName" : "Basic Monthly", 93 | "locale" : "en_US" 94 | } 95 | ], 96 | "productID" : "storekit.example.monthly.basic", 97 | "recurringSubscriptionPeriod" : "P1M", 98 | "referenceName" : "Monthly Basic", 99 | "subscriptionGroupID" : "80040CF5", 100 | "type" : "RecurringSubscription", 101 | "winbackOffers" : [ 102 | 103 | ] 104 | }, 105 | { 106 | "adHocOffers" : [ 107 | 108 | ], 109 | "codeOffers" : [ 110 | 111 | ], 112 | "displayPrice" : "0.99", 113 | "familyShareable" : false, 114 | "groupNumber" : 1, 115 | "internalID" : "656CB466", 116 | "introductoryOffer" : null, 117 | "localizations" : [ 118 | { 119 | "description" : "Unlock all Premium features", 120 | "displayName" : "Premium Monthly", 121 | "locale" : "en_US" 122 | } 123 | ], 124 | "productID" : "storekit.example.monthly.premium", 125 | "recurringSubscriptionPeriod" : "P1M", 126 | "referenceName" : "Monthly Premium", 127 | "subscriptionGroupID" : "80040CF5", 128 | "type" : "RecurringSubscription", 129 | "winbackOffers" : [ 130 | 131 | ] 132 | } 133 | ] 134 | }, 135 | { 136 | "id" : "7C334564", 137 | "localizations" : [ 138 | 139 | ], 140 | "name" : "Simple Subscription", 141 | "subscriptions" : [ 142 | { 143 | "adHocOffers" : [ 144 | 145 | ], 146 | "codeOffers" : [ 147 | 148 | ], 149 | "displayPrice" : "0.99", 150 | "familyShareable" : false, 151 | "groupNumber" : 1, 152 | "internalID" : "F4115127", 153 | "introductoryOffer" : null, 154 | "localizations" : [ 155 | { 156 | "description" : "Unlock all features", 157 | "displayName" : "Monthly", 158 | "locale" : "en_US" 159 | } 160 | ], 161 | "productID" : "simple.monthly", 162 | "promotionalImageID" : "9F541BE7", 163 | "recurringSubscriptionPeriod" : "P1M", 164 | "referenceName" : "Monthly", 165 | "subscriptionGroupID" : "7C334564", 166 | "type" : "RecurringSubscription", 167 | "winbackOffers" : [ 168 | 169 | ] 170 | }, 171 | { 172 | "adHocOffers" : [ 173 | 174 | ], 175 | "codeOffers" : [ 176 | 177 | ], 178 | "displayPrice" : "9.99", 179 | "familyShareable" : false, 180 | "groupNumber" : 1, 181 | "internalID" : "7E1215ED", 182 | "introductoryOffer" : null, 183 | "localizations" : [ 184 | { 185 | "description" : "Unlock all features", 186 | "displayName" : "Yearly", 187 | "locale" : "en_US" 188 | } 189 | ], 190 | "productID" : "simple.yearly", 191 | "recurringSubscriptionPeriod" : "P1Y", 192 | "referenceName" : "Yearly", 193 | "subscriptionGroupID" : "7C334564", 194 | "type" : "RecurringSubscription", 195 | "winbackOffers" : [ 196 | 197 | ] 198 | } 199 | ] 200 | }, 201 | { 202 | "id" : "302C06AC", 203 | "localizations" : [ 204 | 205 | ], 206 | "name" : "Trial Group", 207 | "subscriptions" : [ 208 | { 209 | "adHocOffers" : [ 210 | 211 | ], 212 | "codeOffers" : [ 213 | 214 | ], 215 | "displayPrice" : "0.99", 216 | "familyShareable" : false, 217 | "groupNumber" : 1, 218 | "internalID" : "9409B710", 219 | "introductoryOffer" : { 220 | "internalID" : "331B1F11", 221 | "paymentMode" : "free", 222 | "subscriptionPeriod" : "P1W" 223 | }, 224 | "localizations" : [ 225 | { 226 | "description" : "Unlock all features", 227 | "displayName" : "Monthly", 228 | "locale" : "en_US" 229 | } 230 | ], 231 | "productID" : "trial.monthly", 232 | "recurringSubscriptionPeriod" : "P1M", 233 | "referenceName" : "Monthly Free Trial", 234 | "subscriptionGroupID" : "302C06AC", 235 | "type" : "RecurringSubscription", 236 | "winbackOffers" : [ 237 | 238 | ] 239 | }, 240 | { 241 | "adHocOffers" : [ 242 | 243 | ], 244 | "codeOffers" : [ 245 | 246 | ], 247 | "displayPrice" : "9.99", 248 | "familyShareable" : false, 249 | "groupNumber" : 1, 250 | "internalID" : "96D74CDA", 251 | "introductoryOffer" : { 252 | "displayPrice" : "0.99", 253 | "internalID" : "4CFFC0A5", 254 | "paymentMode" : "free", 255 | "subscriptionPeriod" : "P1M" 256 | }, 257 | "localizations" : [ 258 | { 259 | "description" : "Unlock all features", 260 | "displayName" : "Yearly", 261 | "locale" : "en_US" 262 | } 263 | ], 264 | "productID" : "trial.yearly", 265 | "recurringSubscriptionPeriod" : "P1Y", 266 | "referenceName" : "Yearly Free Trial", 267 | "subscriptionGroupID" : "302C06AC", 268 | "type" : "RecurringSubscription", 269 | "winbackOffers" : [ 270 | 271 | ] 272 | }, 273 | { 274 | "adHocOffers" : [ 275 | 276 | ], 277 | "codeOffers" : [ 278 | 279 | ], 280 | "displayPrice" : "0.99", 281 | "familyShareable" : false, 282 | "groupNumber" : 1, 283 | "internalID" : "9F7DFD4C", 284 | "introductoryOffer" : { 285 | "displayPrice" : "0.49", 286 | "internalID" : "C6F614FE", 287 | "numberOfPeriods" : 1, 288 | "paymentMode" : "payAsYouGo", 289 | "subscriptionPeriod" : "P1M" 290 | }, 291 | "localizations" : [ 292 | { 293 | "description" : "", 294 | "displayName" : "", 295 | "locale" : "en_US" 296 | } 297 | ], 298 | "productID" : "trial.monthly.discount", 299 | "recurringSubscriptionPeriod" : "P1M", 300 | "referenceName" : "Monthly Discounted", 301 | "subscriptionGroupID" : "302C06AC", 302 | "type" : "RecurringSubscription", 303 | "winbackOffers" : [ 304 | 305 | ] 306 | }, 307 | { 308 | "adHocOffers" : [ 309 | 310 | ], 311 | "codeOffers" : [ 312 | 313 | ], 314 | "displayPrice" : "9.99", 315 | "familyShareable" : false, 316 | "groupNumber" : 1, 317 | "internalID" : "F0B5CCBF", 318 | "introductoryOffer" : { 319 | "displayPrice" : "4.99", 320 | "internalID" : "553BBE1F", 321 | "numberOfPeriods" : 1, 322 | "paymentMode" : "payAsYouGo", 323 | "subscriptionPeriod" : "P1Y" 324 | }, 325 | "localizations" : [ 326 | { 327 | "description" : "", 328 | "displayName" : "", 329 | "locale" : "en_US" 330 | } 331 | ], 332 | "productID" : "trial.yearly.discount", 333 | "recurringSubscriptionPeriod" : "P1Y", 334 | "referenceName" : "Yearly Discounted", 335 | "subscriptionGroupID" : "302C06AC", 336 | "type" : "RecurringSubscription", 337 | "winbackOffers" : [ 338 | 339 | ] 340 | } 341 | ] 342 | } 343 | ], 344 | "version" : { 345 | "major" : 4, 346 | "minor" : 0 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /StoreKit-Examples.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 77; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C21E118C2DE06A60004785ED /* StoreKit Example.storekit in Resources */ = {isa = PBXBuildFile; fileRef = C21E118B2DE06A60004785ED /* StoreKit Example.storekit */; }; 11 | /* End PBXBuildFile section */ 12 | 13 | /* Begin PBXFileReference section */ 14 | C21E11772DE06A3A004785ED /* StoreKit-Examples.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "StoreKit-Examples.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 15 | C21E118B2DE06A60004785ED /* StoreKit Example.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = "StoreKit Example.storekit"; sourceTree = ""; }; 16 | /* End PBXFileReference section */ 17 | 18 | /* Begin PBXFileSystemSynchronizedRootGroup section */ 19 | C21E11792DE06A3A004785ED /* StoreKit-Examples */ = { 20 | isa = PBXFileSystemSynchronizedRootGroup; 21 | path = "StoreKit-Examples"; 22 | sourceTree = ""; 23 | }; 24 | /* End PBXFileSystemSynchronizedRootGroup section */ 25 | 26 | /* Begin PBXFrameworksBuildPhase section */ 27 | C21E11742DE06A3A004785ED /* Frameworks */ = { 28 | isa = PBXFrameworksBuildPhase; 29 | buildActionMask = 2147483647; 30 | files = ( 31 | ); 32 | runOnlyForDeploymentPostprocessing = 0; 33 | }; 34 | /* End PBXFrameworksBuildPhase section */ 35 | 36 | /* Begin PBXGroup section */ 37 | C21E116E2DE06A3A004785ED = { 38 | isa = PBXGroup; 39 | children = ( 40 | C21E118B2DE06A60004785ED /* StoreKit Example.storekit */, 41 | C21E11792DE06A3A004785ED /* StoreKit-Examples */, 42 | C21E11782DE06A3A004785ED /* Products */, 43 | ); 44 | sourceTree = ""; 45 | }; 46 | C21E11782DE06A3A004785ED /* Products */ = { 47 | isa = PBXGroup; 48 | children = ( 49 | C21E11772DE06A3A004785ED /* StoreKit-Examples.app */, 50 | ); 51 | name = Products; 52 | sourceTree = ""; 53 | }; 54 | /* End PBXGroup section */ 55 | 56 | /* Begin PBXNativeTarget section */ 57 | C21E11762DE06A3A004785ED /* StoreKit-Examples */ = { 58 | isa = PBXNativeTarget; 59 | buildConfigurationList = C21E11862DE06A3B004785ED /* Build configuration list for PBXNativeTarget "StoreKit-Examples" */; 60 | buildPhases = ( 61 | C21E11732DE06A3A004785ED /* Sources */, 62 | C21E11742DE06A3A004785ED /* Frameworks */, 63 | C21E11752DE06A3A004785ED /* Resources */, 64 | ); 65 | buildRules = ( 66 | ); 67 | dependencies = ( 68 | ); 69 | fileSystemSynchronizedGroups = ( 70 | C21E11792DE06A3A004785ED /* StoreKit-Examples */, 71 | ); 72 | name = "StoreKit-Examples"; 73 | packageProductDependencies = ( 74 | ); 75 | productName = "StoreKit-Examples"; 76 | productReference = C21E11772DE06A3A004785ED /* StoreKit-Examples.app */; 77 | productType = "com.apple.product-type.application"; 78 | }; 79 | /* End PBXNativeTarget section */ 80 | 81 | /* Begin PBXProject section */ 82 | C21E116F2DE06A3A004785ED /* Project object */ = { 83 | isa = PBXProject; 84 | attributes = { 85 | BuildIndependentTargetsInParallel = 1; 86 | LastSwiftUpdateCheck = 1600; 87 | LastUpgradeCheck = 1600; 88 | TargetAttributes = { 89 | C21E11762DE06A3A004785ED = { 90 | CreatedOnToolsVersion = 16.0; 91 | }; 92 | }; 93 | }; 94 | buildConfigurationList = C21E11722DE06A3A004785ED /* Build configuration list for PBXProject "StoreKit-Examples" */; 95 | developmentRegion = en; 96 | hasScannedForEncodings = 0; 97 | knownRegions = ( 98 | en, 99 | Base, 100 | ); 101 | mainGroup = C21E116E2DE06A3A004785ED; 102 | minimizedProjectReferenceProxies = 1; 103 | preferredProjectObjectVersion = 77; 104 | productRefGroup = C21E11782DE06A3A004785ED /* Products */; 105 | projectDirPath = ""; 106 | projectRoot = ""; 107 | targets = ( 108 | C21E11762DE06A3A004785ED /* StoreKit-Examples */, 109 | ); 110 | }; 111 | /* End PBXProject section */ 112 | 113 | /* Begin PBXResourcesBuildPhase section */ 114 | C21E11752DE06A3A004785ED /* Resources */ = { 115 | isa = PBXResourcesBuildPhase; 116 | buildActionMask = 2147483647; 117 | files = ( 118 | C21E118C2DE06A60004785ED /* StoreKit Example.storekit in Resources */, 119 | ); 120 | runOnlyForDeploymentPostprocessing = 0; 121 | }; 122 | /* End PBXResourcesBuildPhase section */ 123 | 124 | /* Begin PBXSourcesBuildPhase section */ 125 | C21E11732DE06A3A004785ED /* Sources */ = { 126 | isa = PBXSourcesBuildPhase; 127 | buildActionMask = 2147483647; 128 | files = ( 129 | ); 130 | runOnlyForDeploymentPostprocessing = 0; 131 | }; 132 | /* End PBXSourcesBuildPhase section */ 133 | 134 | /* Begin XCBuildConfiguration section */ 135 | C21E11842DE06A3B004785ED /* Debug */ = { 136 | isa = XCBuildConfiguration; 137 | buildSettings = { 138 | ALWAYS_SEARCH_USER_PATHS = NO; 139 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 140 | CLANG_ANALYZER_NONNULL = YES; 141 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 142 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 143 | CLANG_ENABLE_MODULES = YES; 144 | CLANG_ENABLE_OBJC_ARC = YES; 145 | CLANG_ENABLE_OBJC_WEAK = YES; 146 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 147 | CLANG_WARN_BOOL_CONVERSION = YES; 148 | CLANG_WARN_COMMA = YES; 149 | CLANG_WARN_CONSTANT_CONVERSION = YES; 150 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 151 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 152 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 153 | CLANG_WARN_EMPTY_BODY = YES; 154 | CLANG_WARN_ENUM_CONVERSION = YES; 155 | CLANG_WARN_INFINITE_RECURSION = YES; 156 | CLANG_WARN_INT_CONVERSION = YES; 157 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 158 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 159 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 160 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 161 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 162 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 163 | CLANG_WARN_STRICT_PROTOTYPES = YES; 164 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 165 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 166 | CLANG_WARN_UNREACHABLE_CODE = YES; 167 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 168 | COPY_PHASE_STRIP = NO; 169 | DEBUG_INFORMATION_FORMAT = dwarf; 170 | ENABLE_STRICT_OBJC_MSGSEND = YES; 171 | ENABLE_TESTABILITY = YES; 172 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 173 | GCC_C_LANGUAGE_STANDARD = gnu17; 174 | GCC_DYNAMIC_NO_PIC = NO; 175 | GCC_NO_COMMON_BLOCKS = YES; 176 | GCC_OPTIMIZATION_LEVEL = 0; 177 | GCC_PREPROCESSOR_DEFINITIONS = ( 178 | "DEBUG=1", 179 | "$(inherited)", 180 | ); 181 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 182 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 183 | GCC_WARN_UNDECLARED_SELECTOR = YES; 184 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 185 | GCC_WARN_UNUSED_FUNCTION = YES; 186 | GCC_WARN_UNUSED_VARIABLE = YES; 187 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 188 | MACOSX_DEPLOYMENT_TARGET = 14.5; 189 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 190 | MTL_FAST_MATH = YES; 191 | ONLY_ACTIVE_ARCH = YES; 192 | SDKROOT = macosx; 193 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 194 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 195 | }; 196 | name = Debug; 197 | }; 198 | C21E11852DE06A3B004785ED /* Release */ = { 199 | isa = XCBuildConfiguration; 200 | buildSettings = { 201 | ALWAYS_SEARCH_USER_PATHS = NO; 202 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 203 | CLANG_ANALYZER_NONNULL = YES; 204 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 205 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 206 | CLANG_ENABLE_MODULES = YES; 207 | CLANG_ENABLE_OBJC_ARC = YES; 208 | CLANG_ENABLE_OBJC_WEAK = YES; 209 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 210 | CLANG_WARN_BOOL_CONVERSION = YES; 211 | CLANG_WARN_COMMA = YES; 212 | CLANG_WARN_CONSTANT_CONVERSION = YES; 213 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 214 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 215 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 216 | CLANG_WARN_EMPTY_BODY = YES; 217 | CLANG_WARN_ENUM_CONVERSION = YES; 218 | CLANG_WARN_INFINITE_RECURSION = YES; 219 | CLANG_WARN_INT_CONVERSION = YES; 220 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 221 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 222 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 223 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 224 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 225 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 226 | CLANG_WARN_STRICT_PROTOTYPES = YES; 227 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 228 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 229 | CLANG_WARN_UNREACHABLE_CODE = YES; 230 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 231 | COPY_PHASE_STRIP = NO; 232 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 233 | ENABLE_NS_ASSERTIONS = NO; 234 | ENABLE_STRICT_OBJC_MSGSEND = YES; 235 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 236 | GCC_C_LANGUAGE_STANDARD = gnu17; 237 | GCC_NO_COMMON_BLOCKS = YES; 238 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 239 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 240 | GCC_WARN_UNDECLARED_SELECTOR = YES; 241 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 242 | GCC_WARN_UNUSED_FUNCTION = YES; 243 | GCC_WARN_UNUSED_VARIABLE = YES; 244 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 245 | MACOSX_DEPLOYMENT_TARGET = 14.5; 246 | MTL_ENABLE_DEBUG_INFO = NO; 247 | MTL_FAST_MATH = YES; 248 | SDKROOT = macosx; 249 | SWIFT_COMPILATION_MODE = wholemodule; 250 | }; 251 | name = Release; 252 | }; 253 | C21E11872DE06A3B004785ED /* Debug */ = { 254 | isa = XCBuildConfiguration; 255 | buildSettings = { 256 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 257 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 258 | CODE_SIGN_ENTITLEMENTS = "StoreKit-Examples/StoreKit_Examples.entitlements"; 259 | CODE_SIGN_STYLE = Automatic; 260 | COMBINE_HIDPI_IMAGES = YES; 261 | CURRENT_PROJECT_VERSION = 1; 262 | DEVELOPMENT_ASSET_PATHS = "\"StoreKit-Examples/Preview Content\""; 263 | DEVELOPMENT_TEAM = 8Q7TMPA46J; 264 | ENABLE_HARDENED_RUNTIME = YES; 265 | ENABLE_PREVIEWS = YES; 266 | GENERATE_INFOPLIST_FILE = YES; 267 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 268 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 269 | INFOPLIST_KEY_UIRequiresFullScreen = NO; 270 | INFOPLIST_KEY_UIStatusBarStyle = ""; 271 | INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait"; 272 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 273 | LD_RUNPATH_SEARCH_PATHS = ( 274 | "$(inherited)", 275 | "@executable_path/../Frameworks", 276 | ); 277 | MARKETING_VERSION = 1.0; 278 | PRODUCT_BUNDLE_IDENTIFIER = "com.goodsnooze.StoreKit-Examples"; 279 | PRODUCT_NAME = "$(TARGET_NAME)"; 280 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 281 | SUPPORTS_MACCATALYST = NO; 282 | SWIFT_EMIT_LOC_STRINGS = YES; 283 | SWIFT_VERSION = 5.0; 284 | TARGETED_DEVICE_FAMILY = "1,2"; 285 | }; 286 | name = Debug; 287 | }; 288 | C21E11882DE06A3B004785ED /* Release */ = { 289 | isa = XCBuildConfiguration; 290 | buildSettings = { 291 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 292 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 293 | CODE_SIGN_ENTITLEMENTS = "StoreKit-Examples/StoreKit_Examples.entitlements"; 294 | CODE_SIGN_STYLE = Automatic; 295 | COMBINE_HIDPI_IMAGES = YES; 296 | CURRENT_PROJECT_VERSION = 1; 297 | DEVELOPMENT_ASSET_PATHS = "\"StoreKit-Examples/Preview Content\""; 298 | DEVELOPMENT_TEAM = 8Q7TMPA46J; 299 | ENABLE_HARDENED_RUNTIME = YES; 300 | ENABLE_PREVIEWS = YES; 301 | GENERATE_INFOPLIST_FILE = YES; 302 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 303 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 304 | INFOPLIST_KEY_UIRequiresFullScreen = NO; 305 | INFOPLIST_KEY_UIStatusBarStyle = ""; 306 | INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait"; 307 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 308 | LD_RUNPATH_SEARCH_PATHS = ( 309 | "$(inherited)", 310 | "@executable_path/../Frameworks", 311 | ); 312 | MARKETING_VERSION = 1.0; 313 | PRODUCT_BUNDLE_IDENTIFIER = "com.goodsnooze.StoreKit-Examples"; 314 | PRODUCT_NAME = "$(TARGET_NAME)"; 315 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 316 | SUPPORTS_MACCATALYST = NO; 317 | SWIFT_EMIT_LOC_STRINGS = YES; 318 | SWIFT_VERSION = 5.0; 319 | TARGETED_DEVICE_FAMILY = "1,2"; 320 | }; 321 | name = Release; 322 | }; 323 | /* End XCBuildConfiguration section */ 324 | 325 | /* Begin XCConfigurationList section */ 326 | C21E11722DE06A3A004785ED /* Build configuration list for PBXProject "StoreKit-Examples" */ = { 327 | isa = XCConfigurationList; 328 | buildConfigurations = ( 329 | C21E11842DE06A3B004785ED /* Debug */, 330 | C21E11852DE06A3B004785ED /* Release */, 331 | ); 332 | defaultConfigurationIsVisible = 0; 333 | defaultConfigurationName = Release; 334 | }; 335 | C21E11862DE06A3B004785ED /* Build configuration list for PBXNativeTarget "StoreKit-Examples" */ = { 336 | isa = XCConfigurationList; 337 | buildConfigurations = ( 338 | C21E11872DE06A3B004785ED /* Debug */, 339 | C21E11882DE06A3B004785ED /* Release */, 340 | ); 341 | defaultConfigurationIsVisible = 0; 342 | defaultConfigurationName = Release; 343 | }; 344 | /* End XCConfigurationList section */ 345 | }; 346 | rootObject = C21E116F2DE06A3A004785ED /* Project object */; 347 | } 348 | -------------------------------------------------------------------------------- /StoreKit-Examples.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /StoreKit-Examples.xcodeproj/xcshareddata/xcschemes/StoreKit-Examples.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 16 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 55 | 56 | 57 | 63 | 65 | 71 | 72 | 73 | 74 | 76 | 77 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /StoreKit-Examples.xcodeproj/xcuserdata/jordibruin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "notification40.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "notification60.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "settings58.png", 17 | "idiom" : "iphone", 18 | "scale" : "2x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "settings87.png", 23 | "idiom" : "iphone", 24 | "scale" : "3x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "spotlight80.png", 29 | "idiom" : "iphone", 30 | "scale" : "2x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "filename" : "spotlight120.png", 35 | "idiom" : "iphone", 36 | "scale" : "3x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "iphone120.png", 41 | "idiom" : "iphone", 42 | "scale" : "2x", 43 | "size" : "60x60" 44 | }, 45 | { 46 | "filename" : "iphone180.png", 47 | "idiom" : "iphone", 48 | "scale" : "3x", 49 | "size" : "60x60" 50 | }, 51 | { 52 | "filename" : "ipadNotification20.png", 53 | "idiom" : "ipad", 54 | "scale" : "1x", 55 | "size" : "20x20" 56 | }, 57 | { 58 | "filename" : "ipadNotification40.png", 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "20x20" 62 | }, 63 | { 64 | "filename" : "ipadSettings29.png", 65 | "idiom" : "ipad", 66 | "scale" : "1x", 67 | "size" : "29x29" 68 | }, 69 | { 70 | "filename" : "ipadSettings58.png", 71 | "idiom" : "ipad", 72 | "scale" : "2x", 73 | "size" : "29x29" 74 | }, 75 | { 76 | "filename" : "ipadSpotlight40.png", 77 | "idiom" : "ipad", 78 | "scale" : "1x", 79 | "size" : "40x40" 80 | }, 81 | { 82 | "filename" : "ipadSpotlight80.png", 83 | "idiom" : "ipad", 84 | "scale" : "2x", 85 | "size" : "40x40" 86 | }, 87 | { 88 | "filename" : "ipad76.png", 89 | "idiom" : "ipad", 90 | "scale" : "1x", 91 | "size" : "76x76" 92 | }, 93 | { 94 | "filename" : "ipad152.png", 95 | "idiom" : "ipad", 96 | "scale" : "2x", 97 | "size" : "76x76" 98 | }, 99 | { 100 | "filename" : "ipadPro167.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "83.5x83.5" 104 | }, 105 | { 106 | "filename" : "appstore1024.png", 107 | "idiom" : "ios-marketing", 108 | "scale" : "1x", 109 | "size" : "1024x1024" 110 | }, 111 | { 112 | "filename" : "mac16.png", 113 | "idiom" : "mac", 114 | "scale" : "1x", 115 | "size" : "16x16" 116 | }, 117 | { 118 | "filename" : "mac32.png", 119 | "idiom" : "mac", 120 | "scale" : "2x", 121 | "size" : "16x16" 122 | }, 123 | { 124 | "filename" : "mac32.png", 125 | "idiom" : "mac", 126 | "scale" : "1x", 127 | "size" : "32x32" 128 | }, 129 | { 130 | "filename" : "mac64.png", 131 | "idiom" : "mac", 132 | "scale" : "2x", 133 | "size" : "32x32" 134 | }, 135 | { 136 | "filename" : "mac128.png", 137 | "idiom" : "mac", 138 | "scale" : "1x", 139 | "size" : "128x128" 140 | }, 141 | { 142 | "filename" : "mac256.png", 143 | "idiom" : "mac", 144 | "scale" : "2x", 145 | "size" : "128x128" 146 | }, 147 | { 148 | "filename" : "mac256.png", 149 | "idiom" : "mac", 150 | "scale" : "1x", 151 | "size" : "256x256" 152 | }, 153 | { 154 | "filename" : "mac512.png", 155 | "idiom" : "mac", 156 | "scale" : "2x", 157 | "size" : "256x256" 158 | }, 159 | { 160 | "filename" : "mac512.png", 161 | "idiom" : "mac", 162 | "scale" : "1x", 163 | "size" : "512x512" 164 | }, 165 | { 166 | "filename" : "mac1024.png", 167 | "idiom" : "mac", 168 | "scale" : "2x", 169 | "size" : "512x512" 170 | } 171 | ], 172 | "info" : { 173 | "author" : "xcode", 174 | "version" : 1 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/appstore1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/appstore1024.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/ipad152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/ipad152.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/ipad76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/ipad76.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/ipadNotification20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/ipadNotification20.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/ipadNotification40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/ipadNotification40.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/ipadPro167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/ipadPro167.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/ipadSettings29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/ipadSettings29.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/ipadSettings58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/ipadSettings58.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/ipadSpotlight40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/ipadSpotlight40.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/ipadSpotlight80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/ipadSpotlight80.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/iphone120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/iphone120.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/iphone180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/iphone180.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/mac1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/mac1024.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/mac128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/mac128.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/mac16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/mac16.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/mac256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/mac256.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/mac32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/mac32.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/mac512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/mac512.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/mac64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/mac64.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/notification40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/notification40.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/notification60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/notification60.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/settings58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/settings58.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/settings87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/settings87.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/spotlight120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/spotlight120.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/spotlight80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/StoreKit-Examples/79ef06c9b13c949f8593e0cb59a280828e665d28/StoreKit-Examples/Assets.xcassets/AppIcon.appiconset/spotlight80.png -------------------------------------------------------------------------------- /StoreKit-Examples/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /StoreKit-Examples/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // StoreKit-Examples 4 | // 5 | // Created by Jordi Bruin on 23/05/2025. 6 | // 7 | 8 | import SwiftUI 9 | import StoreKit 10 | 11 | struct ContentView: View { 12 | 13 | @State var storeKitManager = StoreKitManager() 14 | @State var showEditor = false 15 | 16 | var body: some View { 17 | Group { 18 | if UIDevice.current.userInterfaceIdiom == .pad { 19 | storeKitManager.subscriptionStoreViewOption 20 | .inspector(isPresented: .constant(true)) { 21 | EditorView() 22 | } 23 | } else { 24 | ZStack(alignment: .topTrailing) { 25 | storeKitManager.subscriptionStoreViewOption 26 | 27 | Button { 28 | showEditor.toggle() 29 | } label: { 30 | Text("Edit") 31 | } 32 | .buttonStyle(.borderedProminent) 33 | .tint(.black) 34 | .padding(.top, -60) 35 | .padding(.trailing) 36 | .padding() 37 | } 38 | } 39 | } 40 | .sheet(isPresented: $showEditor) { 41 | NavigationView { 42 | EditorView() 43 | .environment(storeKitManager) 44 | } 45 | } 46 | .environment(storeKitManager) 47 | } 48 | } 49 | 50 | #Preview { 51 | ContentView() 52 | } 53 | 54 | 55 | extension SubscriptionStoreButtonLabel { 56 | static var allCases: [SubscriptionStoreButtonLabel] { 57 | [.action, .automatic, .displayName, .multiline, .price, .singleLine] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /StoreKit-Examples/GridView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GridView.swift 3 | // StoreKit-Examples 4 | // 5 | // Created by Jordi Bruin on 25/05/2025. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct GridView: View { 11 | 12 | @State var showInspector = false 13 | 14 | var body: some View { 15 | NavigationStack { 16 | ScrollView { 17 | LazyVGrid(columns: [ 18 | GridItem(.adaptive(minimum: 400, maximum: 800)), 19 | GridItem(.adaptive(minimum: 400, maximum: 800)), 20 | ]) { 21 | BasicSubscriptionStoreView() 22 | .border(Color.gray, width: 1) 23 | .shadow(radius: 1) 24 | .frame(height: 600) 25 | 26 | GroupedSubscriptionStoreView() 27 | .border(Color.gray, width: 1) 28 | .shadow(radius: 3) 29 | .frame(height: 600) 30 | 31 | PeriodGroupStoreView() 32 | .border(Color.gray, width: 1) 33 | .shadow(radius: 3) 34 | .frame(height: 600) 35 | 36 | GroupedSubscriptionStoreView() 37 | .border(Color.gray, width: 1) 38 | .shadow(radius: 3) 39 | .frame(height: 600) 40 | } 41 | .padding() 42 | } 43 | 44 | .navigationTitle("All StoreView Styles") 45 | .toolbar(content: { 46 | ToolbarItem { 47 | Button { 48 | showInspector.toggle() 49 | } label: { 50 | Text("Editor") 51 | } 52 | } 53 | }) 54 | .inspector(isPresented: $showInspector) { 55 | NavigationStack { 56 | EditorView() 57 | } 58 | .environment(StoreKitManager()) 59 | } 60 | } 61 | 62 | .environment(StoreKitManager()) 63 | } 64 | } 65 | 66 | #Preview { 67 | GridView() 68 | } 69 | -------------------------------------------------------------------------------- /StoreKit-Examples/Managers/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // StoreKit-Examples 4 | // 5 | // Created by Jordi Bruin on 23/05/2025. 6 | // 7 | 8 | struct Constants { 9 | static let groupID = "80040CF5" 10 | static let simpleGroupID = "7C334564" 11 | static let trialGroup = "302C06AC" 12 | 13 | } 14 | 15 | enum Feature: String, CaseIterable, Identifiable { 16 | case featureOne 17 | case featureTwo 18 | case featureThree 19 | case featureFour 20 | 21 | var id: String { self.rawValue } 22 | 23 | var title: String { 24 | switch self { 25 | case .featureOne: 26 | "Feature One" 27 | case .featureTwo: 28 | "Feature Two" 29 | case .featureThree: 30 | "Feature Three" 31 | case .featureFour: 32 | "Feature Four" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /StoreKit-Examples/Managers/StoreKitManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoreKitManager.swift 3 | // StoreKit-Examples 4 | // 5 | // Created by Jordi Bruin on 23/05/2025. 6 | // 7 | 8 | import SwiftUI 9 | import StoreKit 10 | 11 | @Observable 12 | class StoreKitManager { 13 | 14 | var showCancellation = true 15 | var showRestorePurchases = false 16 | var showSignInButton = false 17 | var showRedeemCode = false 18 | var showPolicies = false 19 | 20 | var primarySubscriptionStoreButtonLabel: SubscriptionStoreButtonLabel = .automatic 21 | var secondarySubscriptionStoreButtonLabel: SubscriptionStoreButtonLabel = .automatic 22 | 23 | var tintColor: Color = .green 24 | var foregroundColor: Color = .primary 25 | 26 | var subscriptionStoreViewOption: SubscriptionStoreViewOption = .grouped 27 | 28 | var marketingViewMode: MarketingViewMode = .basic 29 | 30 | var controlStyle: ControlStyle = .automatic 31 | 32 | } 33 | 34 | -------------------------------------------------------------------------------- /StoreKit-Examples/Model/MarketingViewMode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MarketingViewMode.swift 3 | // StoreKit-Examples 4 | // 5 | // Created by Jordi Bruin on 25/05/2025. 6 | // 7 | 8 | import SwiftUI 9 | 10 | enum MarketingViewMode: String, CaseIterable, Identifiable { 11 | case basic 12 | case blinkist 13 | 14 | var id: String { self.rawValue } 15 | 16 | var name: String { 17 | switch self { 18 | case .basic: 19 | "Basic" 20 | case .blinkist: 21 | "Blinkist" 22 | } 23 | } 24 | 25 | var explanation: String { 26 | switch self { 27 | case .basic: 28 | "The default marketing view" 29 | case .blinkist: 30 | "Blinkist Example which shows the timeline for the trial" 31 | } 32 | } 33 | 34 | @ViewBuilder 35 | var view: some View { 36 | switch self { 37 | case .basic: 38 | ZStack { 39 | LinearGradient(colors: [Color.blue, Color.black.opacity(0.8)], startPoint: .top, endPoint: .bottom) 40 | Text("A very basic marketing view") 41 | .foregroundStyle(.white) 42 | .font(.largeTitle) 43 | .bold() 44 | } 45 | case .blinkist: 46 | VStack { 47 | Text("Here is what will happen") 48 | Text("day 1") 49 | Text("day 5") 50 | Text("day 7") 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /StoreKit-Examples/Model/StreamingPassLevel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StreamingPassLevel.swift 3 | // StoreKit-Examples 4 | // 5 | // Created by Jordi Bruin on 23/05/2025. 6 | // 7 | 8 | import SwiftUI 9 | import StoreKit 10 | 11 | enum StreamingPassLevel: Hashable { 12 | case basic 13 | case premium 14 | 15 | init(_ product: Product) { 16 | if product.id.contains("basic") { 17 | self = .basic 18 | } else { 19 | self = .premium 20 | } 21 | } 22 | 23 | var localizedTitle: String { 24 | switch self { 25 | case .basic: 26 | "Basic" 27 | case .premium: 28 | "Premium" 29 | } 30 | } 31 | 32 | @ViewBuilder 33 | var marketingContent: some View { 34 | ZStack { 35 | switch self { 36 | case .basic: 37 | Color.blue 38 | case .premium: 39 | Color.orange 40 | } 41 | 42 | Text(localizedTitle) 43 | .font(.largeTitle) 44 | .bold() 45 | .foregroundStyle(.white) 46 | } 47 | .edgesIgnoringSafeArea(.top) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /StoreKit-Examples/Model/SubscriptionStoreViewOption.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SubscriptionStoreViewOption.swift 3 | // StoreKit-Examples 4 | // 5 | // Created by Jordi Bruin on 25/05/2025. 6 | // 7 | 8 | import SwiftUI 9 | 10 | enum SubscriptionStoreViewOption: String, Identifiable, CaseIterable, View { 11 | case basic 12 | case grouped 13 | case period 14 | case controlStyle 15 | case products 16 | case trials 17 | 18 | var id: String { self.rawValue } 19 | 20 | var body: some View { 21 | switch self { 22 | case .basic: 23 | BasicSubscriptionStoreView() 24 | case .grouped: 25 | GroupedSubscriptionStoreView() 26 | case .period: 27 | PeriodGroupStoreView() 28 | case .controlStyle: 29 | ControlStyleView() 30 | case .products: 31 | ProductsView() 32 | case .trials: 33 | TrialStoreView() 34 | } 35 | } 36 | 37 | var title: String { 38 | switch self { 39 | case .basic: 40 | "None" 41 | case .grouped: 42 | "Grouped" 43 | case .period: 44 | "Period" 45 | case .controlStyle: 46 | "Control Style" 47 | case .products: 48 | "Products" 49 | case .trials: 50 | "Trials" 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /StoreKit-Examples/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /StoreKit-Examples/README.md: -------------------------------------------------------------------------------- 1 | [Store Buttons & Labels] 2 | [] Allow toggling visibility of Store Buttons (e.g., cancellation, restore, redeem code, policies) 3 | [] Allow toggling of Store Label styles (.automatic, .action, .prominent, .inline) 4 | [] Include toggle for button placement (e.g., top, bottom) 5 | [] Support for custom button styling via SwiftUI modifiers 6 | [] Add support for color tinting using `.tint()` on StoreKit views and buttons 7 | [] Add custom control style 8 | [] Add background to the bottom section (.background) 9 | 10 | [Marketing & Features] 11 | [] Add support for different Marketing Material views (image, video, carousel, text) 12 | [] Display Feature Highlights section using `.storeProductViewStyle(.features)` 13 | [] Allow toggling of feature visibility and layout 14 | [] Include option to show or hide marketing header 15 | 16 | [Subscription Options & Groups] 17 | [] View to display Subscription Option Group Set 18 | [] Toggle between different Subscription Groups (e.g., Streaming Pass vs Streaming Pass Plus) 19 | [] Support for Link-style subscription options 20 | [] Show examples with one option, two options, three options 21 | [] Display subscription pricing formats (monthly, yearly, family plan, etc.) 22 | 23 | [Offers & Promotions] 24 | [] Include examples with Free Trials 25 | [] Include examples with Promotional Offers 26 | [] Show discounted intro offers vs standard pricing 27 | [] Toggle between offer eligibility states (e.g., eligible, not eligible) 28 | 29 | [Visible Relationships & State] 30 | [] Toggle visibility of all `VisibleStoreProduct` relationships (like upgrades, crossgrades) 31 | [] Display how relationship affects UI (e.g., upsell vs neutral) 32 | [] Allow mocking of subscription status (subscribed/unsubscribed) 33 | [] Include environment overrides for locale and storefront 34 | 35 | [Multi-View Support] 36 | [] Allow switching between multiple StoreKit Views using sidebar toggles 37 | [] Allow switching between multiple StoreKit Groups using sidebar toggles 38 | [] Support previewing different StoreView variants (.compact, .expanded) 39 | [] Group previews with identifiers for easy comparison 40 | 41 | [Debugging & Utilities] 42 | [] Show output of current StoreKit configuration in JSON 43 | [] Log StoreKit events (e.g., button taps, subscription started, error) 44 | [] Include a toggle for simulated purchase flow vs real 45 | [] Add diagnostics panel to inspect subscription status, product info, transaction history 46 | 47 | [Preview & Testing] 48 | [] Add SwiftUI previews for all combinations of views 49 | [] Allow preview with test products via StoreKit Configuration File 50 | [] Support dynamic product loading via `Product.subscription` 51 | -------------------------------------------------------------------------------- /StoreKit-Examples/StoreKit_Examples.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /StoreKit-Examples/StoreKit_ExamplesApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoreKit_ExamplesApp.swift 3 | // StoreKit-Examples 4 | // 5 | // Created by Jordi Bruin on 23/05/2025. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct StoreKit_ExamplesApp: App { 12 | 13 | // @State var storeKitManager = StoreKitManager() 14 | 15 | var body: some Scene { 16 | WindowGroup { 17 | ContentView() 18 | // .environment(storeKitManager) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /StoreKit-Examples/Views/Custom Views/BadgedPickerControlStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BadgedPickerControlStyle.swift 3 | // StoreKit-Examples 4 | // 5 | // Created by Jordi Bruin on 25/05/2025. 6 | // 7 | 8 | 9 | import StoreKit 10 | import SwiftUI 11 | 12 | struct BadgedPickerControlStyle: SubscriptionStoreControlStyle { 13 | func makeBody(configuration: Configuration) -> some View { 14 | SubscriptionPicker(configuration) { option in 15 | HStack(alignment: .top) { 16 | VStack(alignment: .leading) { 17 | Text(option.displayName) 18 | .font(.title2) 19 | 20 | if option.isFamilyShareable { 21 | FamilyShareableBadge() 22 | } else { 23 | Text("Not Family Shareable") 24 | .foregroundStyle(.red) 25 | } 26 | Text(option.description) 27 | } 28 | Spacer() 29 | } 30 | } confirmation: { option in 31 | // CustomConfirmationButton() 32 | SubscribeButton(option) 33 | } 34 | } 35 | } 36 | 37 | struct FamilyShareableBadge: View { 38 | var body: some View { 39 | Text("Family Shareable") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /StoreKit-Examples/Views/Custom Views/CustomConfirmationButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomConfirmationButton.swift 3 | // StoreKit-Examples 4 | // 5 | // Created by Jordi Bruin on 25/05/2025. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct CustomConfirmationButton: View { 11 | 12 | // TODO: Implement 13 | // let option: StoreKit.Configuration.PickerOption 14 | 15 | var body: some View { 16 | Text("Subscribe") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /StoreKit-Examples/Views/Editor/ColorEditorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorsEditView.swift 3 | // StoreKit-Examples 4 | // 5 | // Created by Jordi Bruin on 25/05/2025. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ColorEditorView: View { 11 | 12 | @Environment(StoreKitManager.self) var storeKitManager 13 | 14 | var body: some View { 15 | @Bindable var storeKitManager = storeKitManager 16 | return Form { 17 | Section { 18 | ColorPicker("Tint Color", selection: $storeKitManager.tintColor, supportsOpacity: true) 19 | } footer: { 20 | Text("The tint color is used as the background for confirmation buttons, and as borders in prominent control styles") 21 | } 22 | 23 | Section { 24 | ColorPicker("Foreground Color", selection: $storeKitManager.foregroundColor, supportsOpacity: false) 25 | } footer: { 26 | Text("The foreground color is for button titles") 27 | } 28 | } 29 | .formStyle(.grouped) 30 | } 31 | } 32 | 33 | #Preview(body: { 34 | NavigationView { 35 | ColorEditorView() 36 | } 37 | .environment(StoreKitManager()) 38 | }) 39 | 40 | -------------------------------------------------------------------------------- /StoreKit-Examples/Views/Editor/EditorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EditorView.swift 3 | // StoreKit-Examples 4 | // 5 | // Created by Jordi Bruin on 23/05/2025. 6 | // 7 | 8 | import SwiftUI 9 | import StoreKit 10 | 11 | struct EditorView: View { 12 | 13 | @Environment(StoreKitManager.self) var storeKitManager 14 | @Environment(\.dismiss) var dismiss 15 | 16 | var body: some View { 17 | @Bindable var storeKitManager = storeKitManager 18 | Form { 19 | storeTypeSection 20 | 21 | if storeKitManager.subscriptionStoreViewOption == .controlStyle { 22 | controlStylePicker 23 | } 24 | 25 | buttonsNavigationLink 26 | subscriptionStoreButtonLabelSection 27 | 28 | colorsNavigationLink 29 | } 30 | .formStyle(.grouped) 31 | .toolbar { 32 | Button { 33 | dismiss() 34 | } label: { 35 | Text("Close") 36 | } 37 | .opacity(UIDevice.current.userInterfaceIdiom == .pad ? 0 : 1) 38 | } 39 | .navigationTitle("Editor") 40 | } 41 | 42 | var storeTypeSection: some View { 43 | @Bindable var storeKitManager = storeKitManager 44 | return Section { 45 | Picker(selection: $storeKitManager.subscriptionStoreViewOption) { 46 | ForEach(SubscriptionStoreViewOption.allCases) { option in 47 | Text(option.title).tag(option) 48 | } 49 | } label: { 50 | Text("Product Group") 51 | } 52 | .pickerStyle(.navigationLink) 53 | 54 | marketingViewSection 55 | } header: { 56 | Text("Product Grouping") 57 | } 58 | } 59 | 60 | @ViewBuilder 61 | var controlStylePicker: some View { 62 | @Bindable var storeKitManager = storeKitManager 63 | Section { 64 | Picker(selection: $storeKitManager.controlStyle) { 65 | ForEach(ControlStyle.allCases) { style in 66 | Text(style.title).tag(style) 67 | } 68 | } label: { 69 | Text("Control Style") 70 | } 71 | } 72 | } 73 | 74 | var buttonsNavigationLink: some View { 75 | NavigationLink { 76 | StoreButtonEditorView() 77 | } label: { 78 | Text("Store Buttons") 79 | } 80 | } 81 | 82 | var colorsNavigationLink: some View { 83 | NavigationLink { 84 | ColorEditorView() 85 | } label: { 86 | HStack { 87 | Text("Colors") 88 | Spacer() 89 | 90 | HStack(spacing: -6) { 91 | Circle() 92 | .foregroundStyle(storeKitManager.tintColor) 93 | .frame(width: 26, height: 26) 94 | 95 | Circle() 96 | .foregroundStyle(storeKitManager.foregroundColor) 97 | .frame(width: 26, height: 26) 98 | } 99 | } 100 | } 101 | } 102 | 103 | var subscriptionStoreButtonLabelSection: some View { 104 | @Bindable var storeKitManager = storeKitManager 105 | return Section { 106 | Picker(selection: $storeKitManager.primarySubscriptionStoreButtonLabel) { 107 | ForEach(SubscriptionStoreButtonLabel.allCases, id: \.self) { type in 108 | Text(nameFor(label: type)) 109 | } 110 | } label: { 111 | Text("Primary Label") 112 | } 113 | 114 | Picker(selection: $storeKitManager.secondarySubscriptionStoreButtonLabel) { 115 | ForEach(SubscriptionStoreButtonLabel.allCases, id: \.self) { type in 116 | Text(nameFor(label: type)) 117 | } 118 | } label: { 119 | Text("Secondary Label") 120 | } 121 | } header: { 122 | Text("Subscription Store Button Labels") 123 | } 124 | } 125 | 126 | @ViewBuilder 127 | var marketingViewSection: some View { 128 | @Bindable var storeKitManager = storeKitManager 129 | Picker(selection: $storeKitManager.marketingViewMode) { 130 | ForEach(MarketingViewMode.allCases) { mode in 131 | Text(mode.name).tag(mode) 132 | } 133 | } label: { 134 | Text("Marketing View Mode") 135 | } 136 | .pickerStyle(.navigationLink) 137 | } 138 | 139 | func nameFor(label: SubscriptionStoreButtonLabel) -> String { 140 | switch label { 141 | case .action: 142 | "Action" 143 | case .automatic: 144 | "Automatic" 145 | case .displayName: 146 | "Display Name" 147 | case .price: 148 | "Price" 149 | case .singleLine: 150 | "Singleline" 151 | case .multiline: 152 | "Multiline" 153 | default: 154 | "Unknown" 155 | } 156 | } 157 | } 158 | 159 | #Preview(body: { 160 | NavigationView { 161 | EditorView() 162 | } 163 | .environment(StoreKitManager()) 164 | }) 165 | -------------------------------------------------------------------------------- /StoreKit-Examples/Views/Editor/StoreButtonEditorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoreButtonEditorView.swift 3 | // StoreKit-Examples 4 | // 5 | // Created by Jordi Bruin on 25/05/2025. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct StoreButtonEditorView: View { 11 | 12 | @Environment(StoreKitManager.self) var storeKitManager 13 | 14 | var body: some View { 15 | @Bindable var storeKitManager = storeKitManager 16 | return Form { 17 | Section { 18 | Toggle(isOn: $storeKitManager.showCancellation) { 19 | Text("Cancellation") 20 | } 21 | } footer: { 22 | Text("Enable this to show an xmark button in the top right corner to let users close the storeview") 23 | } 24 | 25 | Section { 26 | Toggle(isOn: $storeKitManager.showPolicies) { 27 | Text("Policies") 28 | } 29 | } footer: { 30 | Text("Enable this to show links to your Terms of Use and Privacy Policy") 31 | } 32 | 33 | Section { 34 | Toggle(isOn: $storeKitManager.showRedeemCode) { 35 | Text("Redeem Code") 36 | } 37 | } footer: { 38 | Text("Enable this to show a button to let users redeem a promo code") 39 | } 40 | 41 | Section { 42 | Toggle(isOn: $storeKitManager.showRestorePurchases) { 43 | Text("Restore Purchases") 44 | } 45 | } footer: { 46 | Text("Enable this to show a restore purchases button for users who purchased an IAP in the past which for some reason has not been restored properly") 47 | } 48 | 49 | Section { 50 | Toggle(isOn: $storeKitManager.showSignInButton) { 51 | Text("Sign In") 52 | } 53 | } footer: { 54 | Text("Enable this to show a sign in button for users who are not logged in to their AppleID") 55 | } 56 | } 57 | .navigationTitle("Store Buttons") 58 | } 59 | } 60 | 61 | #Preview(body: { 62 | NavigationView { 63 | StoreButtonEditorView() 64 | } 65 | .environment(StoreKitManager()) 66 | }) 67 | 68 | -------------------------------------------------------------------------------- /StoreKit-Examples/Views/Grouped Views/BasicSubscriptionStoreView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BasicSubscriptionStoreView.swift 3 | // StoreKit-Examples 4 | // 5 | // Created by Jordi Bruin on 23/05/2025. 6 | // 7 | 8 | import SwiftUI 9 | import StoreKit 10 | 11 | struct BasicSubscriptionStoreView: View { 12 | 13 | @Environment(StoreKitManager.self) var storeKitManager 14 | 15 | var body: some View { 16 | @Bindable var storeKitManager = storeKitManager 17 | return SubscriptionStoreView(groupID: Constants.groupID, marketingContent: { 18 | storeKitManager.marketingViewMode.view 19 | }) 20 | .subscriptionStoreControlStyle(.compactPicker, placement: .buttonsInBottomBar) 21 | .tint(storeKitManager.tintColor) 22 | .foregroundStyle(storeKitManager.foregroundColor) 23 | 24 | // Buttons 25 | .storeButton(storeKitManager.showCancellation ? .visible : .hidden, for: .cancellation) 26 | .storeButton(storeKitManager.showPolicies ? .visible : .hidden, for: .policies) 27 | .storeButton(storeKitManager.showRedeemCode ? .visible : .hidden, for: .redeemCode) 28 | .storeButton(storeKitManager.showRestorePurchases ? .visible : .hidden, for: .restorePurchases) 29 | .storeButton(storeKitManager.showSignInButton ? .visible : .hidden, for: .signIn) 30 | 31 | // Subscription Store Button Labels 32 | .subscriptionStoreButtonLabel(storeKitManager.primarySubscriptionStoreButtonLabel) 33 | .subscriptionStoreButtonLabel(storeKitManager.secondarySubscriptionStoreButtonLabel) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /StoreKit-Examples/Views/Grouped Views/ControlStyleView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ControlStyleView.swift 3 | // StoreKit-Examples 4 | // 5 | // Created by Jordi Bruin on 25/05/2025. 6 | // 7 | 8 | import SwiftUI 9 | import StoreKit 10 | 11 | enum ControlStyle: String, CaseIterable, Identifiable, View { 12 | case automatic 13 | case compactPicker 14 | case buttons 15 | case pagedPicker 16 | case pagedProminentPicker 17 | case picker 18 | case prominentPicker 19 | case custom 20 | 21 | var id: String { self.rawValue } 22 | 23 | var title: String { 24 | switch self { 25 | case .automatic: "Automatic" 26 | case .compactPicker: "Compact Picker" 27 | case .buttons: "Buttons" 28 | case .pagedPicker: "Paged Picker" 29 | case .pagedProminentPicker: "Paged Prominent Picker" 30 | case .picker: "Picker" 31 | case .prominentPicker: "ProminentPicker" 32 | case .custom: "Custom" 33 | } 34 | } 35 | 36 | var body: some View { 37 | switch self { 38 | case .automatic: 39 | SubscriptionStoreView(groupID: Constants.simpleGroupID) 40 | .subscriptionStoreControlStyle(.automatic, placement: .automatic) 41 | case .compactPicker: 42 | SubscriptionStoreView(groupID: Constants.simpleGroupID) 43 | .subscriptionStoreControlStyle(.compactPicker, placement: .automatic) 44 | case .buttons: 45 | SubscriptionStoreView(groupID: Constants.simpleGroupID) 46 | .subscriptionStoreControlStyle(.buttons) 47 | case .pagedPicker: 48 | SubscriptionStoreView(groupID: Constants.simpleGroupID) 49 | .subscriptionStoreControlStyle(.pagedPicker, placement: .buttonsInBottomBar) 50 | case .pagedProminentPicker: 51 | SubscriptionStoreView(groupID: Constants.simpleGroupID) 52 | .subscriptionStoreControlStyle(.pagedProminentPicker, placement: .buttonsInBottomBar) 53 | case .picker: 54 | SubscriptionStoreView(groupID: Constants.simpleGroupID) 55 | .subscriptionStoreControlStyle(.picker, placement: .buttonsInBottomBar) 56 | case .prominentPicker: 57 | SubscriptionStoreView(groupID: Constants.simpleGroupID) 58 | .subscriptionStoreControlStyle(.prominentPicker, placement: .buttonsInBottomBar) 59 | case .custom: 60 | SubscriptionStoreView(groupID: Constants.simpleGroupID) 61 | .subscriptionStoreControlStyle(BadgedPickerControlStyle()) 62 | } 63 | } 64 | } 65 | 66 | struct ControlStyleView: View { 67 | @Environment(StoreKitManager.self) var storeKitManager 68 | 69 | var body: some View { 70 | storeKitManager.controlStyle 71 | } 72 | } 73 | 74 | 75 | -------------------------------------------------------------------------------- /StoreKit-Examples/Views/Grouped Views/GroupedSubscriptionStoreView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GroupedSubscriptionStoreView.swift 3 | // StoreKit-Examples 4 | // 5 | // Created by Jordi Bruin on 23/05/2025. 6 | // 7 | 8 | import SwiftUI 9 | import StoreKit 10 | 11 | struct GroupedSubscriptionStoreView: View { 12 | 13 | @Environment(StoreKitManager.self) var storeKitManager 14 | 15 | var body: some View { 16 | SubscriptionStoreView(groupID: Constants.groupID) { 17 | SubscriptionOptionGroupSet { product in 18 | StreamingPassLevel(product) 19 | } label: { product in 20 | Text(product.localizedTitle) 21 | } marketingContent: { product in 22 | product.marketingContent 23 | } 24 | } 25 | .subscriptionStoreControlStyle(.compactPicker, placement: .buttonsInBottomBar) 26 | .tint(storeKitManager.tintColor) 27 | .foregroundStyle(storeKitManager.foregroundColor) 28 | 29 | // Buttons 30 | .storeButton(storeKitManager.showCancellation ? .visible : .hidden, for: .cancellation) 31 | .storeButton(storeKitManager.showPolicies ? .visible : .hidden, for: .policies) 32 | .storeButton(storeKitManager.showRedeemCode ? .visible : .hidden, for: .redeemCode) 33 | .storeButton(storeKitManager.showRestorePurchases ? .visible : .hidden, for: .restorePurchases) 34 | .storeButton(storeKitManager.showSignInButton ? .visible : .hidden, for: .signIn) 35 | 36 | // Subscription Store Button Labels 37 | .subscriptionStoreButtonLabel(storeKitManager.primarySubscriptionStoreButtonLabel) 38 | .subscriptionStoreButtonLabel(storeKitManager.secondarySubscriptionStoreButtonLabel) 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /StoreKit-Examples/Views/Grouped Views/PeriodGroupStoreView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PeriodGroupStoreView.swift 3 | // StoreKit-Examples 4 | // 5 | // Created by Jordi Bruin on 25/05/2025. 6 | // 7 | 8 | import SwiftUI 9 | import StoreKit 10 | 11 | struct PeriodGroupStoreView: View { 12 | 13 | @Environment(StoreKitManager.self) var storeKitManager 14 | 15 | var body: some View { 16 | SubscriptionStoreView(groupID: Constants.simpleGroupID) { 17 | SubscriptionPeriodGroupSet { period in 18 | Text("period") 19 | } 20 | } 21 | .subscriptionStoreControlStyle(.compactPicker, placement: .buttonsInBottomBar) 22 | .tint(storeKitManager.tintColor) 23 | .foregroundStyle(storeKitManager.foregroundColor) 24 | 25 | // Buttons 26 | .storeButton(storeKitManager.showCancellation ? .visible : .hidden, for: .cancellation) 27 | .storeButton(storeKitManager.showPolicies ? .visible : .hidden, for: .policies) 28 | .storeButton(storeKitManager.showRedeemCode ? .visible : .hidden, for: .redeemCode) 29 | .storeButton(storeKitManager.showRestorePurchases ? .visible : .hidden, for: .restorePurchases) 30 | .storeButton(storeKitManager.showSignInButton ? .visible : .hidden, for: .signIn) 31 | 32 | // Subscription Store Button Labels 33 | .subscriptionStoreButtonLabel(storeKitManager.primarySubscriptionStoreButtonLabel) 34 | .subscriptionStoreButtonLabel(storeKitManager.secondarySubscriptionStoreButtonLabel) 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /StoreKit-Examples/Views/Grouped Views/ProductsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProductsView.swift 3 | // StoreKit-Examples 4 | // 5 | // Created by Jordi Bruin on 25/05/2025. 6 | // 7 | 8 | import SwiftUI 9 | import StoreKit 10 | 11 | struct ProductsView: View { 12 | @Environment(StoreKitManager.self) var storeKitManager 13 | 14 | var body: some View { 15 | ScrollView { 16 | ProductView(id: "simple.monthly", prefersPromotionalIcon: false) 17 | ProductView(id: "simple.monthly", prefersPromotionalIcon: true) 18 | } 19 | } 20 | } 21 | 22 | #Preview(body: { 23 | ProductsView() 24 | .environment(StoreKitManager()) 25 | }) 26 | 27 | 28 | -------------------------------------------------------------------------------- /StoreKit-Examples/Views/Grouped Views/TrialStoreView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PeriodGroupStoreView 2.swift 3 | // StoreKit-Examples 4 | // 5 | // Created by Jordi Bruin on 25/05/2025. 6 | // 7 | 8 | import SwiftUI 9 | import StoreKit 10 | 11 | struct TrialStoreView: View { 12 | 13 | @Environment(StoreKitManager.self) var storeKitManager 14 | 15 | var body: some View { 16 | SubscriptionStoreView(groupID: Constants.trialGroup) { 17 | SubscriptionPeriodGroupSet { period in 18 | Color.blue 19 | } 20 | } 21 | .subscriptionStoreControlStyle(.compactPicker, placement: .buttonsInBottomBar) 22 | .tint(storeKitManager.tintColor) 23 | .foregroundStyle(storeKitManager.foregroundColor) 24 | 25 | // Buttons 26 | .storeButton(storeKitManager.showCancellation ? .visible : .hidden, for: .cancellation) 27 | .storeButton(storeKitManager.showPolicies ? .visible : .hidden, for: .policies) 28 | .storeButton(storeKitManager.showRedeemCode ? .visible : .hidden, for: .redeemCode) 29 | .storeButton(storeKitManager.showRestorePurchases ? .visible : .hidden, for: .restorePurchases) 30 | .storeButton(storeKitManager.showSignInButton ? .visible : .hidden, for: .signIn) 31 | 32 | // Subscription Store Button Labels 33 | .subscriptionStoreButtonLabel(storeKitManager.primarySubscriptionStoreButtonLabel) 34 | .subscriptionStoreButtonLabel(storeKitManager.secondarySubscriptionStoreButtonLabel) 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /StoreKit-Examples/Views/MarketingViews/BlinkistMarketingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BlinkistMarketingView.swift 3 | // StoreKit-Examples 4 | // 5 | // Created by Jordi Bruin on 23/05/2025. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct BlinkistMarketingView: View { 11 | var body: some View { 12 | Text("Blinkist view here") 13 | } 14 | } 15 | --------------------------------------------------------------------------------