├── 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 |
8 |
9 | ### Control Styles
10 |
11 |
12 | ### Colors
13 |
14 |
15 | ## Benefits of using StoreViews
16 | - [x] Easy to implement
17 | - [x] Localized
18 | - [x] Accessible
19 | - [x] No dependencies
20 |
21 |
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 |
--------------------------------------------------------------------------------