diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e6b74ff --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# CHANGELOG + +## 0.1.0 (2024-10-17) +* Initial Release + diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..6ee8642 --- /dev/null +++ b/Package.swift @@ -0,0 +1,15 @@ +// swift-tools-version: 5.8 +import PackageDescription + +let package = Package( + name: "StoreKitHelper", + products: [ + .library(name: "StoreKitHelper", targets: ["StoreKitHelper"]), + ], + dependencies: [ + + ], + targets: [ + .target(name: "StoreKitHelper", dependencies: []) + ] +) diff --git a/README.md b/README.md index cf12e64..41d0714 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,93 @@ # StoreKitHelper -A lightweight StoreKit wrapper designed to simplify purchases with SwiftUI. + +A lightweight StoreKit2 wrapper designed to simplify purchases with SwiftUI. + +## How do I get started + +1. Define your in-app purchases (IAPs) or subscriptions by conforming to the `ProductRepresentable` protocol, as shown below: + +```swift +enum AppProduct: String, CaseIterable, ProductRepresentable { + + case iap1 = "yourApp.purchase1" + case iap2 = "yourApp.purchase2" + case subscription1 = "yourApp.subscription1" + case subscription2 = "yourApp.subscription2" + + func getId() -> String { + rawValue + } +} +``` + +2. Initialize `PurchaseHelper` + +```swift +import StoreKitHelper + +struct MainView: View { + @StateObject private var purchaseHelper = PurchaseHelper(products: AppProduct.allCases) + + var body: some View { + VStack { + ... + } + .environmentObject(purchaseHelper) + .onAppear { + // this will handle fetching purchases and sync owned purchases + purchaseHelper.fetchAndSync() + } + } +} +``` + +3. Display and handle purchases anywhere in the app. + +```swift +struct PaywallView: View { + @EnvironmentObject purchaseHelper: PurchaseHelper + + var body: some View { + VStack { + // get purchase info with all the product data + let purchase = purchaseHelper.getProduct(AppProduct.subscription1) + + // your view presenting the product data + PurchaseInfoView(purchase) + .onTapGesture { + purchaseHelper.purchase(AppProduct.subscription1) + } + } + } + .onChange(of: purchaseHelper.loadingInProgress) { newValue in + if purchaseHelper.isPurchased(AppProduct.subscription1) { + // Success! + } + } + } +} +``` + +Since `PurchaseHelper` is an `ObservableObject`, any change will automatically propagate to the views utilizing any of the public properties. + + +## Is That All? + +Yes, that’s all you need to get started! There’s no unnecessary boilerplate or overcomplicated abstractions. Everything runs smoothly behind the scenes. + +For more advanced configurations and details, check out the public properties and functions in the `PurchaseHelper` class. + + +## Installation + +To integrate `StoreKitHelper` into your project, add it via Swift Package Manager or manually. + +### Swift Package Manager +1. In Xcode, select `File > Add Packages`. +2. Enter the URL of the StoreKitHelper repository. +3. Choose the latest version and install. + + +## Contributing + +Missing something or have suggestions? Feel free to open a PR, and we’ll take a look! diff --git a/Sources/StoreKitHelper/ProductRepresentable.swift b/Sources/StoreKitHelper/ProductRepresentable.swift new file mode 100644 index 0000000..6a1ee5e --- /dev/null +++ b/Sources/StoreKitHelper/ProductRepresentable.swift @@ -0,0 +1,10 @@ +// +// ProductRepresentable.swift +// StoreKitHelper +// +// Created by Marcel Vojtkovszky on 2024-10-17. +// + +public protocol ProductRepresentable { + func getId() -> String +} diff --git a/Sources/StoreKitHelper/PurchaseHelper.swift b/Sources/StoreKitHelper/PurchaseHelper.swift new file mode 100644 index 0000000..1c98630 --- /dev/null +++ b/Sources/StoreKitHelper/PurchaseHelper.swift @@ -0,0 +1,186 @@ +// +// PurchaseHelper.swift +// StoreKitHelper +// +// Created by Marcel Vojtkovszky on 2024-10-17. +// + +import Foundation +import StoreKit +import Combine + +@MainActor +public class PurchaseHelper: ObservableObject { + + /// Indicated if products have been fetched (if `fetchProducts` or `fetchAndSync` has been called yet) + @Published private(set) public var productsFetched: Bool = false + /// Indicated if purchases have been synced (if `syncPurchases` or `fetchAndSync` has been called yet) + @Published private(set) public var purchasesSynced: Bool = false + /// Indicates if we have all products and purchases fetched, in practice meaning we can safely use all the methods + public var purchasesReady: Bool { + return productsFetched && purchasesSynced + } + /// Is `true` whenever products are fetching, syncyng purchases is in progress, or purchase is in progress - but only one of those at once + @Published private(set) public var loadingInProgress: Bool = false + + @Published private var purchasedProductIds: [String] = [] // active purchases + @Published private var products: [Product] = [] // StoreKit products + + private let allProductIds: [String] + private let storeKitCommunicator = StoreKitCommunicator() + + + /// Initialize helper + /// - Parameters: + /// - productIds: all product ids supported by the app. + /// - listenForUpdates: if set to true, listen for transaction updates otside of the app. this can generally be omitted If you call `fetchAndSync` every time main view appears, + init(products: [ProductRepresentable]) { + self.allProductIds = products.map { $0.getId() } + + Task { + await storeKitCommunicator.listenForTransactionUpdatesAsync { _ in + // result can be ignored, we just wanted to finish the transaction + } + } + } + + /// Determine if a given product has been purchased. + func isPurchased(_ product: ProductRepresentable) -> Bool { + guard purchasesSynced else { + print("PurchaseHelper purchases not synced yet. Call syncPurchases() first") + return false + } + return purchasedProductIds.contains(product.getId()) + } + + /// Get `StoreKit`product for given app product. + /// Make sure `fetchAndSync` or `fetchProducts` is called before that + func getProduct(_ product: ProductRepresentable) -> Product? { + guard productsFetched else { + print("PurchaseHelper products not fetched yet. Call fetchProducts() first") + return nil + } + return products.first { $0.id == product.getId() } + } + + /// Will initialize and handle fetching products and syncing purchases + /// Suggested to call this when view appears as it will guarantee `purchasesReady` to end up being `true` + func fetchAndSync() { + guard !loadingInProgress else { + print("PurchaseHelper purchase is in progress, fetchAndSync() ignored") + return + } + + print("PurchaseHelper fetchAndSync()") + self.loadingInProgress = true + let willFetchProducts = !self.productsFetched + let productIds = self.allProductIds + + Task { + if willFetchProducts { + await storeKitCommunicator.fetchProductsAsync(productIds: productIds) { products, error in + updateUI { [weak self] in + guard let self = self else { return } + if let products { + self.products = products + self.productsFetched = true + } + } + } + } + await storeKitCommunicator.syncPurchasesAsync { ids in + updateUI { [weak self] in + guard let self = self else { return } + self.purchasedProductIds = ids + self.purchasesSynced = true + } + } + updateUI { [weak self] in + guard let self = self else { return } + self.loadingInProgress = false + } + } + } + + /// Fetches products from the store. + /// - While the process is in progress, `loadingInProgress` will be `true` + func fetchProducts() { + guard !loadingInProgress else { + print("PurchaseHelper purchase is in progress, fetchProducts() ignored") + return + } + + print("PurchaseHelper fetchProducts()") + self.loadingInProgress = true + let productIds = self.allProductIds + + Task { + await storeKitCommunicator.fetchProductsAsync(productIds: productIds) { products, error in + updateUI { [weak self] in + guard let self = self else { return } + self.products = products ?? [] + self.productsFetched = true + self.loadingInProgress = false + } + } + } + } + + /// Synch owned purchases (entitlements) from the store + /// - While the process is in progress, `loadingInProgress` will be `true` + func syncPurchases() { + guard !loadingInProgress else { + print("PurchaseHelper purchase is in progress, syncPurchases() ignored") + return + } + + print("PurchaseHelper syncPurchases()") + self.loadingInProgress = true + + Task { + await storeKitCommunicator.syncPurchasesAsync { ids in + updateUI { [weak self] in + guard let self = self else { return } + self.purchasedProductIds = ids + self.purchasesSynced = true + self.loadingInProgress = false + } + } + } + } + + /// Init purchase of product + /// - Before the process starts, `purchaseFlowSuccess` will be set to `false` and set to `true` only upon successful purchase. + /// - While the process is in progress, `loadingInProgress` will be `true` + func purchase(_ product: ProductRepresentable, options: Set = []) { + guard !loadingInProgress else { + print("PurchaseHelper purchase is in progress, purchase() ignored") + return + } + + print("PurchaseHelper purchase \(product.getId())") + self.loadingInProgress = true + + if let storeProduct = getProduct(product) { + Task { + await storeKitCommunicator.purchaseAsync(product: storeProduct, options: options) { productId in + updateUI { [weak self] in + guard let self = self else { return } + if let productId, !self.purchasedProductIds.contains(productId) { + self.purchasedProductIds.append(productId) + } + self.loadingInProgress = false + } + } + } + } else { + print("PurchaseHelper no product found with id \(product.getId())") + } + } + + private func updateUI(_ updates: @escaping () -> Void) { + DispatchQueue.main.async { + updates() + } + } +} diff --git a/Sources/StoreKitHelper/StoreKitCommunicator.swift b/Sources/StoreKitHelper/StoreKitCommunicator.swift new file mode 100644 index 0000000..dead27e --- /dev/null +++ b/Sources/StoreKitHelper/StoreKitCommunicator.swift @@ -0,0 +1,75 @@ +// +// StoreKitCommunicator.swift +// StoreKitHelper +// +// Created by Marcel Vojtkovszky on 2024-10-17. +// + +import Foundation +import StoreKit + +internal class StoreKitCommunicator { + + func fetchProductsAsync(productIds: [String], callback: ([Product]?, Error?) -> Void) async { + do { + let products = try await Product.products(for: productIds) + print("PurchaseHelper products have been fetched \(products.map({ $0.id }))") + callback(products, nil) + } catch { + print("PurchaseHelper Error fetching products: \(error)") + callback(nil, error) + } + } + + func syncPurchasesAsync(callback: ([String]) -> Void) async { + var newPurchasedProductIds: [String] = [] + for await result in Transaction.currentEntitlements { + if case .verified(let transaction) = result { + newPurchasedProductIds.append(transaction.productID) + await transaction.finish() + } + } + print("PurchaseHelper syncPurchases complete, purchased products: \(newPurchasedProductIds)") + callback(newPurchasedProductIds) + } + + func purchaseAsync(product: Product, options: Set = [], callback: (String?) -> Void) async { + do { + let result = try await product.purchase(options: options) + switch result { + case .success(let verification): + switch verification { + case .verified(let transaction): + print("PurchaseHelper transaction verified for \(transaction.productID)") + await transaction.finish() + callback(transaction.productID) + case .unverified: + print("PurchaseHelper transaction unverified") + callback(nil) + } + case .userCancelled: + print("PurchaseHelper user canceled the purchase") + callback(nil) + case .pending: + print("PurchaseHelper purchase pending") + callback(nil) + @unknown default: + print("PurchaseHelper encountered an unknown purchase result") + callback(nil) + } + } catch { + print("PurchaseHelper failed: \(error)") + callback(nil) + } + } + + func listenForTransactionUpdatesAsync(callback: (String) -> Void) async { + for await result in Transaction.updates { + if case .verified(let transaction) = result { + print("PurchaseHelper transaction updated outside the app: \(transaction.productID)") + callback(transaction.productID) + await transaction.finish() + } + } + } +} diff --git a/StoreKitHelper.h b/StoreKitHelper.h new file mode 100644 index 0000000..f77825b --- /dev/null +++ b/StoreKitHelper.h @@ -0,0 +1,18 @@ +// +// StoreKitHelper.h +// StoreKitHelper +// +// Created by Marcel Vojtkovszky on 2024-10-17. +// + +#import + +//! Project version number for StoreKitHelper. +FOUNDATION_EXPORT double StoreKitHelperVersionNumber; + +//! Project version string for StoreKitHelper. +FOUNDATION_EXPORT const unsigned char StoreKitHelperVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/StoreKitHelper.xcodeproj/project.pbxproj b/StoreKitHelper.xcodeproj/project.pbxproj new file mode 100644 index 0000000..b2cbb20 --- /dev/null +++ b/StoreKitHelper.xcodeproj/project.pbxproj @@ -0,0 +1,377 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 1FF53C9D2CC135E100075EF8 /* StoreKitHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FF53C972CC135E100075EF8 /* StoreKitHelper.h */; settings = {ATTRIBUTES = (Public, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 1FA9A6CF2CC1355800825315 /* StoreKitHelper.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StoreKitHelper.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 1FF53C972CC135E100075EF8 /* StoreKitHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StoreKitHelper.h; sourceTree = ""; }; + 1FF53CAC2CC13ACA00075EF8 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 1FF53CA82CC13ABA00075EF8 /* Sources */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = Sources; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 1FA9A6CC2CC1355800825315 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 1FA9A6C52CC1355800825315 = { + isa = PBXGroup; + children = ( + 1FF53CAC2CC13ACA00075EF8 /* Package.swift */, + 1FF53C972CC135E100075EF8 /* StoreKitHelper.h */, + 1FF53CA82CC13ABA00075EF8 /* Sources */, + 1FA9A6D02CC1355800825315 /* Products */, + ); + sourceTree = ""; + }; + 1FA9A6D02CC1355800825315 /* Products */ = { + isa = PBXGroup; + children = ( + 1FA9A6CF2CC1355800825315 /* StoreKitHelper.framework */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 1FA9A6CA2CC1355800825315 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 1FF53C9D2CC135E100075EF8 /* StoreKitHelper.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 1FA9A6CE2CC1355800825315 /* StoreKitHelper */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1FA9A6D72CC1355800825315 /* Build configuration list for PBXNativeTarget "StoreKitHelper" */; + buildPhases = ( + 1FA9A6CA2CC1355800825315 /* Headers */, + 1FA9A6CB2CC1355800825315 /* Sources */, + 1FA9A6CC2CC1355800825315 /* Frameworks */, + 1FA9A6CD2CC1355800825315 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 1FF53CA82CC13ABA00075EF8 /* Sources */, + ); + name = StoreKitHelper; + packageProductDependencies = ( + ); + productName = StoreKitHelper; + productReference = 1FA9A6CF2CC1355800825315 /* StoreKitHelper.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 1FA9A6C62CC1355800825315 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastUpgradeCheck = 1600; + TargetAttributes = { + 1FA9A6CE2CC1355800825315 = { + CreatedOnToolsVersion = 16.0; + }; + }; + }; + buildConfigurationList = 1FA9A6C92CC1355800825315 /* Build configuration list for PBXProject "StoreKitHelper" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 1FA9A6C52CC1355800825315; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = 1FA9A6D02CC1355800825315 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 1FA9A6CE2CC1355800825315 /* StoreKitHelper */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 1FA9A6CD2CC1355800825315 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 1FA9A6CB2CC1355800825315 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1FA9A6D82CC1355800825315 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; + BUILD_LIBRARY_FOR_DISTRIBUTION = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = X825ZR9EK5; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = ( + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 15.5; + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = com.vojtkovszky.StoreKitHelper; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = auto; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + XROS_DEPLOYMENT_TARGET = 2.0; + }; + name = Debug; + }; + 1FA9A6D92CC1355800825315 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; + BUILD_LIBRARY_FOR_DISTRIBUTION = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = X825ZR9EK5; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = ( + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 15.5; + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = com.vojtkovszky.StoreKitHelper; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = auto; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + XROS_DEPLOYMENT_TARGET = 2.0; + }; + name = Release; + }; + 1FA9A6DA2CC1355800825315 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 1FA9A6DB2CC1355800825315 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SWIFT_COMPILATION_MODE = wholemodule; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1FA9A6C92CC1355800825315 /* Build configuration list for PBXProject "StoreKitHelper" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1FA9A6DA2CC1355800825315 /* Debug */, + 1FA9A6DB2CC1355800825315 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1FA9A6D72CC1355800825315 /* Build configuration list for PBXNativeTarget "StoreKitHelper" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1FA9A6D82CC1355800825315 /* Debug */, + 1FA9A6D92CC1355800825315 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 1FA9A6C62CC1355800825315 /* Project object */; +} diff --git a/StoreKitHelper.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/StoreKitHelper.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/StoreKitHelper.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/StoreKitHelper.xcodeproj/xcuserdata/marcel.xcuserdatad/xcschemes/xcschememanagement.plist b/StoreKitHelper.xcodeproj/xcuserdata/marcel.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..052a8e3 --- /dev/null +++ b/StoreKitHelper.xcodeproj/xcuserdata/marcel.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + StoreKitHelper.xcscheme_^#shared#^_ + + orderHint + 0 + + + +