├── !examples └── InAppPurchaseManager │ ├── InAppPurchaseManager.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── wangchujiang.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── InAppPurchaseManager.xcscheme │ └── xcuserdata │ │ └── wangchujiang.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist │ ├── InAppPurchaseManager │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── ContentView.swift │ ├── InAppPurchaseManager.entitlements │ ├── InAppPurchaseManagerApp.swift │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── SubscriptionView.swift │ └── store.swift │ └── Product.storekit ├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ ├── ci.yml │ └── tag.yml ├── .gitignore ├── CHANGELOG.md ├── README.md ├── README.zh.md ├── assets ├── logo.png ├── screenshots-1-all.png ├── screenshots-1-cn.png ├── screenshots-1.png ├── screenshots-2-cn.png ├── screenshots-2.png ├── screenshots-3-cn.png ├── screenshots-3.png └── social-preview.png ├── data └── explore.json ├── privacy-policy.md ├── privacy-policy.zh.md ├── renovate.json ├── terms-of-service.md └── terms-of-service.zh.md /!examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 7D4C0A642C3ED21B0050CB10 /* InAppPurchaseManagerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D4C0A632C3ED21B0050CB10 /* InAppPurchaseManagerApp.swift */; }; 11 | 7D4C0A662C3ED21B0050CB10 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D4C0A652C3ED21B0050CB10 /* ContentView.swift */; }; 12 | 7D4C0A682C3ED21E0050CB10 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7D4C0A672C3ED21E0050CB10 /* Assets.xcassets */; }; 13 | 7D4C0A6C2C3ED21E0050CB10 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7D4C0A6B2C3ED21E0050CB10 /* Preview Assets.xcassets */; }; 14 | 7D4C0A742C3ED2B70050CB10 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7D4C0A732C3ED2B70050CB10 /* StoreKit.framework */; }; 15 | 7D4C0A762C3ED3570050CB10 /* store.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D4C0A752C3ED3570050CB10 /* store.swift */; }; 16 | 7D4C0A792C3EDEB70050CB10 /* SubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D4C0A782C3EDEB70050CB10 /* SubscriptionView.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | 7D4C0A602C3ED21B0050CB10 /* InAppPurchaseManager.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = InAppPurchaseManager.app; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | 7D4C0A632C3ED21B0050CB10 /* InAppPurchaseManagerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchaseManagerApp.swift; sourceTree = ""; }; 22 | 7D4C0A652C3ED21B0050CB10 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 23 | 7D4C0A672C3ED21E0050CB10 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 24 | 7D4C0A692C3ED21E0050CB10 /* InAppPurchaseManager.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = InAppPurchaseManager.entitlements; sourceTree = ""; }; 25 | 7D4C0A6B2C3ED21E0050CB10 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 26 | 7D4C0A732C3ED2B70050CB10 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; 27 | 7D4C0A752C3ED3570050CB10 /* store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = store.swift; sourceTree = ""; }; 28 | 7D4C0A772C3ED5C20050CB10 /* Product.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Product.storekit; sourceTree = ""; }; 29 | 7D4C0A782C3EDEB70050CB10 /* SubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionView.swift; sourceTree = ""; }; 30 | /* End PBXFileReference section */ 31 | 32 | /* Begin PBXFrameworksBuildPhase section */ 33 | 7D4C0A5D2C3ED21B0050CB10 /* Frameworks */ = { 34 | isa = PBXFrameworksBuildPhase; 35 | buildActionMask = 2147483647; 36 | files = ( 37 | 7D4C0A742C3ED2B70050CB10 /* StoreKit.framework in Frameworks */, 38 | ); 39 | runOnlyForDeploymentPostprocessing = 0; 40 | }; 41 | /* End PBXFrameworksBuildPhase section */ 42 | 43 | /* Begin PBXGroup section */ 44 | 7D4C0A572C3ED21B0050CB10 = { 45 | isa = PBXGroup; 46 | children = ( 47 | 7D4C0A772C3ED5C20050CB10 /* Product.storekit */, 48 | 7D4C0A622C3ED21B0050CB10 /* InAppPurchaseManager */, 49 | 7D4C0A612C3ED21B0050CB10 /* Products */, 50 | 7D4C0A722C3ED2B70050CB10 /* Frameworks */, 51 | ); 52 | sourceTree = ""; 53 | }; 54 | 7D4C0A612C3ED21B0050CB10 /* Products */ = { 55 | isa = PBXGroup; 56 | children = ( 57 | 7D4C0A602C3ED21B0050CB10 /* InAppPurchaseManager.app */, 58 | ); 59 | name = Products; 60 | sourceTree = ""; 61 | }; 62 | 7D4C0A622C3ED21B0050CB10 /* InAppPurchaseManager */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | 7D4C0A752C3ED3570050CB10 /* store.swift */, 66 | 7D4C0A632C3ED21B0050CB10 /* InAppPurchaseManagerApp.swift */, 67 | 7D4C0A782C3EDEB70050CB10 /* SubscriptionView.swift */, 68 | 7D4C0A652C3ED21B0050CB10 /* ContentView.swift */, 69 | 7D4C0A672C3ED21E0050CB10 /* Assets.xcassets */, 70 | 7D4C0A692C3ED21E0050CB10 /* InAppPurchaseManager.entitlements */, 71 | 7D4C0A6A2C3ED21E0050CB10 /* Preview Content */, 72 | ); 73 | path = InAppPurchaseManager; 74 | sourceTree = ""; 75 | }; 76 | 7D4C0A6A2C3ED21E0050CB10 /* Preview Content */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 7D4C0A6B2C3ED21E0050CB10 /* Preview Assets.xcassets */, 80 | ); 81 | path = "Preview Content"; 82 | sourceTree = ""; 83 | }; 84 | 7D4C0A722C3ED2B70050CB10 /* Frameworks */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 7D4C0A732C3ED2B70050CB10 /* StoreKit.framework */, 88 | ); 89 | name = Frameworks; 90 | sourceTree = ""; 91 | }; 92 | /* End PBXGroup section */ 93 | 94 | /* Begin PBXNativeTarget section */ 95 | 7D4C0A5F2C3ED21B0050CB10 /* InAppPurchaseManager */ = { 96 | isa = PBXNativeTarget; 97 | buildConfigurationList = 7D4C0A6F2C3ED21E0050CB10 /* Build configuration list for PBXNativeTarget "InAppPurchaseManager" */; 98 | buildPhases = ( 99 | 7D4C0A5C2C3ED21B0050CB10 /* Sources */, 100 | 7D4C0A5D2C3ED21B0050CB10 /* Frameworks */, 101 | 7D4C0A5E2C3ED21B0050CB10 /* Resources */, 102 | ); 103 | buildRules = ( 104 | ); 105 | dependencies = ( 106 | ); 107 | name = InAppPurchaseManager; 108 | productName = InAppPurchaseManager; 109 | productReference = 7D4C0A602C3ED21B0050CB10 /* InAppPurchaseManager.app */; 110 | productType = "com.apple.product-type.application"; 111 | }; 112 | /* End PBXNativeTarget section */ 113 | 114 | /* Begin PBXProject section */ 115 | 7D4C0A582C3ED21B0050CB10 /* Project object */ = { 116 | isa = PBXProject; 117 | attributes = { 118 | BuildIndependentTargetsInParallel = 1; 119 | LastSwiftUpdateCheck = 1540; 120 | LastUpgradeCheck = 1540; 121 | TargetAttributes = { 122 | 7D4C0A5F2C3ED21B0050CB10 = { 123 | CreatedOnToolsVersion = 15.4; 124 | }; 125 | }; 126 | }; 127 | buildConfigurationList = 7D4C0A5B2C3ED21B0050CB10 /* Build configuration list for PBXProject "InAppPurchaseManager" */; 128 | compatibilityVersion = "Xcode 14.0"; 129 | developmentRegion = en; 130 | hasScannedForEncodings = 0; 131 | knownRegions = ( 132 | en, 133 | Base, 134 | ); 135 | mainGroup = 7D4C0A572C3ED21B0050CB10; 136 | productRefGroup = 7D4C0A612C3ED21B0050CB10 /* Products */; 137 | projectDirPath = ""; 138 | projectRoot = ""; 139 | targets = ( 140 | 7D4C0A5F2C3ED21B0050CB10 /* InAppPurchaseManager */, 141 | ); 142 | }; 143 | /* End PBXProject section */ 144 | 145 | /* Begin PBXResourcesBuildPhase section */ 146 | 7D4C0A5E2C3ED21B0050CB10 /* Resources */ = { 147 | isa = PBXResourcesBuildPhase; 148 | buildActionMask = 2147483647; 149 | files = ( 150 | 7D4C0A6C2C3ED21E0050CB10 /* Preview Assets.xcassets in Resources */, 151 | 7D4C0A682C3ED21E0050CB10 /* Assets.xcassets in Resources */, 152 | ); 153 | runOnlyForDeploymentPostprocessing = 0; 154 | }; 155 | /* End PBXResourcesBuildPhase section */ 156 | 157 | /* Begin PBXSourcesBuildPhase section */ 158 | 7D4C0A5C2C3ED21B0050CB10 /* Sources */ = { 159 | isa = PBXSourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | 7D4C0A762C3ED3570050CB10 /* store.swift in Sources */, 163 | 7D4C0A792C3EDEB70050CB10 /* SubscriptionView.swift in Sources */, 164 | 7D4C0A662C3ED21B0050CB10 /* ContentView.swift in Sources */, 165 | 7D4C0A642C3ED21B0050CB10 /* InAppPurchaseManagerApp.swift in Sources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXSourcesBuildPhase section */ 170 | 171 | /* Begin XCBuildConfiguration section */ 172 | 7D4C0A6D2C3ED21E0050CB10 /* Debug */ = { 173 | isa = XCBuildConfiguration; 174 | buildSettings = { 175 | ALWAYS_SEARCH_USER_PATHS = NO; 176 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 177 | CLANG_ANALYZER_NONNULL = YES; 178 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 179 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 180 | CLANG_ENABLE_MODULES = YES; 181 | CLANG_ENABLE_OBJC_ARC = YES; 182 | CLANG_ENABLE_OBJC_WEAK = YES; 183 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 184 | CLANG_WARN_BOOL_CONVERSION = YES; 185 | CLANG_WARN_COMMA = YES; 186 | CLANG_WARN_CONSTANT_CONVERSION = YES; 187 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 188 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 189 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 190 | CLANG_WARN_EMPTY_BODY = YES; 191 | CLANG_WARN_ENUM_CONVERSION = YES; 192 | CLANG_WARN_INFINITE_RECURSION = YES; 193 | CLANG_WARN_INT_CONVERSION = YES; 194 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 195 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 196 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 197 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 198 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 199 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 200 | CLANG_WARN_STRICT_PROTOTYPES = YES; 201 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 202 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 203 | CLANG_WARN_UNREACHABLE_CODE = YES; 204 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 205 | COPY_PHASE_STRIP = NO; 206 | DEBUG_INFORMATION_FORMAT = dwarf; 207 | ENABLE_STRICT_OBJC_MSGSEND = YES; 208 | ENABLE_TESTABILITY = YES; 209 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 210 | GCC_C_LANGUAGE_STANDARD = gnu17; 211 | GCC_DYNAMIC_NO_PIC = NO; 212 | GCC_NO_COMMON_BLOCKS = YES; 213 | GCC_OPTIMIZATION_LEVEL = 0; 214 | GCC_PREPROCESSOR_DEFINITIONS = ( 215 | "DEBUG=1", 216 | "$(inherited)", 217 | ); 218 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 219 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 220 | GCC_WARN_UNDECLARED_SELECTOR = YES; 221 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 222 | GCC_WARN_UNUSED_FUNCTION = YES; 223 | GCC_WARN_UNUSED_VARIABLE = YES; 224 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 225 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 226 | MTL_FAST_MATH = YES; 227 | ONLY_ACTIVE_ARCH = YES; 228 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 229 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 230 | }; 231 | name = Debug; 232 | }; 233 | 7D4C0A6E2C3ED21E0050CB10 /* Release */ = { 234 | isa = XCBuildConfiguration; 235 | buildSettings = { 236 | ALWAYS_SEARCH_USER_PATHS = NO; 237 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 238 | CLANG_ANALYZER_NONNULL = YES; 239 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 240 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 241 | CLANG_ENABLE_MODULES = YES; 242 | CLANG_ENABLE_OBJC_ARC = YES; 243 | CLANG_ENABLE_OBJC_WEAK = YES; 244 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 245 | CLANG_WARN_BOOL_CONVERSION = YES; 246 | CLANG_WARN_COMMA = YES; 247 | CLANG_WARN_CONSTANT_CONVERSION = YES; 248 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 249 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 250 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 251 | CLANG_WARN_EMPTY_BODY = YES; 252 | CLANG_WARN_ENUM_CONVERSION = YES; 253 | CLANG_WARN_INFINITE_RECURSION = YES; 254 | CLANG_WARN_INT_CONVERSION = YES; 255 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 256 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 257 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 258 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 259 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 260 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 261 | CLANG_WARN_STRICT_PROTOTYPES = YES; 262 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 263 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 264 | CLANG_WARN_UNREACHABLE_CODE = YES; 265 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 266 | COPY_PHASE_STRIP = NO; 267 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 268 | ENABLE_NS_ASSERTIONS = NO; 269 | ENABLE_STRICT_OBJC_MSGSEND = YES; 270 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 271 | GCC_C_LANGUAGE_STANDARD = gnu17; 272 | GCC_NO_COMMON_BLOCKS = YES; 273 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 274 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 275 | GCC_WARN_UNDECLARED_SELECTOR = YES; 276 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 277 | GCC_WARN_UNUSED_FUNCTION = YES; 278 | GCC_WARN_UNUSED_VARIABLE = YES; 279 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 280 | MTL_ENABLE_DEBUG_INFO = NO; 281 | MTL_FAST_MATH = YES; 282 | SWIFT_COMPILATION_MODE = wholemodule; 283 | }; 284 | name = Release; 285 | }; 286 | 7D4C0A702C3ED21E0050CB10 /* Debug */ = { 287 | isa = XCBuildConfiguration; 288 | buildSettings = { 289 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 290 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 291 | CODE_SIGN_ENTITLEMENTS = InAppPurchaseManager/InAppPurchaseManager.entitlements; 292 | CODE_SIGN_STYLE = Automatic; 293 | CURRENT_PROJECT_VERSION = 1; 294 | DEVELOPMENT_ASSET_PATHS = "\"InAppPurchaseManager/Preview Content\""; 295 | DEVELOPMENT_TEAM = GR99S2ZJZQ; 296 | ENABLE_HARDENED_RUNTIME = YES; 297 | ENABLE_PREVIEWS = YES; 298 | GENERATE_INFOPLIST_FILE = YES; 299 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 300 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 301 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 302 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 303 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 304 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 305 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; 306 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; 307 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 308 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 309 | IPHONEOS_DEPLOYMENT_TARGET = 17.5; 310 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 311 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 312 | MACOSX_DEPLOYMENT_TARGET = 14.5; 313 | MARKETING_VERSION = 1.0; 314 | PRODUCT_BUNDLE_IDENTIFIER = com.wangchujiang.InAppPurchaseManager; 315 | PRODUCT_NAME = "$(TARGET_NAME)"; 316 | SDKROOT = auto; 317 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 318 | SWIFT_EMIT_LOC_STRINGS = YES; 319 | SWIFT_VERSION = 5.0; 320 | TARGETED_DEVICE_FAMILY = "1,2"; 321 | }; 322 | name = Debug; 323 | }; 324 | 7D4C0A712C3ED21E0050CB10 /* Release */ = { 325 | isa = XCBuildConfiguration; 326 | buildSettings = { 327 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 328 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 329 | CODE_SIGN_ENTITLEMENTS = InAppPurchaseManager/InAppPurchaseManager.entitlements; 330 | CODE_SIGN_STYLE = Automatic; 331 | CURRENT_PROJECT_VERSION = 1; 332 | DEVELOPMENT_ASSET_PATHS = "\"InAppPurchaseManager/Preview Content\""; 333 | DEVELOPMENT_TEAM = GR99S2ZJZQ; 334 | ENABLE_HARDENED_RUNTIME = YES; 335 | ENABLE_PREVIEWS = YES; 336 | GENERATE_INFOPLIST_FILE = YES; 337 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 338 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 339 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 340 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 341 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 342 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 343 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; 344 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; 345 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 346 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 347 | IPHONEOS_DEPLOYMENT_TARGET = 17.5; 348 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 349 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 350 | MACOSX_DEPLOYMENT_TARGET = 14.5; 351 | MARKETING_VERSION = 1.0; 352 | PRODUCT_BUNDLE_IDENTIFIER = com.wangchujiang.InAppPurchaseManager; 353 | PRODUCT_NAME = "$(TARGET_NAME)"; 354 | SDKROOT = auto; 355 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 356 | SWIFT_EMIT_LOC_STRINGS = YES; 357 | SWIFT_VERSION = 5.0; 358 | TARGETED_DEVICE_FAMILY = "1,2"; 359 | }; 360 | name = Release; 361 | }; 362 | /* End XCBuildConfiguration section */ 363 | 364 | /* Begin XCConfigurationList section */ 365 | 7D4C0A5B2C3ED21B0050CB10 /* Build configuration list for PBXProject "InAppPurchaseManager" */ = { 366 | isa = XCConfigurationList; 367 | buildConfigurations = ( 368 | 7D4C0A6D2C3ED21E0050CB10 /* Debug */, 369 | 7D4C0A6E2C3ED21E0050CB10 /* Release */, 370 | ); 371 | defaultConfigurationIsVisible = 0; 372 | defaultConfigurationName = Release; 373 | }; 374 | 7D4C0A6F2C3ED21E0050CB10 /* Build configuration list for PBXNativeTarget "InAppPurchaseManager" */ = { 375 | isa = XCConfigurationList; 376 | buildConfigurations = ( 377 | 7D4C0A702C3ED21E0050CB10 /* Debug */, 378 | 7D4C0A712C3ED21E0050CB10 /* Release */, 379 | ); 380 | defaultConfigurationIsVisible = 0; 381 | defaultConfigurationName = Release; 382 | }; 383 | /* End XCConfigurationList section */ 384 | }; 385 | rootObject = 7D4C0A582C3ED21B0050CB10 /* Project object */; 386 | } 387 | -------------------------------------------------------------------------------- /!examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /!examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /!examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/project.xcworkspace/xcuserdata/wangchujiang.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaywcjlove/devtutor/d4c03e29c04073ca6b5e9d9eee9fce81b83c0383/!examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/project.xcworkspace/xcuserdata/wangchujiang.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /!examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/xcshareddata/xcschemes/InAppPurchaseManager.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 | -------------------------------------------------------------------------------- /!examples/InAppPurchaseManager/InAppPurchaseManager.xcodeproj/xcuserdata/wangchujiang.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | InAppPurchaseManager.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 7D4C0A5F2C3ED21B0050CB10 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /!examples/InAppPurchaseManager/InAppPurchaseManager/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 | -------------------------------------------------------------------------------- /!examples/InAppPurchaseManager/InAppPurchaseManager/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "1x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "2x", 16 | "size" : "16x16" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "1x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "2x", 26 | "size" : "32x32" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "2x", 36 | "size" : "128x128" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "1x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "2x", 46 | "size" : "256x256" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "1x", 51 | "size" : "512x512" 52 | }, 53 | { 54 | "idiom" : "mac", 55 | "scale" : "2x", 56 | "size" : "512x512" 57 | } 58 | ], 59 | "info" : { 60 | "author" : "xcode", 61 | "version" : 1 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /!examples/InAppPurchaseManager/InAppPurchaseManager/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /!examples/InAppPurchaseManager/InAppPurchaseManager/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // InAppPurchaseManager 4 | // 5 | // Created by 王楚江 on 2024/7/10. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | @EnvironmentObject private var entitlement: SubscriptionsManager 12 | @State var showingSubscriptionView = false 13 | var body: some View { 14 | VStack { 15 | if entitlement.hasPro == false { 16 | Button("Subscription Purchase") { 17 | showingSubscriptionView.toggle() 18 | } 19 | } else { 20 | Text("You have subscribed to purchase") 21 | } 22 | 23 | #if DEBUG 24 | let label = "清除 hasPro=\(entitlement.hasPro)" 25 | if entitlement.hasPro == true { 26 | Button(label) { 27 | entitlement.hasPro = false 28 | } 29 | } 30 | #endif 31 | } 32 | .padding() 33 | .sheet(isPresented: $showingSubscriptionView) { 34 | SubscriptionView() 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /!examples/InAppPurchaseManager/InAppPurchaseManager/InAppPurchaseManager.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 | -------------------------------------------------------------------------------- /!examples/InAppPurchaseManager/InAppPurchaseManager/InAppPurchaseManagerApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InAppPurchaseManagerApp.swift 3 | // InAppPurchaseManager 4 | // 5 | // Created by 王楚江 on 2024/7/10. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct InAppPurchaseManagerApp: App { 12 | @StateObject private var subscriptionsManager: SubscriptionsManager = SubscriptionsManager() 13 | var body: some Scene { 14 | WindowGroup { 15 | ContentView() 16 | .environmentObject(subscriptionsManager) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /!examples/InAppPurchaseManager/InAppPurchaseManager/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /!examples/InAppPurchaseManager/InAppPurchaseManager/SubscriptionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SubscriptionView.swift 3 | // InAppPurchaseManager 4 | // 5 | // Created by 王楚江 on 2024/7/10. 6 | // 7 | 8 | import SwiftUI 9 | import StoreKit 10 | 11 | struct SubscriptionView: View { 12 | @Environment(\.dismiss) var dismiss 13 | @EnvironmentObject private var subscriptions: SubscriptionsManager 14 | @State private var message: String = "" 15 | let privacyPolicy = URL(string: "https://wangchujiang.com/copybook-generator/privacy-policy.html")! 16 | let termsOfService = URL(string: "https://wangchujiang.com/copybook-generator/terms-of-service.html")! 17 | var body: some View { 18 | NavigationStack { 19 | if !subscriptions.products.isEmpty { 20 | VStack { 21 | SubscriptionStoreView(productIDs: subscriptions.productIDs) 22 | .storeButton(.visible, for: .policies) 23 | .subscriptionStorePolicyDestination(url: privacyPolicy, for: .privacyPolicy) 24 | .subscriptionStorePolicyDestination(url: termsOfService, for: .termsOfService) 25 | .onInAppPurchaseCompletion(perform: { product, result in 26 | if case .success(.success(let transaction)) = result { 27 | print("Purchased successfully: \(transaction.signedDate)") 28 | await subscriptions.updatePurchasedProducts() 29 | dismiss() 30 | } else { 31 | print("Something else happened") 32 | } 33 | }) 34 | Button(action: { 35 | Task { 36 | await subscriptions.restorePurchases() 37 | dismiss() 38 | } 39 | }, label: { 40 | Text("Restore Subscription") 41 | }) 42 | #if os(macOS) 43 | .buttonStyle(.link) 44 | #endif 45 | .offset(y: -22) 46 | } 47 | .background(.background) 48 | .frame(minWidth: 320, minHeight: 580) 49 | .frame(maxWidth: 450) 50 | } else { 51 | VStack { 52 | if message.isEmpty { 53 | ProgressView().progressViewStyle(.circular).scaleEffect(1).ignoresSafeArea(.all) 54 | } else { 55 | Text(message).foregroundStyle(.red) 56 | } 57 | } 58 | .padding(.horizontal) 59 | .frame(minWidth: 230, minHeight: 120) 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /!examples/InAppPurchaseManager/InAppPurchaseManager/store.swift: -------------------------------------------------------------------------------- 1 | // 2 | // store.swift 3 | // InAppPurchaseManager 4 | // 5 | // Created by 王楚江 on 2024/7/10. 6 | // 7 | 8 | import SwiftUI 9 | import StoreKit 10 | 11 | /// 管理订阅产品和购买记录 12 | @MainActor class SubscriptionsManager: NSObject, ObservableObject { 13 | /// 订阅产品的标识符数组 14 | let productIDs: [String] = [ 15 | "com.wangchujiang.InAppPurchaseManager.monthly", 16 | "com.wangchujiang.InAppPurchaseManager.yearly", 17 | "com.wangchujiang.InAppPurchaseManager.lifetime" 18 | ] 19 | /// 记录已购买的产品标识符集合 20 | var purchasedProductIDs: Set = [] 21 | /// UserDefaults 实例,用于存储权限状态 22 | static let userDefaults = UserDefaults(suiteName: "com.wangchujiang.InAppPurchaseManager.vip")! 23 | /// 使用 @AppStorage 将 hasPro 属性保存到 UserDefaults 中 24 | @AppStorage("hasPro", store: userDefaults) var hasPro: Bool = false 25 | /// 发布订阅产品信息 26 | @Published var products: [Product] = [] 27 | /// 更新任务 28 | private var updates: Task? = nil 29 | /// 初始化方法,接收 EntitlementManager 实例作为参数 30 | override init() { 31 | super.init() 32 | // 监听交易更新 33 | self.updates = observeTransactionUpdates() 34 | Task { 35 | await self.loadProducts(action: { err, success in 36 | 37 | }) 38 | } 39 | // 添加自身作为 SKPaymentTransactionObserver 40 | SKPaymentQueue.default().add(self) 41 | } 42 | // 析构方法,取消更新任务 43 | deinit { 44 | updates?.cancel() 45 | } 46 | // MARK: - 观察交易更新 47 | /// 异步观察交易更新 48 | func observeTransactionUpdates() -> Task { 49 | Task(priority: .background) { [unowned self] in 50 | for await _ in Transaction.updates { 51 | await self.updatePurchasedProducts() 52 | } 53 | } 54 | } 55 | } 56 | 57 | // MARK: - StoreKit2 API 扩展 58 | extension SubscriptionsManager { 59 | // MARK: - 加载产品列表 60 | /// 异步加载产品列表 61 | func loadProducts(action: ((_ err: String?, _ success: Bool) -> Void)?) async { 62 | do { 63 | // 使用 Product.products(for:) 加载产品信息,并按价格排序 64 | self.products = try await Product.products(for: productIDs).sorted(by: { $0.price > $1.price }) 65 | print("self.product: \(self.products)") 66 | action?(nil, true) 67 | } catch { 68 | let errorString = String(localized: "Failed to get the product! Please check the network! \n\(error.localizedDescription)") 69 | action?(errorString, false) 70 | } 71 | } 72 | // MARK: - 购买产品 73 | /// 异步购买产品 74 | func buyProduct(_ product: Product) async { 75 | do { 76 | // 使用 product.purchase() 进行产品购买 77 | let result = try await product.purchase() 78 | switch result { 79 | case let .success(.verified(transaction)): 80 | print("Successful purhcase 成功购买") 81 | // 完成交易并更新已购买产品 82 | await transaction.finish() 83 | await self.updatePurchasedProducts() 84 | case let .success(.unverified(_, error)): 85 | // Successful purchase but transaction/receipt can't be verified 86 | // Could be a jailbroken phone 87 | // 购买成功,但交易/收据无法验证 88 | // 可能是越狱手机 89 | print("Unverified purchase. Might be jailbroken. Error: \(error)") 90 | break 91 | case .pending: 92 | // Transaction waiting on SCA (Strong Customer Authentication) or approval from Ask to Buy 93 | // 等待 SCA(Strong Customer Authentication)或“要求购买”批准的交易 94 | break 95 | case .userCancelled: 96 | print("User cancelled!") 97 | break 98 | @unknown default: 99 | print("Failed to purchase the product!") 100 | break 101 | } 102 | } catch { 103 | print("Failed to purchase the product!") 104 | } 105 | } 106 | // MARK: - 更新已购买的产品 107 | /// 异步更新已购买的产品 108 | func updatePurchasedProducts() async { 109 | /// 一系列最新交易,使用户有权进行应用内购买和订阅。 110 | for await result in Transaction.currentEntitlements { 111 | guard case .verified(let transaction) = result else { continue } 112 | handleTransaction(transaction) 113 | } 114 | } 115 | // MARK: - 处理交易 116 | /// 处理交易 117 | private func handleTransaction(_ transaction: StoreKit.Transaction) { 118 | if transaction.revocationDate == nil { 119 | // 如果交易未被撤销,则将产品标识符添加到已购买集合中 120 | if !self.purchasedProductIDs.contains(transaction.productID) { 121 | self.purchasedProductIDs.insert(transaction.productID) 122 | } 123 | } else { 124 | // 如果交易被撤销,则从已购买集合中移除产品标识符 125 | self.purchasedProductIDs.remove(transaction.productID) 126 | } 127 | 128 | // 通过 productID 获取 Product 对象 129 | guard let product = products.first(where: { $0.id == transaction.productID }) else { 130 | // 处理 product 不存在的情况 131 | print("Product with ID \(transaction.productID) not found.") 132 | return 133 | } 134 | 135 | if let expirationDate = transaction.expirationDate, product.type == .autoRenewable { 136 | // 更新 EntitlementManager 的 hasPro 属性 137 | hasPro = isExpirationDate(expirationDate: expirationDate) 138 | } 139 | } 140 | 141 | // MARK: - 判断订阅是否过期 142 | /// 判断订阅是否过期 143 | func isExpirationDate(expirationDate: Date) -> Bool { 144 | return expirationDate > Date() // 如果 expirationDate 在 currentDate 之后,返回 true 表示未过期 145 | } 146 | // MARK: - 恢复购买 147 | /// 异步恢复购买 148 | func restorePurchases() async { 149 | do { 150 | // 同步应用内购买信息 151 | try await AppStore.sync() 152 | // 更新已购买的产品 153 | await updatePurchasedProducts() 154 | } catch { 155 | print("Restore purchases failed: \(error.localizedDescription)") 156 | } 157 | } 158 | } 159 | 160 | // MARK: - SKPaymentTransactionObserver 实现 161 | extension SubscriptionsManager: @preconcurrency SKPaymentTransactionObserver { 162 | // 支付队列更新交易 163 | func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { 164 | print("Subscriptions Payment Queue! updated!") 165 | } 166 | // 应用内购买准备添加到支付队列 167 | func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool { 168 | print("Subscriptions Payment Queue! Should Add Store Payment!") 169 | return true 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /!examples/InAppPurchaseManager/Product.storekit: -------------------------------------------------------------------------------- 1 | { 2 | "identifier" : "8F58A673", 3 | "nonRenewingSubscriptions" : [ 4 | 5 | ], 6 | "products" : [ 7 | 8 | ], 9 | "settings" : { 10 | "_failTransactionsEnabled" : false, 11 | "_locale" : "en_US", 12 | "_storefront" : "USA", 13 | "_storeKitErrors" : [ 14 | { 15 | "current" : null, 16 | "enabled" : false, 17 | "name" : "Load Products" 18 | }, 19 | { 20 | "current" : null, 21 | "enabled" : false, 22 | "name" : "Purchase" 23 | }, 24 | { 25 | "current" : null, 26 | "enabled" : false, 27 | "name" : "Verification" 28 | }, 29 | { 30 | "current" : null, 31 | "enabled" : false, 32 | "name" : "App Store Sync" 33 | }, 34 | { 35 | "current" : null, 36 | "enabled" : false, 37 | "name" : "Subscription Status" 38 | }, 39 | { 40 | "current" : null, 41 | "enabled" : false, 42 | "name" : "App Transaction" 43 | }, 44 | { 45 | "current" : null, 46 | "enabled" : false, 47 | "name" : "Manage Subscriptions Sheet" 48 | }, 49 | { 50 | "current" : null, 51 | "enabled" : false, 52 | "name" : "Refund Request Sheet" 53 | }, 54 | { 55 | "current" : null, 56 | "enabled" : false, 57 | "name" : "Offer Code Redeem Sheet" 58 | } 59 | ] 60 | }, 61 | "subscriptionGroups" : [ 62 | { 63 | "id" : "2F793903", 64 | "localizations" : [ 65 | 66 | ], 67 | "name" : "VIP Pro Example", 68 | "subscriptions" : [ 69 | { 70 | "adHocOffers" : [ 71 | 72 | ], 73 | "codeOffers" : [ 74 | 75 | ], 76 | "displayPrice" : "0.99", 77 | "familyShareable" : false, 78 | "groupNumber" : 1, 79 | "internalID" : "FF45B2EF", 80 | "introductoryOffer" : null, 81 | "localizations" : [ 82 | { 83 | "description" : "All Access Monthly", 84 | "displayName" : "Pro Monthly", 85 | "locale" : "en_US" 86 | }, 87 | { 88 | "description" : "每月全部访问", 89 | "displayName" : "专业月会员", 90 | "locale" : "zh_CN" 91 | } 92 | ], 93 | "productID" : "com.wangchujiang.InAppPurchaseManager.monthly", 94 | "recurringSubscriptionPeriod" : "P1M", 95 | "referenceName" : "Pro Monthly", 96 | "subscriptionGroupID" : "2F793903", 97 | "type" : "RecurringSubscription" 98 | }, 99 | { 100 | "adHocOffers" : [ 101 | 102 | ], 103 | "codeOffers" : [ 104 | 105 | ], 106 | "displayPrice" : "12.99", 107 | "familyShareable" : false, 108 | "groupNumber" : 1, 109 | "internalID" : "908E4B0F", 110 | "introductoryOffer" : null, 111 | "localizations" : [ 112 | { 113 | "description" : "All Access Annually", 114 | "displayName" : "Pro Yearly", 115 | "locale" : "en_US" 116 | }, 117 | { 118 | "description" : "每年全部访问", 119 | "displayName" : "专业年会员", 120 | "locale" : "zh_CN" 121 | } 122 | ], 123 | "productID" : "com.wangchujiang.InAppPurchaseManager.yearly", 124 | "recurringSubscriptionPeriod" : "P1M", 125 | "referenceName" : "Pro Yearly", 126 | "subscriptionGroupID" : "2F793903", 127 | "type" : "RecurringSubscription" 128 | }, 129 | { 130 | "adHocOffers" : [ 131 | 132 | ], 133 | "codeOffers" : [ 134 | 135 | ], 136 | "displayPrice" : "66.99", 137 | "familyShareable" : false, 138 | "groupNumber" : 1, 139 | "internalID" : "26539A2F", 140 | "introductoryOffer" : null, 141 | "localizations" : [ 142 | { 143 | "description" : "All Access Lifetime", 144 | "displayName" : "Pro Lifetime", 145 | "locale" : "en_US" 146 | }, 147 | { 148 | "description" : "全部终生访问", 149 | "displayName" : "专业终生版", 150 | "locale" : "zh_CN" 151 | } 152 | ], 153 | "productID" : "com.wangchujiang.InAppPurchaseManager.lifetime", 154 | "recurringSubscriptionPeriod" : "P1M", 155 | "referenceName" : "Pro Lifetime", 156 | "subscriptionGroupID" : "2F793903", 157 | "type" : "RecurringSubscription" 158 | } 159 | ] 160 | } 161 | ], 162 | "version" : { 163 | "major" : 3, 164 | "minor" : 0 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # *.md linguist-language=Swift 2 | *.json linguist-language=Swift 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: jaywcjlove 2 | buy_me_a_coffee: jaywcjlove 3 | custom: ["https://www.paypal.me/kennyiseeyou", "https://jaywcjlove.github.io/#/sponsor"] 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI/CD 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | build-deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: actions/setup-node@v4 13 | with: 14 | node-version: 20 15 | registry-url: 'https://registry.npmjs.org' 16 | 17 | - name: Create Tag 18 | id: create_tag 19 | uses: jaywcjlove/create-tag-action@main 20 | with: 21 | test: '[R|r]elease[d]\s+[v|V]\d(\.\d+){0,2}' 22 | 23 | - name: Create Released Tag 24 | uses: jaywcjlove/create-tag-action@main 25 | with: 26 | version: ${{ steps.create_tag.outputs.version }} 27 | release: true 28 | body: | 29 |

30 | DevTutor for SwiftUI AppStore 31 | 32 | DevTutor for SwiftUI AppStore iOS 33 | 34 |

35 | 36 | - name: Get latest tag 37 | id: get_latest_tag 38 | run: echo "LATEST_TAG=$(git describe --tags $(git rev-list --tags --max-count=1))" >> $GITHUB_ENV 39 | 40 | - name: Create idoc config. 41 | run: | 42 | cat > idoc.yml << EOF 43 | site: "DevTutor for SwiftUI {{version:${{ steps.create_tag.outputs.version }}}}" 44 | description: DevTutor is an application designed to assist developers in creating exceptional apps using SwiftUI 45 | keywords: SwiftUI,devtutor,swift,Tutorial app,Developer,tools,Coding,document 46 | favicon: assets/logo.png 47 | logo: ./assets/logo.png 48 | openSource: https://github.com/jaywcjlove/devtutor 49 | homepage: https://wangchujiang.com/devtutor/ 50 | tocs: false 51 | element: 52 | wrapper: style=max-width:720px; 53 | meta: 54 | - 55 | - 56 | - 57 | - 58 | - 59 | - 60 | - 61 | - 62 | - 63 | - 64 | - 65 | - 66 | - 67 | - 68 | menus: 69 | Home: index.html 70 | Apps: 71 | url: https://wangchujiang.com/#/app 72 | target: __blank 73 | About: 74 | url: https://wangchujiang.com/#/about 75 | target: __blank 76 | sideEffectFiles: 77 | - README.zh.md 78 | - terms-of-service.md 79 | - terms-of-service.zh.md 80 | - privacy-policy.md 81 | - privacy-policy.zh.md 82 | footer: | 83 | Terms of Service • 84 | Privacy Policy • 85 | Sponsor • 86 | More Apps

87 | Generated by idoc v{{idocVersion}} 88 | EOF 89 | 90 | - run: npm install idoc@1 -g 91 | - run: idoc 92 | - run: cp -rp assets/social-preview.png dist/assets/ 93 | 94 | - name: Deploy 95 | uses: peaceiris/actions-gh-pages@v4 96 | with: 97 | commit_message: ${{ github.event.head_commit.message }} 98 | github_token: ${{ secrets.GITHUB_TOKEN }} 99 | publish_dir: ./dist -------------------------------------------------------------------------------- /.github/workflows/tag.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | # branches: 5 | # - main 6 | tags: 7 | - v* 8 | 9 | jobs: 10 | tags: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: write 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Extract version from tag 18 | id: extract_version 19 | run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV 20 | 21 | - name: Create Tag 22 | id: create_tag 23 | uses: jaywcjlove/create-tag-action@main 24 | with: 25 | version: ${{ env.VERSION }} 26 | release: true 27 | body: | 28 |

29 | DevTutor for macOS 30 | DevTutor for SwiftUI for iOS iOS 31 |

-------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | 3 | .DS_Store 4 | .cache 5 | .vscode 6 | .idea 7 | 8 | *.bak 9 | *.tem 10 | *.temp 11 | #.swp 12 | *.*~ 13 | ~*.* 14 | 15 | # IDEA 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | === 3 | 4 | DevTutor AppStore 5 | 6 | DevTutor for SwiftUI AppStore iOS 7 | 8 | 9 | ## [1.22.0](https://github.com/jaywcjlove/devtutor/releases/tag/v1.22.0) 10 | 11 | 1. feat: Add `StoreKitHelper` documentation. 12 | 2. chore: Update main menu. 13 | 3. fix: Fix incorrect receipt existence check. 14 | 4. feat: Add update check functionality. 15 | 16 | --- 17 | 18 | 1. feat: 添加 `StoreKitHelper` 文档。 19 | 2. chore: 更新主菜单。 20 | 3. fix: 修复错误的收据存在性检查。 21 | 4. feat: 添加更新检查功能。 22 | 23 | ## [1.21.0](https://github.com/jaywcjlove/devtutor/releases/tag/v1.21.0) 24 | 25 | 1. Updated `hsplitview` document. 26 | 2. Updated `store-app-store-complete-example` document. 27 | 3. Updated `cheatsheets while` document. 28 | 4. Updated `cheatsheets sets` document. 29 | 5. Updated `cheatsheets extensions` document. 30 | 6. Updated `cheatsheets text` document. 31 | 7. Updated `cheatsheets print` document. 32 | 33 | --- 34 | 35 | 1. 更新 `hsplitview` 文档。 36 | 2. 更新 `store-app-store-complete-example` 文档。 37 | 3. 更新 `cheatsheets while` 文档。 38 | 4. 更新 `cheatsheets sets` 文档。 39 | 5. 更新 `cheatsheets extensions` 文档。 40 | 6. 更新 `cheatsheets text` 文档。 41 | 7. 更新 `cheatsheets print` 文档。 42 | 43 | ## [1.20.0](https://github.com/jaywcjlove/devtutor/releases/tag/v1.20.0) 44 | 45 | 1. Doc: update store-share-purchases document. 46 | 2. Doc: update store-app-store-complete-example document. 47 | 3. Doc: update deleting-data document. 48 | 4. Doc: update cheatsheets-rectanglemark.zh document. 49 | 5. Fix: optimize the payment failure handling logic. 50 | 6. Feat: update command menu. 51 | 52 | --- 53 | 54 | 1. 文档:更新 store-share-purchases 文档。 55 | 2. 文档:更新 store-app-store-complete-example 文档。 56 | 3. 文档:更新 deleting-data 文档。 57 | 4. 文档:更新 cheatsheets-rectanglemark.zh 文档。 58 | 5. 修复:优化支付失败处理逻辑。 59 | 6. 功能:更新命令菜单。 60 | 61 | ## [1.19.0](https://github.com/jaywcjlove/devtutor/releases/tag/v1.19.0) 62 | 63 | 1. feat: add swiftdata-model-migration document 64 | 2. doc: update swiftdata-querying-data document 65 | 3. doc: update swiftdata-deleting-data document 66 | 67 | --- 68 | 69 | 1. feat: 添加 swiftdata 模型迁移文档 70 | 2. doc: 更新 swiftdata 数据查询文档 71 | 3. doc: 更新 swiftdata 数据删除文档 72 | 73 | ## [1.18.0](https://github.com/jaywcjlove/devtutor/releases/tag/v1.18.0) 74 | 75 | 1. feat: Add bookmark feature. 76 | 2. fix: Resolve incorrect app icon display issue. 77 | 3. fix: Resolve TOCs display issue. 78 | 79 | --- 80 | 81 | 1. 新功能: 添加书签功能。 82 | 2. 修复: 修复应用图标显示错误问题。 83 | 3. 修复: 修复 TOCs 展示问题。 84 | 85 | ## [1.17.0](https://github.com/jaywcjlove/devtutor/releases/tag/v1.17.0) 86 | 87 | 1. Added Xcode shortcuts cheatsheet 88 | 2. Added UIKit and AppKit integration guide 89 | 3. Updated lists cheatsheet document 90 | 4. Updated SwiftData model definition document 91 | 92 | --- 93 | 94 | 1. 添加 Xcode 快捷键备忘单 95 | 2. 添加 UIKit 和 AppKit 集成指南 96 | 3. 更新列表备忘单文档 97 | 4. 更新 SwiftData 模型定义文档 98 | 99 | ## [1.16.0](https://github.com/jaywcjlove/devtutor/releases/tag/v1.16.0) 100 | 101 | 1. doc: update swift-guide-errorhandling 102 | 2. doc: update swift-guide-language-reference-attributes 103 | 3. doc: update swift-welcome-revisionhistory 104 | 4. doc: update swift-guide-language-reference-expressions 105 | 106 | --- 107 | 108 | 1. 文档:更新 Swift 指南 - 错误处理 109 | 2. 文档:更新 Swift 指南 - 语言参考 - 属性 110 | 3. 文档:更新 Swift 欢迎 - 修订历史 111 | 4. 文档:更新 Swift 指南 - 语言参考 - 表达式 112 | 113 | ## 1.15 114 | 115 | 1. feat: add SwiftData document. 116 | 2. fix: fix email address error. 117 | 3. doc: update application-I18n.md 118 | 4. doc: update cheatsheets-progressview document. 119 | 5. doc: update cheatsheets-shape document. 120 | 121 | --- 122 | 123 | 1. 功能:添加 SwiftData 文档。 124 | 2. 修复:修复电子邮件地址错误。 125 | 3. 文档:更新 application-I18n.md。 126 | 4. 文档:更新 cheatsheets-progressview 文档。 127 | 5. 文档:更新 cheatsheets-shape 文档。 128 | 129 | ## 1.14 130 | 131 | 1. doc: update guide-language-reference-statements.zh 132 | 2. doc: update guide-controlflow.zh 133 | 3. doc: update cheatsheets-application-i18n 134 | 4. doc: update guide-language-reference-patterns.zh 135 | 5. style: update detail pages styles. 136 | 6. doc: update arrays document. 137 | 138 | --- 139 | 140 | 1. 文档:更新指南语言参考-语句(zh) 141 | 2. 文档:更新指南-控制流(zh) 142 | 3. 文档:更新速查表-应用程序国际化 143 | 4. 文档:更新指南语言参考-模式(zh) 144 | 5. 样式:更新详细页面样式 145 | 6. 文档:更新数组文档 146 | 147 | ## 1.13 148 | 149 | 1. doc: update operationqueue document 150 | 2. doc: update button document 151 | 3. doc: update sheet document 152 | 4. doc: update popover document 153 | 5. fix: fix typo 154 | 155 | --- 156 | 157 | 1. 更新 operationqueue 文档 158 | 2. 更新 button 文档 159 | 3. 更新 sheet 文档 160 | 4. 更新 popover 文档 161 | 162 | ## 1.12 163 | 164 | 1. feat: add controlgroup document. 165 | 2. doc: update cheatsheets menus. 166 | 3. doc: update text document. 167 | 4. doc: update application-i18n. 168 | 5. doc: update linemark. 169 | 170 | --- 171 | 172 | 1. 新增 controlgroup 文档 173 | 2. 更新 cheatsheets 菜单 174 | 3. 更新 text 文档 175 | 4. 更新 application-i18n 文档 176 | 5. 更新 linemark 文档 177 | 178 | ## 1.11 179 | 180 | 1. Add in-app purchase tutorial with images. 181 | 182 | --- 183 | 184 | 1. 添加应用内购买图文教程 185 | 186 | ## 1.10 187 | 188 | 1. doc: update print document. 189 | 2. feat: add canvas document. 190 | 3. doc: update functional-methods-comparison document. 191 | 4. doc: update flatmap document. 192 | 193 | --- 194 | 195 | 1. 新增 canvas 文档 196 | 2. 更新 print 文档 197 | 3. 更新 functional-methods-comparison 文档 198 | 4. 更新 flatmap 文档 199 | 200 | ## 1.9 201 | 202 | 1. fix: fix toc issue. 203 | 2. feat: add map document. 204 | 3. doc: update properties document. 205 | 4. doc: update closures document. 206 | 5. doc: update for-in/while document 207 | 6. doc: update detect-os-version document. 208 | 7. doc: update switch document. 209 | 210 | --- 211 | 212 | 1. 修复文章目录问题 213 | 2. 新增地图 map 文档 214 | 3. 更新 properties 文档示例 215 | 4. 更新 closures 文档示例 216 | 5. 更新 for-in/while 文档示例 217 | 6. 更新 detect-os-version 文档示例 218 | 7. 更新 switch 文档示例 219 | 220 | ## 1.8 221 | 222 | 1. doc: update guide-accesscontrol & reference-expressions & Updated for Swift 6 223 | 2. feat: add tocs feature 224 | 3. feat: add `SignInWithAppleButton` 225 | 4. feat: add `LocalAuthenticationView`. 226 | 5. doc: update text, lists, subscriptionstoreview, sharelink, subscriptionstoreview, differentiate, form, forEach 227 | 6. doc: update cheatsheets-detect-os-version document. 228 | 7. doc: update variables document. 229 | 230 | --- 231 | 232 | 1. 文档更新 accesscontrol & reference-expressions & 更新为 Swift 6 233 | 2. 新增文章目录 234 | 3. 新增苹果登录按钮视图文档 235 | 4. 新增生物识别验证视图 236 | 5. 文档更新 text, lists, subscriptionstoreview, sharelink, subscriptionstoreview, differentiate, form, forEach 237 | 6. 更新 cheatsheets-detect-os-version 文档更新 238 | 7. 更新 variables 文档 239 | 240 | ## 1.7 241 | 242 | 1. add Swift Cheat Sheets document. 243 | 244 | --- 245 | 246 | 1. 添加 Swift 速查表文档。 247 | 248 | ## 1.6 249 | 250 | 1. doc: update dialog document 251 | 2. doc: update divider document 252 | 3. doc: update lexicalstructure document 253 | 4. doc: update summaryofthegrammar document 254 | 5. doc: update types document 255 | 6. doc: update memorysafety document 256 | 7. doc: update protocols document 257 | 8. doc: update text document 258 | 9. doc: update subscriptionstoreview document 259 | 10. doc: update inspector document 260 | 11. doc: update geometryreader document 261 | 12. feat: add right-click menu to sidebar. 262 | 13. feat: update packages data. 263 | 264 | --- 265 | 266 | 1. 更新 dialog 文档 267 | 2. 更新 divider 文档 268 | 3. 更新 lexicalstructure 文档 269 | 4. 更新 summaryofthegrammar 文档 270 | 5. 更新 types 文档 271 | 6. 更新 memorysafety 文档 272 | 7. 更新 protocols 文档 273 | 8. 更新 text 文档 274 | 9. 更新 subscriptionstoreview 文档 275 | 10. 更新 inspector 文档 276 | 11. 更新 geometryreader 文档 277 | 12. 添加右键菜单到侧边栏 278 | 13. 更新第三方包数据 279 | 280 | ## 1.5 281 | 282 | 1. add ViewThatFits document 283 | 2. add Inspector document 284 | 3. update Menu document 285 | 286 | --- 287 | 288 | 1. 添加 ViewThatFits 文档 289 | 2. 添加 Inspector 文档 290 | 3. 更新 Menu 文档 291 | 292 | ## 1.4 293 | 294 | 1. add AreaMark document. 295 | 2. add BarMark document. 296 | 3. add Chart document. 297 | 4. add LineMark document. 298 | 5. add PointMark document. 299 | 6. add RectangleMark document. 300 | 7. add RuleMark document. 301 | 8. add SectorMark document. 302 | 9. update SubscriptionStoreView document. 303 | 10. add StoreView document. 304 | 11. add ProductView document. 305 | 12. add ContentUnavailableView document. 306 | 307 | --- 308 | 309 | 1. 添加 AreaMark 文档 310 | 2. 添加 BarMark 文档 311 | 3. 添加 Chart 文档 312 | 4. 添加 LineMark 文档 313 | 5. 添加 PointMark 文档 314 | 6. 添加 RectangleMark 文档 315 | 7. 添加 RuleMark 文档 316 | 8. 添加 SectorMark 文档 317 | 9. 更新 SubscriptionStoreView 文档 318 | 10. 添加 StoreView 文档 319 | 11. 添加 ProductView 文档 320 | 12. 添加 ContentUnavailableView 文档 321 | 322 | ## 1.3 323 | 324 | 1. add search feature 325 | 2. add Search document 326 | 3. add labeledcontent document 327 | 4. add form document 328 | 5. add application-I18n document 329 | 6. update detect-language document 330 | 331 | --- 332 | 333 | 1. 添加搜索功能 334 | 2. 添加 Search 文档 335 | 3. 添加 labeledcontent 文档 336 | 4. 添加 form 文档 337 | 5. 添加 application-I18n 文档 338 | 6. 更新 detect-language 文档 339 | 340 | ## 1.2 341 | 342 | 1. add table document. 343 | 2. add transition document 344 | 3. add withanimation document 345 | 4. add phaseanimator document 346 | 5. add detect-language document 347 | 6. add animation document 348 | 7. update tapgesture document 349 | 350 | --- 351 | 352 | 1. 添加 table 文档 353 | 2. 添加 transition 文档 354 | 3. 添加 withanimation 文档 355 | 4. 添加 phaseanimator 文档 356 | 5. 添加 detect-language 文档 357 | 6. 添加 animation 文档 358 | 7. 更新 tapgesture 文档 359 | 360 | ## 1.1 361 | 362 | 1. feat: update attributes/statements document 363 | 2. doc: update pass-a-view/outlinegroup/detect-dark-mode cheatsheets 364 | 3. feat: add path/gesture/dialog/asyncimag cheatsheets 365 | 4. fix: fix sidebar scroll issue 366 | 367 | --- 368 | 369 | 1. 更新 attributes/statements 文档 370 | 2. 更新 pass-a-view/outlinegroup/detect-dark-mode 速查手册 371 | 3. 新增 path/gesture/dialog/asyncimag 速查手册 372 | 4. 修复侧边栏滚动列表定位问题 373 | 374 | ## 1.0 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | DevTutor for SwiftUI LOGO 5 |

DevTutor for SwiftUI

6 | 7 |

8 | 中文 • 9 | FAQ • 10 | Contact & Support • 11 | Changelog 12 |

13 |

14 | DevTutor for SwiftUI AppStore 15 | 16 | DevTutor for SwiftUI AppStore iOS 17 | 18 |

19 |
20 | 21 |
22 | 23 | minimum OS requirement: `macOS 14.0` / `iOS 17.0` 24 | 25 |
26 | 27 | "DevTutor for SwiftUI" is an application designed to assist developers in creating exceptional apps using SwiftUI. It offers replicable code samples and corresponding UI previews to streamline your coding process. Additionally, it includes local offline access to the official Swift Programming Language documentation in both Chinese and English. 28 | 29 | ![DevTutor for SwiftUI screenshots-1](./assets/screenshots-1-all.png) 30 | 31 | ## Main Features 32 | 33 | - Provide sample code that can be used directly in your project 34 | - View in real time how your code affects the appearance of the application 35 | - Provide official bilingual Chinese and English Swift programming language documentation for offline use 36 | - Explore a collection of third-party packages, see [Explore Data](https://github.com/jaywcjlove/swiftui-tutorial/blob/main/data/explore.json) 37 | - And more features... 38 | 39 | ![DevTutor for SwiftUI screenshots-1](./assets/screenshots-1.png) 40 | 41 | ![DevTutor for SwiftUI screenshots-2](./assets/screenshots-2.png) 42 | 43 | ![DevTutor for SwiftUI screenshots-3](./assets/screenshots-3.png) 44 | 45 | ## About SwiftUI 46 | 47 | SwiftUI is an innovative, declarative UI framework developed by Apple for building interfaces across iOS, macOS, watchOS, and tvOS. Launched in 2019, its goal is to simplify the development of cross-platform applications. Using the Swift programming language, developers can describe the elements and layout of user interfaces declaratively. 48 | 49 | ## Frequently Asked Questions 50 | 51 | ### Who is it for? 52 | 53 | SwiftUI is suitable for developers of all levels, from beginners to experienced professionals, all of whom can benefit from it. Here are some groups that might find learning and using SwiftUI particularly advantageous: 54 | 55 | 1. **Beginner Developers**: SwiftUI's declarative syntax is easier to learn compared to traditional imperative programming because it's more intuitive. Developers just need to describe what the UI should look like, not how to implement it. This makes it easier for beginners to get started and see results quickly. 56 | 2. **Experienced iOS/macOS Developers**: For those already familiar with the Apple ecosystem and Swift language, SwiftUI offers a more modern, efficient way to build user interfaces. It can also help them streamline their existing UIKit or AppKit code towards a more modular and reusable design. 57 | 3. **Cross-Platform Application Developers**: Developers planning to create applications that run across iOS, macOS, watchOS, and tvOS will benefit from SwiftUI's cross-platform capabilities. Using the same codebase can significantly reduce development and maintenance costs. 58 | 4. **UI/UX Designers**: For designers looking to translate designs directly into code, SwiftUI's real-time previews and declarative syntax allow them to iterate designs quickly and see their implementation in real-time. 59 | 5. **Professionals Looking to Increase Productivity**: SwiftUI's efficient features, such as real-time previews and declarative syntax, can significantly speed up development time and reduce debugging, allowing professionals to release products faster. 60 | 61 | In summary, SwiftUI's design philosophy and tool support make it a widely popular framework, valuable to anyone interested in programming, whether they are beginners or experienced professionals looking to enhance development efficiency and quality. 62 | 63 | ### What can it be used for? 64 | 65 | SwiftUI can be used to develop a wide variety of applications for Apple's multiple platforms, including iOS, macOS, watchOS, and tvOS. Here are some specific types of applications that can be developed using SwiftUI: 66 | 67 | 1. **Mobile and Tablet Applications**: With SwiftUI, developers can create beautiful, responsive applications for iPhones and iPads. Whether it's social media apps, e-commerce platforms, or health and fitness trackers, SwiftUI provides all the necessary interface components and animations. 68 | 2. **Desktop Applications**: macOS applications can also be built using SwiftUI. These applications can range from simple utility software to complex editing tools, with SwiftUI providing everything needed to create professional-level desktop applications. 69 | 3. **Wearable Device Applications**: For Apple Watch, SwiftUI allows developers to design interactions for small screens. These applications are often used for quick information displays, health monitoring, or as an extension of smartphone applications. 70 | 4. **TV Applications**: On Apple TV, developers can use SwiftUI to create large-screen entertainment and multimedia applications, such as streaming apps or games. 71 | 5. **Multi-Platform Applications**: SwiftUI's cross-platform features enable developers to create a unified application experience across all of Apple's devices, simplifying the development process and ensuring consistency across platforms. 72 | 6. **Educational and Training Applications**: Educational applications can also leverage SwiftUI's interactivity and appeal to provide dynamic learning experiences, such as interactive textbooks and learning games. 73 | 7. **Corporate Internal Applications**: Companies can develop applications for internal use, such as employee communications, project management tools, or customer relationship management systems, all optimized through SwiftUI's design and functionality. 74 | 8. **Prototype Design and Testing**: SwiftUI's rapid iteration capabilities and real-time previews make it an ideal choice for quick prototype design. Designers and developers can quickly build and test interface designs, accelerating the development process. 75 | 76 | SwiftUI's flexibility and ease of use make it a powerful tool for developing modern, responsive applications. Whether for personal projects or professional development, SwiftUI meets a variety of needs and expectations. 77 | 78 | 79 | -------------------------------------------------------------------------------- /README.zh.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | DevTutor LOGO 5 |

DevTutor - SwiftUI 开发辅导

6 | 7 |

8 | English • 9 | 常见问题 • 10 | 联系&支持 • 11 | 变更日志 12 |

13 |

14 | DevTutor AppStore 15 | 16 | DevTutor for SwiftUI AppStore iOS 17 | 18 |

19 |
20 | 21 |
22 | 23 | 最低操作系统要求:`macOS 14.0` / `iOS 17.0` 24 | 25 |
26 | 27 | 「DevTutor」是一款旨在帮助开发者使用 SwiftUI 创建出色应用程序的应用。它提供可复制的代码示例和相应的用户界面预览,以简化您的编码过程。此外,还包括了《Swift 编程语言》官方中英文文档的本地离线预览。 28 | 29 | ![DevTutor for SwiftUI screenshots-1](./assets/screenshots-1-all.png) 30 | 31 | ## 主要功能 32 | 33 | - 提供可直接在您的项目中使用的样本代码 34 | - 实时查看您的代码如何影响应用的外观 35 | - 提供官方中英文 Swift 编程语言离线文档 36 | - 探索第三方包集合,详见 [探索数据](https://github.com/jaywcjlove/devtutor/blob/main/data/explore.json) 37 | - 以及更多功能... 38 | 39 | ![DevTutor for SwiftUI screenshots-1](./assets/screenshots-1-cn.png) 40 | 41 | ![DevTutor for SwiftUI screenshots-2](./assets/screenshots-2-cn.png) 42 | 43 | ![DevTutor for SwiftUI screenshots-3](./assets/screenshots-3-cn.png) 44 | 45 | ## 关于 SwiftUI 46 | 47 | SwiftUI 是苹果公司开发的一个创新、申明式的 UI 框架,用于在 iOS、macOS、watchOS 和 tvOS 上构建用户界面。它于 2019 年发布,目标是简化跨平台应用程序开发的过程。SwiftUI 使用 Swift 编程语言,允许开发者以申明式语法来描述用户界面的元素和布局。 48 | 49 | ## 常见问题解答 50 | 51 | ### 适合什么样的人? 52 | 53 | SwiftUI 适合各种水平的开发者,从初学者到有经验的专业人士,都可以从中受益。以下是一些可能特别适合学习和使用 SwiftUI 的不同群体: 54 | 55 | 1. **新手开发者**:SwiftUI 的申明式语法相比较传统的命令式编程更易学习,因为它更加直观,开发者只需描述 UI 应该呈现的样子,而不是如何实现。这使得初学者更容易上手,并快速看到成效。 56 | 2. **有经验的 iOS/macOS 开发者**:对于已经熟悉 Apple 生态系统和 Swift 语言的开发者,SwiftUI 提供了一种更现代、高效的方式来构建用户界面。它还能帮助他们简化现有的 UIKit 或 AppKit 代码,转向更加模块化和可复用的设计。 57 | 3. **跨平台应用开发者**:开发者计划开发可以在 iOS、macOS、watchOS 和 tvOS 上运行的应用程序将受益于 SwiftUI 的跨平台特性。使用同一套代码基础,可以大大减少开发和维护成本。 58 | 4. **UI/UX 设计师**:对于希望将设计直接转化为代码的设计师,SwiftUI 的实时预览和申明式语法使他们可以快速迭代设计,并直观地看到其实现效果。 59 | 5. **想要提高生产力的专业人士**:SwiftUI 的高效特性(如实时预览和申明式语法)可以显著提高开发速度,减少调试时间,使得专业开发者能够更快地发布产品。 60 | 61 | 总之,SwiftUI 的设计理念和工具支持使它成为广泛受欢迎的框架,无论是对编程感兴趣的新手,还是希望提升开发效率和质量的经验丰富的专业人士,都能从中找到价值。 62 | 63 | ### 可以用来做什么? 64 | 65 | SwiftUI 可以用于开发各种类型的应用程序,适用于 Apple 的多个平台,包括 iOS、macOS、watchOS 和 tvOS。以下是一些可以利用 SwiftUI 开发的具体应用类型: 66 | 67 | 1. **手机和平板应用**:使用 SwiftUI,开发者可以为 iPhone 和 iPad 创建美观、响应灵敏的应用程序。无论是社交媒体应用、电子商务平台还是健康与健身追踪器,SwiftUI 都能提供所需的界面组件和动画。 68 | 2. **桌面应用**:macOS 应用程序同样可以利用 SwiftUI 构建。这些应用程序可以从简单的实用工具软件到复杂的编辑工具等不等,SwiftUI 提供了创建专业级桌面应用所需的一切功能。 69 | 3. **可穿戴设备应用**:对于 Apple Watch,SwiftUI 允许开发者设计小屏幕交互。这些应用通常用于快速信息显示、健康监测或作为智能手机应用的延伸。 70 | 4. **电视应用**:在 Apple TV 上,开发者可以使用 SwiftUI 创建大屏幕的娱乐和多媒体应用,如视频流应用或游戏。 71 | 5. **多平台应用**:SwiftUI 的跨平台特性使得开发者可以为 Apple 的所有设备创建统一的应用体验,从而简化开发过程,并确保各平台间的高度一致性。 72 | 6. **教育和培训应用**:教育应用也可以利用 SwiftUI 的互动性和吸引力,提供动态的学习体验,如互动教科书、学习游戏等。 73 | 7. **企业内部应用**:公司可以开发内部使用的应用程序,如员工通讯、项目管理工具或客户关系管理系统,这些都可以通过 SwiftUI 实现优化设计和功能。 74 | 8. **原型设计和测试**:SwiftUI 的快速迭代能力和实时预览特性使其成为快速原型设计的理想选择。设计师和开发者可以迅速构建和测试界面设计,加速开发过程。 75 | 76 | SwiftUI 提供的灵活性和易用性使其成为开发现代、响应快速的应用程序的强大工具。无论是个人项目还是专业开发,SwiftUI 都能满足不同的需求和预期。 77 | 78 | 79 | -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaywcjlove/devtutor/d4c03e29c04073ca6b5e9d9eee9fce81b83c0383/assets/logo.png -------------------------------------------------------------------------------- /assets/screenshots-1-all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaywcjlove/devtutor/d4c03e29c04073ca6b5e9d9eee9fce81b83c0383/assets/screenshots-1-all.png -------------------------------------------------------------------------------- /assets/screenshots-1-cn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaywcjlove/devtutor/d4c03e29c04073ca6b5e9d9eee9fce81b83c0383/assets/screenshots-1-cn.png -------------------------------------------------------------------------------- /assets/screenshots-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaywcjlove/devtutor/d4c03e29c04073ca6b5e9d9eee9fce81b83c0383/assets/screenshots-1.png -------------------------------------------------------------------------------- /assets/screenshots-2-cn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaywcjlove/devtutor/d4c03e29c04073ca6b5e9d9eee9fce81b83c0383/assets/screenshots-2-cn.png -------------------------------------------------------------------------------- /assets/screenshots-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaywcjlove/devtutor/d4c03e29c04073ca6b5e9d9eee9fce81b83c0383/assets/screenshots-2.png -------------------------------------------------------------------------------- /assets/screenshots-3-cn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaywcjlove/devtutor/d4c03e29c04073ca6b5e9d9eee9fce81b83c0383/assets/screenshots-3-cn.png -------------------------------------------------------------------------------- /assets/screenshots-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaywcjlove/devtutor/d4c03e29c04073ca6b5e9d9eee9fce81b83c0383/assets/screenshots-3.png -------------------------------------------------------------------------------- /assets/social-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaywcjlove/devtutor/d4c03e29c04073ca6b5e9d9eee9fce81b83c0383/assets/social-preview.png -------------------------------------------------------------------------------- /data/explore.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Third party Guides", 4 | "localizations": { 5 | "zh": "第三方指南" 6 | }, 7 | "icon": "shippingbox.fill", 8 | "children": [ 9 | { "name": "30 Days of Swift", "url": "https://github.com/allenwong/30DaysofSwift" }, 10 | { "name": "About Swift", "url": "https://github.com/NicolaLancellotti-About/About-Swift" }, 11 | { "name": "Awesome Swift Education", "url": "https://github.com/hsavit1/Awesome-Swift-Education" }, 12 | { "name": "Conferences.digital", "url": "https://github.com/zagahr/Conferences.digital" }, 13 | { "name": "Hacking With Swift", "url": "https://www.hackingwithswift.com" }, 14 | { "name": "Ray Wenderlich Tutorials, Videos, Podcasts and books", "url": "https://www.kodeco.com" }, 15 | { "name": "Swift & SwiftUI Tutorials", "url": "https://janeshswift.com" }, 16 | { "name": "Swift Education", "url": "https://github.com/swifteducation" }, 17 | { "name": "swift-tips", "url": "https://github.com/vincent-pradeilles/swift-tips" }, 18 | { "name": "SwiftDoc", "url": "https://swiftdoc.org/" }, 19 | { "name": "SwiftGuide CN", "url": "https://github.com/ipader/SwiftGuide" }, 20 | { "name": "SwiftTips", "url": "https://github.com/JohnSundell/SwiftTips" }, 21 | { "name": "SwiftUI Example", "localizations": { "zh": "SwiftUI 示例" }, "url": "https://github.com/jaywcjlove/swiftui-example" }, 22 | { "name": "Swift Tutorial", "localizations": { "zh": "Swift 教程" }, "url": "https://github.com/jaywcjlove/swift-tutorial" } 23 | ] 24 | }, 25 | { 26 | "name": "Routing", 27 | "localizations": { 28 | "zh": "路由" 29 | }, 30 | "icon": "shippingbox.fill", 31 | "children": [ 32 | { "name": "Appz", "url": "https://github.com/SwiftKitz/Appz" }, 33 | { "name": "SwiftUI Navigation", "url": "https://github.com/pointfreeco/swiftui-navigation" }, 34 | { "name": "Crossroad", "url": "https://github.com/giginet/Crossroad" }, 35 | { "name": "LiteRoute", "url": "https://github.com/SpectralDragon/LiteRoute" }, 36 | { "name": "Linker", "url": "https://github.com/MaksimKurpa/Linker" }, 37 | { "name": "MonarchRouter", "url": "https://github.com/nikans/MonarchRouter" } 38 | ] 39 | }, 40 | { 41 | "name": "App Store", 42 | "localizations": { 43 | "zh": "应用商店" 44 | }, 45 | "icon": "shippingbox.fill", 46 | "children": [ 47 | { "name": "Apphud", "url": "https://github.com/apphud/ApphudSDK" }, 48 | { "name": "AppReview", "url": "https://github.com/mezhevikin/AppReview" }, 49 | { "name": "InAppPurchase", "url": "https://github.com/jinSasaki/InAppPurchase" }, 50 | { "name": "merchantkit", "url": "https://github.com/benjaminmayo/merchantkit" }, 51 | { "name": "SwiftyStoreKit", "url": "https://github.com/bizz84/SwiftyStoreKit" } 52 | ] 53 | }, 54 | { 55 | "name": "Audio", 56 | "localizations": { 57 | "zh": "音频" 58 | }, 59 | "icon": "shippingbox.fill", 60 | "children": [ 61 | { "name": "AudioKit", "url": "https://github.com/audiokit/AudioKit" }, 62 | { "name": "AudioPlayer", "url": "https://github.com/delannoyk/AudioPlayer" }, 63 | { "name": "AudioPlayerSwift", "url": "https://github.com/tbaranes/AudioPlayerSwift" }, 64 | { "name": "Beethoven", "url": "https://github.com/vadymmarkov/Beethoven" }, 65 | { "name": "FDSoundActivatedRecorder", "url": "https://github.com/fulldecent/FDSoundActivatedRecorder" }, 66 | { "name": "FDWaveformView", "url": "https://github.com/fulldecent/FDWaveformView" }, 67 | { "name": "ModernAVPlayer", "url": "https://github.com/noreasonprojects/ModernAVPlayer" }, 68 | { "name": "MusicKit", "url": "https://github.com/vprtwn/MusicKit" }, 69 | { "name": "Soundable", "url": "https://github.com/ThXou/Soundable" }, 70 | { "name": "SwiftAudioPlayer", "url": "https://github.com/tanhakabir/SwiftAudioPlayer" }, 71 | { "name": "SwiftySound", "url": "https://github.com/adamcichy/SwiftySound" }, 72 | { "name": "voice-overlay-ios", "url": "https://github.com/algolia/voice-overlay-ios" } 73 | ] 74 | }, 75 | { 76 | "name": "API", 77 | "icon": "shippingbox.fill", 78 | "children": [ 79 | { "name": "GitHubAPI", "url": "https://github.com/serhii-londar/GithubAPI" }, 80 | { "name": "PXGoogleDirections", "url": "https://github.com/poulpix/PXGoogleDirections" }, 81 | { "name": "RandomUserSwift", "url": "https://github.com/dingwilson/RandomUserSwift" }, 82 | { "name": "reddift", "url": "https://github.com/sonsongithub/reddift" }, 83 | { "name": "Swifter Twitter", "url": "https://github.com/mattdonnelly/Swifter" }, 84 | { "name": "Swiftkube", "url": "https://github.com/swiftkube/client" }, 85 | { "name": "SwiftlySalesforce", "url": "https://github.com/mike4aday/SwiftlySalesforce" }, 86 | { "name": "SwiftyInsta", "url": "https://github.com/TheM4hd1/SwiftyInsta" } 87 | ] 88 | }, 89 | { 90 | "name": "Animation", 91 | "localizations": { 92 | "zh": "动画" 93 | }, 94 | "icon": "shippingbox.fill", 95 | "children": [ 96 | { "name": "Advance", "url": "https://github.com/timdonnelly/Advance" }, 97 | { "name": "ChainPageCollectionView", "url": "https://github.com/jindulys/ChainPageCollectionView" }, 98 | { "name": "CocoaSprings", "url": "https://github.com/MacPaw/CocoaSprings" }, 99 | { "name": "Comets", "url": "https://github.com/cruisediary/Comets" }, 100 | { "name": "Ease", "url": "https://github.com/roberthein/Ease" }, 101 | { "name": "EasyAnimation", "url": "https://github.com/icanzilb/EasyAnimation" }, 102 | { "name": "Elephant", "url": "https://github.com/s2mr/Elephant" }, 103 | { "name": "FlightAnimator", "url": "https://github.com/AntonTheDev/FlightAnimator" }, 104 | { "name": "Gemini", "url": "https://github.com/shoheiyokoyama/Gemini" }, 105 | { "name": "IBAnimatable", "url": "https://github.com/IBAnimatable/IBAnimatable" }, 106 | { "name": "Interpolate", "url": "https://github.com/marmelroy/Interpolate" }, 107 | { "name": "lottie-ios", "url": "https://github.com/airbnb/lottie-ios" }, 108 | { "name": "Pastel", "url": "https://github.com/cruisediary/Pastel" }, 109 | { "name": "Poi", "url": "https://github.com/HideakiTouhara/Poi" }, 110 | { "name": "Presentation", "url": "https://github.com/hyperoslo/Presentation" }, 111 | { "name": "Pulsator", "url": "https://github.com/shu223/pulsator" }, 112 | { "name": "Sica", "url": "https://github.com/cats-oss/Sica" }, 113 | { "name": "Spring", "url": "https://github.com/MengTo/Spring" }, 114 | { "name": "SpriteKitEasingSwift", "url": "https://github.com/craiggrummitt/SpriteKitEasingSwift" }, 115 | { "name": "spruce-ios", "url": "https://github.com/willowtreeapps/spruce-ios" }, 116 | { "name": "Stellar", "url": "https://github.com/AugustRush/Stellar" }, 117 | { "name": "TheAnimation", "url": "https://github.com/marty-suzuki/TheAnimation" }, 118 | { "name": "ViewAnimator", "url": "https://github.com/marcosgriselli/ViewAnimator" }, 119 | { "name": "YapAnimator", "url": "https://github.com/yapstudios/YapAnimator" } 120 | ] 121 | }, 122 | { 123 | "name": "Algorithm", 124 | "localizations": { 125 | "zh": "算法" 126 | }, 127 | "icon": "shippingbox.fill", 128 | "children": [ 129 | { "name": "Algorithm", "url": "https://github.com/CosmicMind/Algorithm" }, 130 | { "name": "BTree", "url": "https://github.com/attaswift/BTree" }, 131 | { "name": "swift-algorithm-club", "url": "https://github.com/kodecocodes/swift-algorithm-club" }, 132 | { "name": "SwiftLCS", "url": "https://github.com/Frugghi/SwiftLCS" } 133 | ] 134 | }, 135 | { 136 | "name": "Bluetooth", 137 | "localizations": { 138 | "zh": "蓝牙" 139 | }, 140 | "icon": "shippingbox.fill", 141 | "children": [ 142 | { "name": "BlueCap", "url": "https://github.com/troystribling/BlueCap" }, 143 | { "name": "Bluejay", "url": "https://github.com/steamclock/bluejay" }, 144 | { "name": "BluetoothKit", "url": "https://github.com/rhummelmose/BluetoothKit" }, 145 | { "name": "RxBluetoothKit", "url": "https://github.com/polidea/RxBluetoothKit" }, 146 | { "name": "SwiftyBluetooth", "url": "https://github.com/jordanebelanger/SwiftyBluetooth" } 147 | ] 148 | }, 149 | { 150 | "name": "Barcode", 151 | "localizations": { 152 | "zh": "条形码" 153 | }, 154 | "icon": "shippingbox.fill", 155 | "children": [ 156 | { "name": "BarcodeScanner", "url": "https://github.com/hyperoslo/BarcodeScanner" }, 157 | { "name": "EFQRCode", "url": "https://github.com/EFPrefix/EFQRCode" }, 158 | { "name": "QRCodeReader.swift", "url": "https://github.com/yannickl/QRCodeReader.swift" } 159 | ] 160 | }, 161 | { 162 | "name": "Camera", 163 | "localizations": { 164 | "zh": "相机" 165 | }, 166 | "icon": "shippingbox.fill", 167 | "children": [ 168 | { "name": "CameraBackground", "url": "https://github.com/yonat/CameraBackground" }, 169 | { "name": "CameraKit-iOS", "url": "https://github.com/CameraKit/camerakit-ios" }, 170 | { "name": "FDTake", "url": "https://github.com/fulldecent/FDTake" }, 171 | { "name": "Fusuma", "url": "https://github.com/ytakzk/Fusuma" }, 172 | { "name": "MediaPicker", "url": "https://github.com/exyte/mediapicker" }, 173 | { "name": "NextLevel", "url": "https://github.com/NextLevel/NextLevel" } 174 | ] 175 | }, 176 | { 177 | "name": "Device", 178 | "localizations": { 179 | "zh": "设备" 180 | }, 181 | "icon": "shippingbox.fill", 182 | "children": [ 183 | { "name": "Device", "url": "https://github.com/Ekhoo/Device" }, 184 | { "name": "Device.swift", "url": "https://github.com/schickling/Device.swift" }, 185 | { "name": "DeviceKit", "url": "https://github.com/devicekit/DeviceKit" }, 186 | { "name": "Deviice", "url": "https://github.com/andrealufino/Deviice" }, 187 | { "name": "Luminous", "url": "https://github.com/andrealufino/Luminous" }, 188 | { "name": "Thingy", "url": "https://github.com/bojan/Thingy" }, 189 | { "name": "UIDeviceComplete", "url": "https://github.com/Nirma/UIDeviceComplete" } 190 | ] 191 | }, 192 | { 193 | "name": "Text", 194 | "localizations": { 195 | "zh": "文本" 196 | }, 197 | "icon": "shippingbox.fill", 198 | "children": [ 199 | { "name": "Attributed", "url": "https://github.com/Nirma/Attributed" }, 200 | { "name": "AttributedTextView", "url": "https://github.com/evermeer/AttributedTextView" }, 201 | { "name": "BonMot", "url": "https://github.com/Rightpoint/BonMot" }, 202 | { "name": "Croc", "url": "https://github.com/JKalash/Croc" }, 203 | { "name": "edhita", "url": "https://github.com/tnantoka/edhita" }, 204 | { "name": "MarkdownKit", "url": "https://github.com/bmoliveira/MarkdownKit" }, 205 | { "name": "MarkdownView", "url": "https://github.com/keitaoouchi/MarkdownView" }, 206 | { "name": "MarkyMark", "url": "https://github.com/M2Mobi/Marky-Mark" }, 207 | { "name": "Notepad", "url": "https://github.com/ruddfawcett/Notepad" }, 208 | { "name": "OEMentions", "url": "https://github.com/omar14/OEMentions" }, 209 | { "name": "Parsey", "url": "https://github.com/rxwei/Parsey" }, 210 | { "name": "Pluralize.swift", "url": "https://github.com/joshualat/Pluralize.swift" }, 211 | { "name": "PredicateFlow", "url": "https://github.com/andreadelfante/PredicateFlow" }, 212 | { "name": "PrediKit", "url": "https://github.com/KrakenDev/PrediKit" }, 213 | { "name": "Regex by crossroadlabs", "url": "https://github.com/crossroadlabs/Regex" }, 214 | { "name": "Regex by sindresorhus", "url": "https://github.com/sindresorhus/Regex" }, 215 | { "name": "RichEditorView", "url": "https://github.com/cjwirth/RichEditorView" }, 216 | { "name": "Sprinter", "url": "https://github.com/nicklockwood/Sprinter" }, 217 | { "name": "SwiftRichString", "url": "https://github.com/malcommac/SwiftRichString" }, 218 | { "name": "SwiftVerbalExpressions", "url": "https://github.com/VerbalExpressions/SwiftVerbalExpressions" }, 219 | { "name": "SwiftyAttributes", "url": "https://github.com/eddiekaiger/SwiftyAttributes" }, 220 | { "name": "Tagging", "url": "https://github.com/k-lpmg/Tagging" }, 221 | { "name": "Texstyle", "url": "https://github.com/rosberry/texstyle" }, 222 | { "name": "TextAttributes", "url": "https://github.com/delba/TextAttributes" }, 223 | { "name": "TextBuilder", "url": "https://github.com/davdroman/TextBuilder" }, 224 | { "name": "TwitterTextEditor", "url": "https://github.com/twitter/TwitterTextEditor" }, 225 | { "name": "VEditorKit", "url": "https://github.com/GeekTree0101/VEditorKit" } 226 | ] 227 | }, 228 | { 229 | "name": "Images", 230 | "localizations": { 231 | "zh": "图片" 232 | }, 233 | "icon": "shippingbox.fill", 234 | "children": [ 235 | { "name": "Agrume", "url": "https://github.com/JanGorman/Agrume" }, 236 | { "name": "AlamofireImage", "url": "https://github.com/Alamofire/AlamofireImage" }, 237 | { "name": "APNGKit", "url": "https://github.com/onevcat/APNGKit" }, 238 | { "name": "ATGMediaBrowser", "url": "https://github.com/altayer-digital/ATGMediaBrowser" }, 239 | { "name": "AXPhotoViewer", "url": "https://github.com/alexhillc/AXPhotoViewer" }, 240 | { "name": "BlockiesSwift", "url": "https://github.com/Boilertalk/BlockiesSwift" }, 241 | { "name": "Brightroom", "url": "https://github.com/muukii/Brightroom" }, 242 | { "name": "CTPanoramaView", "url": "https://github.com/scihant/CTPanoramaView" }, 243 | { "name": "DTPhotoViewerController", "url": "https://github.com/tungvoduc/DTPhotoViewerController" }, 244 | { "name": "FacebookImagePicker", "url": "https://github.com/floriangbh/FacebookImagePicker" }, 245 | { "name": "FaceCrop", "url": "https://github.com/Ancestry/FaceCrop" }, 246 | { "name": "FlexibleImage", "url": "https://github.com/kawoou/FlexibleImage" }, 247 | { "name": "FMPhotoPicker", "url": "https://github.com/congnd/FMPhotoPicker" }, 248 | { "name": "gifu", "url": "https://github.com/kaishin/gifu" }, 249 | { "name": "GPUImage 2", "url": "https://github.com/BradLarson/GPUImage2" }, 250 | { "name": "GPUImage 3", "url": "https://github.com/BradLarson/GPUImage3" }, 251 | { "name": "HanekeSwift", "url": "https://github.com/Haneke/HanekeSwift" }, 252 | { "name": "Harbeth", "url": "https://github.com/yangKJ/Harbeth" }, 253 | { "name": "ImageDetect", "url": "https://github.com/Feghal/ImageDetect" }, 254 | { "name": "ImageLoader", "url": "https://github.com/hirohisa/ImageLoaderSwift" }, 255 | { "name": "ImageScout", "url": "https://github.com/kaishin/ImageScout" }, 256 | { "name": "ImageViewer", "url": "https://github.com/Krisiacik/ImageViewer" }, 257 | { "name": "ImgixSwift", "url": "https://github.com/imgix/imgix-swift" }, 258 | { "name": "JLStickerTextView", "url": "https://github.com/Textcat/JLStickerTextView" }, 259 | { "name": "Kanvas", "url": "https://github.com/tumblr/kanvas-ios" }, 260 | { "name": "Kingfisher", "url": "https://github.com/onevcat/Kingfisher" }, 261 | { "name": "LetterAvatarKit", "url": "https://github.com/vpeschenkov/LetterAvatarKit" }, 262 | { "name": "Lightbox", "url": "https://github.com/hyperoslo/Lightbox" }, 263 | { "name": "MapleBacon", "url": "https://github.com/JanGorman/MapleBacon" }, 264 | { "name": "MCScratchImageView", "url": "https://github.com/Minecodecraft/MCScratchImageView" }, 265 | { "name": "Moa", "url": "https://github.com/evgenyneu/moa" }, 266 | { "name": "Nuke", "url": "https://github.com/kean/Nuke" }, 267 | { "name": "PassportScanner", "url": "https://github.com/evermeer/PassportScanner" }, 268 | { "name": "Rough", "url": "https://github.com/bakhtiyork/Rough" }, 269 | { "name": "Sharaku", "url": "https://github.com/makomori/Sharaku" }, 270 | { "name": "Snowflake", "url": "https://github.com/onmyway133/Snowflake" }, 271 | { "name": "SwiftDraw", "url": "https://github.com/swhitty/SwiftDraw" }, 272 | { "name": "SwiftGen-Assets", "url": "https://github.com/SwiftGen/SwiftGen#assets-catalogs" }, 273 | { "name": "SwiftSVG", "url": "https://github.com/mchoe/SwiftSVG" }, 274 | { "name": "SwiftWebImage", "url": "https://github.com/geekaurora/SwiftWebImage" }, 275 | { "name": "SwiftyGif", "url": "https://github.com/kirualex/SwiftyGif" }, 276 | { "name": "TinyCrayon", "url": "https://github.com/TinyCrayon/TinyCrayon-iOS-SDK" }, 277 | { "name": "Toucan", "url": "https://github.com/gavinbunney/Toucan" }, 278 | { "name": "UIImageColors", "url": "https://github.com/jathu/UIImageColors" }, 279 | { "name": "YPImagePicker", "url": "https://github.com/Yummypets/YPImagePicker" }, 280 | { "name": "ZImageCropper", "url": "https://github.com/ZaidPathan/ZImageCropper" } 281 | ] 282 | }, 283 | { 284 | "name": "Menu", 285 | "localizations": { 286 | "zh": "菜单" 287 | }, 288 | "icon": "shippingbox.fill", 289 | "children": [ 290 | { "name": "AKSwiftSlideMenu", "url": "https://github.com/ashishkakkad8/AKSwiftSlideMenu" }, 291 | { "name": "CircleMenu", "url": "https://github.com/Ramotion/circle-menu" }, 292 | { "name": "ENSwiftSideMenu", "url": "https://github.com/evnaz/ENSwiftSideMenu" }, 293 | { "name": "FanMenu", "url": "https://github.com/exyte/fan-menu" }, 294 | { "name": "FlowingMenu", "url": "https://github.com/yannickl/FlowingMenu" }, 295 | { "name": "GuillotineMenu", "url": "https://github.com/Yalantis/GuillotineMenu" }, 296 | { "name": "HHFloatingView", "url": "https://github.com/hemangshah/HHFloatingView" }, 297 | { "name": "InteractiveSideMenu", "url": "https://github.com/handsomecode/InteractiveSideMenu" }, 298 | { "name": "KWDrawerController", "url": "https://github.com/Kawoou/KWDrawerController" }, 299 | { "name": "MenuItemKit", "url": "https://github.com/cxa/MenuItemKit" }, 300 | { "name": "Pagemenu", "url": "https://github.com/PageMenu/PageMenu" }, 301 | { "name": "PagingKit", "url": "https://github.com/kazuhiro4949/PagingKit" }, 302 | { "name": "Panels", "url": "https://github.com/antoniocasero/Panels" }, 303 | { "name": "Parchment", "url": "https://github.com/rechsteiner/Parchment" }, 304 | { "name": "PopMenu", "url": "https://github.com/CaliCastle/PopMenu" }, 305 | { "name": "SideMenu", "url": "https://github.com/jonkykong/SideMenu" }, 306 | { "name": "SlideMenuControllerSwift", "url": "https://github.com/dekatotoro/SlideMenuControllerSwift" }, 307 | { "name": "SwipeMenuViewController", "url": "https://github.com/yysskk/SwipeMenuViewController" }, 308 | { "name": "XLPagerTabStrip", "url": "https://github.com/xmartlabs/XLPagerTabStrip" }, 309 | { "name": "YNDropDownMenu", "url": "https://github.com/younatics/YNDropDownMenu" } 310 | ] 311 | }, 312 | { 313 | "name": "Calendar", 314 | "localizations": { 315 | "zh": "日历" 316 | }, 317 | "icon": "shippingbox.fill", 318 | "children": [ 319 | { "name": "CalendarKit", "url": "https://github.com/richardtop/CalendarKit" }, 320 | { "name": "CalendarView", "url": "https://github.com/mmick66/CalendarView" }, 321 | { "name": "DateTimePicker", "url": "https://github.com/itsmeichigo/DateTimePicker" }, 322 | { "name": "ElegantCalendar", "url": "https://github.com/ThasianX/ElegantCalendar" }, 323 | { "name": "HorizonCalendar", "url": "https://github.com/airbnb/HorizonCalendar" }, 324 | { "name": "JTAppleCalendar", "url": "https://github.com/patchthecode/JTAppleCalendar" }, 325 | { "name": "KVKCalendar", "url": "https://github.com/kvyatkovskys/KVKCalendar" }, 326 | { "name": "Workaholic", "url": "https://github.com/hemangshah/Workaholic" } 327 | ] 328 | }, 329 | { 330 | "name": "Events", 331 | "localizations": { 332 | "zh": "事件" 333 | }, 334 | "icon": "shippingbox.fill", 335 | "children": [ 336 | 337 | { "name": "Bond", "url": "https://github.com/DeclarativeHub/Bond" }, 338 | { "name": "Combinative", "url": "https://github.com/noppefoxwolf/Combinative" }, 339 | { "name": "EmitterKit", "url": "https://github.com/aleclarson/emitter-kit" }, 340 | { "name": "FutureKit", "url": "https://github.com/FutureKit/FutureKit" }, 341 | { "name": "Katana", "url": "https://github.com/BendingSpoons/katana-swift" }, 342 | { "name": "LightweightObservable", "url": "https://github.com/fxm90/LightweightObservable" }, 343 | { "name": "NoticeObserveKit", "url": "https://github.com/marty-suzuki/NoticeObserveKit" }, 344 | { "name": "Notificationz", "url": "https://github.com/SwiftKitz/Notificationz" }, 345 | { "name": "Observable", "url": "https://github.com/roberthein/Observable" }, 346 | { "name": "OneWay", "url": "https://github.com/DevYeom/OneWay" }, 347 | { "name": "OpenCombine", "url": "https://github.com/OpenCombine/OpenCombine" }, 348 | { "name": "PMKVObserver", "url": "https://github.com/postmates/PMKVObserver/" }, 349 | { "name": "PromiseKit", "url": "https://github.com/mxcl/PromiseKit" }, 350 | { "name": "ReactiveCocoa", "url": "https://github.com/ReactiveCocoa/ReactiveCocoa" }, 351 | { "name": "ReactorKit", "url": "https://github.com/ReactorKit/ReactorKit" }, 352 | { "name": "ReSwift", "url": "https://github.com/ReSwift/ReSwift" }, 353 | { "name": "RxSwift", "url": "https://github.com/ReactiveX/RxSwift" }, 354 | { "name": "Signals", "url": "https://github.com/artman/Signals" }, 355 | { "name": "SwiftEventBus", "url": "https://github.com/cesarferreira/SwiftEventBus" }, 356 | { "name": "Tempura", "url": "https://github.com/BendingSpoons/tempura-swift" }, 357 | { "name": "Tokamak", "url": "https://github.com/TokamakUI/Tokamak" }, 358 | { "name": "Tomorrowland", "url": "https://github.com/lilyball/Tomorrowland" }, 359 | { "name": "TopicEventBus", "url": "https://github.com/mcmatan/topicEventBus" }, 360 | { "name": "VueFlux", "url": "https://github.com/ra1028/VueFlux" }, 361 | { "name": "When", "url": "https://github.com/vadymmarkov/When" } 362 | ] 363 | }, 364 | { 365 | "name": "System", 366 | "localizations": { 367 | "zh": "系统" 368 | }, 369 | "icon": "shippingbox.fill", 370 | "children": [ 371 | { "name": "BlueSignals", "url": "https://github.com/Kitura/BlueSignals" }, 372 | { "name": "LaunchAtLogin", "url": "https://github.com/sindresorhus/LaunchAtLogin" }, 373 | { "name": "SystemKit", "url": "https://github.com/beltex/SystemKit/" } 374 | ] 375 | }, 376 | { 377 | "name": "UI", 378 | "icon": "shippingbox.fill", 379 | "children": [ 380 | { "name": "ActivityIndicatorView", "url": "https://github.com/exyte/ActivityIndicatorView" }, 381 | { "name": "AECoreDataUI", "url": "https://github.com/tadija/AERecord" }, 382 | { "name": "AGCircularPicker", "url": "https://github.com/agilie/AGCircularPicker" }, 383 | { "name": "AMScrollingNavbar", "url": "https://github.com/andreamazz/AMScrollingNavbar" }, 384 | { "name": "Arale", "url": "https://github.com/supercomputra/Arale" }, 385 | { "name": "BadgeHub", "url": "https://github.com/jogendra/BadgeHub" }, 386 | { "name": "BatteryView", "url": "https://github.com/yonat/BatteryView" }, 387 | { "name": "BetterSafariView", "url": "https://github.com/stleamist/BetterSafariView" }, 388 | { "name": "BottomSheet", "url": "https://github.com/joomcode/BottomSheet" }, 389 | { "name": "BreakOutToRefresh", "url": "https://github.com/dasdom/BreakOutToRefresh" }, 390 | { "name": "BulletinBoard", "url": "https://github.com/alexisakers/BulletinBoard" }, 391 | { "name": "CapturePreventionKit", "url": "https://github.com/Jaesung-Jung/CapturePreventionKit" }, 392 | { "name": "CircularProgress", "url": "https://github.com/sindresorhus/CircularProgress" }, 393 | { "name": "ClassicKit", "url": "https://github.com/Baddaboo/ClassicKit" }, 394 | { "name": "ContainerController", "url": "https://github.com/mrustaa/ContainerController" }, 395 | { "name": "CountryPickerView", "url": "https://github.com/kizitonwose/CountryPickerView" }, 396 | { "name": "CustomSegue", "url": "https://github.com/phimage/CustomSegue" }, 397 | { "name": "DeckTransition", "url": "https://github.com/HarshilShah/DeckTransition" }, 398 | { "name": "DockProgress", "url": "https://github.com/sindresorhus/DockProgress" }, 399 | { "name": "Dodo", "url": "https://github.com/evgenyneu/Dodo" }, 400 | { "name": "Doric Design System Foundation", "url": "https://github.com/jayeshk/Doric" }, 401 | { "name": "DropDown", "url": "https://github.com/AssistoLab/DropDown" }, 402 | { "name": "Elissa", "url": "https://github.com/KitchenStories/Elissa" }, 403 | { "name": "EstMusicIndicator", "url": "https://github.com/Aufree/ESTMusicIndicator" }, 404 | { "name": "Family", "url": "https://github.com/zenangst/Family" }, 405 | { "name": "FAQView", "url": "https://github.com/mukeshthawani/faqview" }, 406 | { "name": "Fashion", "url": "https://github.com/vadymmarkov/Fashion" }, 407 | { "name": "FlagKit", "url": "https://github.com/madebybowtie/FlagKit" }, 408 | { "name": "FlexibleHeader", "url": "https://github.com/k-lpmg/FlexibleHeader" }, 409 | { "name": "FloatRatingView", "url": "https://github.com/glenyi/FloatRatingView" }, 410 | { "name": "Fluid Slider", "url": "https://github.com/Ramotion/fluid-slider" }, 411 | { "name": "GaugeKit", "url": "https://github.com/skywinder/GaugeKit" }, 412 | { "name": "GMStepper", "url": "https://github.com/gmertk/GMStepper" }, 413 | { "name": "GradientProgressBar", "url": "https://github.com/fxm90/GradientProgressBar" }, 414 | { "name": "GRMustache", "url": "https://github.com/groue/GRMustache.swift" }, 415 | { "name": "GrowingTextView", "url": "https://github.com/KennethTsang/GrowingTextView" }, 416 | { "name": "HGCircularSlider", "url": "https://github.com/HamzaGhazouani/HGCircularSlider" }, 417 | { "name": "HidesNavigationBarWhenPushed", "url": "https://github.com/gontovnik/HidesNavigationBarWhenPushed" }, 418 | { "name": "HorizontalDial", "url": "https://github.com/kciter/HorizontalDial" }, 419 | { "name": "HPParallaxHeader", "url": "https://github.com/ngochiencse/HPParallaxHeader" }, 420 | { "name": "IGColorPicker", "url": "https://github.com/iGenius-Srl/IGColorPicker" }, 421 | { "name": "InstantSearch iOS", "url": "https://github.com/algolia/instantsearch-ios" }, 422 | { "name": "KALoader", "url": "https://github.com/Kirillzzy/KALoader" }, 423 | { "name": "KMNavigationBarTransition", "url": "https://github.com/MoZhouqi/KMNavigationBarTransition" }, 424 | { "name": "KMPlaceholderTextView", "url": "https://github.com/MoZhouqi/KMPlaceholderTextView" }, 425 | { "name": "LeeGo", "url": "https://github.com/wangshengjia/LeeGo" }, 426 | { "name": "LicensePlist", "url": "https://github.com/mono0926/LicensePlist" }, 427 | { "name": "LiquidLoader", "url": "https://github.com/yoavlt/LiquidLoader" }, 428 | { "name": "LoadingShimmer", "url": "https://github.com/jogendra/LoadingShimmer" }, 429 | { "name": "Macaw", "url": "https://github.com/exyte/macaw" }, 430 | { "name": "Magnetic", "url": "https://github.com/efremidze/Magnetic" }, 431 | { "name": "Mandoline", "url": "https://github.com/blueapron/Mandoline" }, 432 | { "name": "MantleModal", "url": "https://github.com/canalesb93/MantleModal" }, 433 | { "name": "Material", "url": "https://github.com/CosmicMind/Material" }, 434 | { "name": "Material Components for iOS", "url": "https://github.com/material-components/material-components-ios" }, 435 | { "name": "MaterialKit", "url": "https://github.com/nghialv/MaterialKit" }, 436 | { "name": "MediaBrowser", "url": "https://github.com/younatics/MediaBrowser" }, 437 | { "name": "MPParallaxView", "url": "https://github.com/DroidsOnRoids/MPParallaxView" }, 438 | { "name": "MultiSelectSegmentedControl", "url": "https://github.com/yonat/MultiSelectSegmentedControl" }, 439 | { "name": "MultiSlider", "url": "https://github.com/yonat/MultiSlider" }, 440 | { "name": "MXParallaxHeader", "url": "https://github.com/maxep/MXParallaxHeader" }, 441 | { "name": "MZFormSheetPresentationController", "url": "https://github.com/m1entus/MZFormSheetPresentationController" }, 442 | { "name": "NeumorphismKit", "url": "https://github.com/y-okudera/NeumorphismKit" }, 443 | { "name": "NextGrowingTextView", "url": "https://github.com/FluidGroup/NextGrowingTextView" }, 444 | { "name": "NVActivityIndicatorView", "url": "https://github.com/ninjaprox/NVActivityIndicatorView" }, 445 | { "name": "OverlayContainer", "url": "https://github.com/applidium/OverlayContainer" }, 446 | { "name": "Partition Kit", "url": "https://github.com/kieranb662/PartitionKit" }, 447 | { "name": "Popovers", "url": "https://github.com/aheze/Popovers" }, 448 | { "name": "Preferences", "url": "https://github.com/sindresorhus/Preferences" }, 449 | { "name": "ProgressIndicatorView", "url": "https://github.com/exyte/ProgressIndicatorView" }, 450 | { "name": "PullToDismiss", "url": "https://github.com/sgr-ksmt/PullToDismiss" }, 451 | { "name": "RangeSeekSlider", "url": "https://github.com/WorldDownTown/RangeSeekSlider" }, 452 | { "name": "Reel search", "url": "https://github.com/Ramotion/reel-search" }, 453 | { "name": "ResizingTokenField", "url": "https://github.com/tadejr/ResizingTokenField" }, 454 | { "name": "RetroProgress", "url": "https://github.com/hyperoslo/RetroProgress" }, 455 | { "name": "SectionedSlider", "url": "https://github.com/LeonardoCardoso/SectionedSlider" }, 456 | { "name": "SelectionDialog", "url": "https://github.com/kciter/SelectionDialog" }, 457 | { "name": "ShadowView", "url": "https://github.com/PierrePerrin/ShadowView" }, 458 | { "name": "Shiny", "url": "https://github.com/efremidze/Shiny" }, 459 | { "name": "ShowSomeProgress", "url": "https://github.com/stoneburner/ShowSomeProgress" }, 460 | { "name": "SkeletonView", "url": "https://github.com/Juanpe/SkeletonView" }, 461 | { "name": "SKPhotoBrowser", "url": "https://github.com/suzuki-0000/SKPhotoBrowser" }, 462 | { "name": "Spots", "url": "https://github.com/hyperoslo" }, 463 | { "name": "SpreadsheetView", "url": "https://github.com/kishikawakatsumi/SpreadsheetView" }, 464 | { "name": "StarryStars", "url": "https://github.com/peterprokop/StarryStars" }, 465 | { "name": "StatefulViewController", "url": "https://github.com/aschuch/StatefulViewController" }, 466 | { "name": "StepProgressView", "url": "https://github.com/yonat/StepProgressView" }, 467 | { "name": "SweetCurtain", "url": "https://github.com/multimediasuite/SweetCurtain" }, 468 | { "name": "SwiftyUI", "url": "https://github.com/haoking/SwiftyUI" }, 469 | { "name": "TagListView", "url": "https://github.com/ElaWorkshop/TagListView" }, 470 | { "name": "Toaster", "url": "https://github.com/devxoul/Toaster" }, 471 | { "name": "Twinkle", "url": "https://github.com/piemonte/Twinkle" }, 472 | { "name": "UIPheonix", "url": "https://github.com/MKGitHub/UIPheonix" }, 473 | { "name": "UltraDrawerView", "url": "https://github.com/super-ultra/UltraDrawerView" }, 474 | { "name": "URLEmbeddedView", "url": "https://github.com/marty-suzuki/URLEmbeddedView" }, 475 | { "name": "Wallet", "url": "https://github.com/russ-stamant/Wallet" }, 476 | { "name": "Windless", "url": "https://github.com/ParkGwangBeom/Windless" }, 477 | { "name": "WSTagsField", "url": "https://github.com/whitesmith/WSTagsField" }, 478 | { "name": "YMTreeMap", "url": "https://github.com/yahoo/YMTreeMap" }, 479 | { "name": "YNSearch", "url": "https://github.com/younatics/YNSearch" } 480 | ] 481 | }, 482 | { 483 | "name": "Maps", 484 | "localizations": { 485 | "zh": "地图" 486 | }, 487 | "icon": "shippingbox.fill", 488 | "children": [ 489 | { "name": "Cluster", "url": "https://github.com/efremidze/Cluster" }, 490 | { "name": "FlyoverKit", "url": "https://github.com/SvenTiigi/FlyoverKit" }, 491 | { "name": "GEOSwift", "url": "https://github.com/GEOSwift/GEOSwift" }, 492 | { "name": "LocoKit", "url": "https://github.com/sobri909/LocoKit" } 493 | ] 494 | }, 495 | { 496 | "name": "Keyboard", 497 | "localizations": { 498 | "zh": "键盘" 499 | }, 500 | "icon": "shippingbox.fill", 501 | "children": [ 502 | { "name": "IHKeyboardAvoiding", "url": "https://github.com/IdleHandsApps/IHKeyboardAvoiding" }, 503 | { "name": "IQKeyboardManager", "url": "https://github.com/hackiftekhar/IQKeyboardManager" }, 504 | { "name": "ISEmojiView", "url": "https://github.com/isaced/ISEmojiView" }, 505 | { "name": "KeyboardHideManager", "url": "https://github.com/bonyadmitr/KeyboardHideManager" }, 506 | { "name": "KeyboardShortcuts", "url": "https://github.com/sindresorhus/KeyboardShortcuts" }, 507 | { "name": "Ribbon", "url": "https://github.com/chriszielinski/Ribbon" }, 508 | { "name": "Typist", "url": "https://github.com/totocaster/Typist" } 509 | ] 510 | }, 511 | { 512 | "name": "Network", 513 | "localizations": { 514 | "zh": "网络" 515 | }, 516 | "icon": "shippingbox.fill", 517 | "children": [ 518 | { "name": "Alamofire", "url": "https://github.com/Alamofire/Alamofire" }, 519 | { "name": "APIKit", "url": "https://github.com/ishkawa/APIKit" }, 520 | { "name": "Ciao", "url": "https://github.com/AlTavares/Ciao" }, 521 | { "name": "CodyFire", "url": "https://github.com/CodyFlame/CodyFire" }, 522 | { "name": "Conduit", "url": "https://github.com/mindbody/Conduit" }, 523 | { "name": "Connectivity", "url": "https://github.com/rwbutler/Connectivity" }, 524 | { "name": "Dots", "url": "https://github.com/iAmrSalman/Dots" }, 525 | { "name": "GoodNetworking", "url": "https://github.com/GoodRequest/GoodNetworking" }, 526 | { "name": "Heimdallr.swift", "url": "https://github.com/trivago/Heimdallr.swift" }, 527 | { "name": "Just", "url": "https://github.com/dduan/Just" }, 528 | { "name": "Malibu", "url": "https://github.com/hyperoslo/Malibu" }, 529 | { "name": "Moya", "url": "https://github.com/Moya/Moya" }, 530 | { "name": "MultiPeer", "url": "https://github.com/dingwilson/MultiPeer" }, 531 | { "name": "Netfox", "url": "https://github.com/kasketis/netfox" }, 532 | { "name": "Netswift", "url": "https://github.com/MrSkwiggs/Netswift" }, 533 | { "name": "OAuth2", "url": "https://github.com/p2/OAuth2" }, 534 | { "name": "OAuthSwift", "url": "https://github.com/OAuthSwift/OAuthSwift" }, 535 | { "name": "Pitaya", "url": "https://github.com/johnlui/Pitaya" }, 536 | { "name": "PMHTTP", "url": "https://github.com/postmates/PMHTTP" }, 537 | { "name": "Postal", "url": "https://github.com/snipsco/Postal" }, 538 | { "name": "Reachability.swift", "url": "https://github.com/ashleymills/Reachability.swift" }, 539 | { "name": "ReactiveAPI", "url": "https://github.com/sky-uk/ReactiveAPI" }, 540 | { "name": "ResponseDetective", "url": "https://github.com/netguru/ResponseDetective" }, 541 | { "name": "RxNetworks", "url": "https://github.com/yangKJ/RxNetworks" }, 542 | { "name": "ShadowsocksX-NG", "url": "https://github.com/shadowsocks/ShadowsocksX-NG" }, 543 | { "name": "Siesta", "url": "https://bustoutsolutions.github.io/siesta/" }, 544 | { "name": "SolarNetwork", "url": "https://github.com/ThreeGayHub/SolarNetwork" }, 545 | { "name": "SwiftHTTP", "url": "https://github.com/daltoniam/SwiftHTTP" }, 546 | { "name": "SwiftyOAuth", "url": "https://github.com/delba/SwiftyOAuth" }, 547 | { "name": "TermiNetwork", "url": "https://github.com/billp/TermiNetwork" }, 548 | { "name": "TRON", "url": "https://github.com/MLSDev/TRON" }, 549 | { "name": "Wormholy", "url": "https://github.com/pmusolino/Wormholy" } 550 | ] 551 | }, 552 | { 553 | "name": "Misc", 554 | "localizations": { 555 | "zh": "杂项" 556 | }, 557 | "icon": "shippingbox.fill", 558 | "children": [ 559 | { "name": "Beak", "url": "https://github.com/yonaskolb/Beak" }, 560 | { "name": "BetterCodable", "url": "https://github.com/marksands/BetterCodableDecoder" }, 561 | { "name": "CodableWrappers", "url": "https://github.com/GottaGetSwifty/CodableWrappers" }, 562 | { "name": "Fugen", "url": "https://github.com/almazrafi/Fugen" }, 563 | { "name": "MemberwiseInit", "url": "https://github.com/gohanlon/swift-memberwise-init-macro" }, 564 | { "name": "Model2App", "url": "https://github.com/Q-Mobile/Model2App" }, 565 | { "name": "Surmagic", "url": "https://github.com/gurhub/surmagic" }, 566 | { "name": "SwagGen", "url": "https://github.com/yonaskolb/SwagGen" }, 567 | { "name": "Swiftbrew", "url": "https://github.com/swiftbrew/Swiftbrew" }, 568 | { "name": "SwiftGen", "url": "https://github.com/SwiftGen/SwiftGen" }, 569 | { "name": "SwiftKit", "url": "https://github.com/SvenTiigi/SwiftKit" }, 570 | { "name": "SwiftPlate", "url": "https://github.com/JohnSundell/SwiftPlate" }, 571 | { "name": "Toybox", "url": "https://github.com/giginet/Toybox" }, 572 | { "name": "Tuist", "url": "https://github.com/tuist/tuist" }, 573 | { "name": "xc", "url": "https://github.com/s2mr/xc" }, 574 | { "name": "xcbeautify", "url": "https://github.com/cpisciotta/xcbeautify" }, 575 | { "name": "XcodeGen", "url": "https://github.com/yonaskolb/XcodeGen" }, 576 | { "name": "xcodeproj", "url": "https://github.com/tuist/xcodeproj" } 577 | ] 578 | } 579 | ] -------------------------------------------------------------------------------- /privacy-policy.md: -------------------------------------------------------------------------------- 1 |

2 | 中文 3 |

4 | 5 | 6 | Privacy Policy 7 | === 8 | 9 | Welcome to **DevTutor**, developed by Kenny Wang, which provides some free content and subscription services to unlock all features. This privacy policy aims to transparently explain how we handle your personal information, including how we collect, use, and protect it. Please read this policy carefully to understand our commitment to your privacy. 10 | 11 | ### Information Collection and Use 12 | 13 | Our app is an offline local app that does not actively collect, store, or transmit any personal identifiable information or sensitive information. 14 | 15 | ### Data Storage and Security 16 | 17 | We use industry-standard security measures to protect the information stored on our servers, preventing unauthorized access, disclosure, modification, or destruction. All personal information is encrypted using Secure Sockets Layer (SSL) technology. 18 | 19 | ### Third-Party Services 20 | 21 | Our app may use third-party services (such as payment processing, data analysis, etc.), which may require access to some user information. We only partner with service providers who also prioritize user privacy protection and ensure they comply with this privacy policy. 22 | 23 | ### User Rights 24 | 25 | You can access, correct, or delete your personal information at any time. If you want to cancel your subscription or delete your account, please contact our support team. 26 | 27 | ### Children's Privacy 28 | 29 | This app is not designed for children under 13. We do not intentionally collect or request personal information from children under 13. If we discover that we have collected such information, we will take immediate action to delete it. 30 | 31 | ### Privacy Policy Updates 32 | 33 | Our privacy policy may be adjusted according to business development and legal requirements. Any significant changes will be notified through in-app notifications or your registered email address. We recommend you review this policy regularly to understand the latest privacy information. 34 | 35 | ### Contact Us 36 | 37 | If you have any questions or concerns about our privacy policy or need help managing your information, please contact us at kennyiseeyou@gmail.com. 38 | This policy takes effect on April 21, 2024. 39 | -------------------------------------------------------------------------------- /privacy-policy.zh.md: -------------------------------------------------------------------------------- 1 |

2 | English 3 |

4 | 5 | 6 | 7 | 隐私政策 8 | === 9 | 10 | 欢迎使用「DevTutor」,本应用由 Kenny Wang 开发,提供部分免费内容及订阅服务以解锁所有功能。本隐私政策旨在透明地阐述我们如何处理您的个人信息,包括我们如何收集、使用及保护这些信息。请仔细阅读本政策,了解我们对您隐私的承诺。 11 | 12 | ### 信息收集与使用 13 | 14 | 我们的应用是一个离线本地应用程序,不会主动收集、存储或传输任何个人身份识别信息或敏感信息。 15 | 16 | ### 数据存储与安全 17 | 18 | 我们采用行业标准的安全措施来保护存储在我们服务器上的信息,防止未经授权访问、披露、修改或销毁。所有个人信息都通过安全套接层技术(SSL)进行加密处理。 19 | 20 | ### 第三方服务 21 | 22 | 我们的应用可能会使用第三方服务(如支付处理、数据分析等),这些服务可能需要访问部分用户信息。我们仅与那些同样重视用户隐私保护的服务提供商合作,并确保他们遵守本隐私政策的规定。 23 | 24 | ### 用户权利 25 | 26 | 您可以随时访问、更正或删除您的个人信息。如果您希望取消订阅或删除您的账户,请通过我们的支持团队操作。 27 | 28 | ### 儿童隐私 29 | 30 | 本应用不针对13岁以下儿童设计。我们不会故意收集或请求13岁以下儿童的个人信息。如果我们得知收集了此类信息,将立即采取措施删除这些信息。 31 | 32 | ### 隐私政策更新 33 | 34 | 我们的隐私政策可能会根据业务发展和法律要求进行调整。任何重大更改将通过应用内通知或您注册的邮箱地址通知您。建议您定期查看本政策,以了解最新的隐私信息。 35 | 36 | ### 联系我们 37 | 38 | 如果您对我们的隐私政策有任何疑问或需要帮助管理您的信息,请通过以下邮箱与我们联系:kennyiseeyou@gmail.com。 39 | 40 | 本政策自 2024 年 04 月 21 日起生效。 -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /terms-of-service.md: -------------------------------------------------------------------------------- 1 |

2 | 中文 3 |

4 | 5 | 6 | Terms of Service 7 | === 8 | 9 | Welcome to the "DevTutor" application. We recommend that you read and understand the following terms of service carefully before subscribing or making a purchase. 10 | 11 | ### 1. Application Features 12 | 13 | The "DevTutor" is an educational tool designed for developers who want to master SwiftUI. This app provides detailed code examples and corresponding interface previews to help you learn and practice programming skills efficiently. In addition, the app integrates the official "Swift Programming Language" documentation, available in both Chinese and English, and can be read offline, ensuring that you can continue to learn even without an internet connection. 14 | 15 | ### 2. Free Version Content 16 | 17 | The free version offers the complete official documentation of the "Swift Programming Language" and select basic chapters of the SwiftUI reference manual. These resources are intended to provide a quick start for beginners and help you understand the fundamental concepts. 18 | 19 | ### 3. Upgrade to Professional Edition 20 | 21 | By upgrading to the Professional Edition, you will unlock all advanced chapters, including in-depth feature analysis and guides for building complex projects. Additionally, Professional Edition users will receive priority access to all new features and content updates. 22 | 23 | ### 4. Refund Policy 24 | 25 | We are committed to providing high-quality service. If you encounter technical issues during the subscription process or are dissatisfied with our services, you can contact us for assistance via the support email within the app. If the issue cannot be resolved, you are entitled to a full refund. Please contact Apple's support team to process the refund through the following link: 26 | 27 | [Request a Refund](https://support.apple.com/118223) 28 | 29 | Refund requests must be submitted within 30 days of purchase and require valid proof of purchase. 30 | 31 | ### 5. Updates to Terms of Service 32 | 33 | These terms of service may be updated according to legal requirements or service adjustments. Any changes will be published on this page and will take effect immediately upon publication. We recommend that you visit this page regularly to stay informed of the latest terms of service. 34 | 35 | ### 6. Contact Information 36 | 37 | We highly value user feedback and suggestions. If you have any questions or concerns about our privacy policy or terms of service, please contact us through the "About Us" section, and we will respond as soon as possible. 38 | 39 | We are committed to protecting your personal information and will not disclose user data to third parties without permission. 40 | 41 | Thank you for choosing "DevTutor" We look forward to accompanying you on your learning journey. 42 | -------------------------------------------------------------------------------- /terms-of-service.zh.md: -------------------------------------------------------------------------------- 1 |

2 | English 3 |

4 | 5 | 6 | 服务条款 7 | === 8 | 9 | 欢迎您使用「DevTutor」应用。我们建议在您订阅或购买前,仔细阅读并理解以下服务条款。 10 | 11 | ### 1. 应用功能介绍 12 | 13 | 「DevTutor」是为希望精通 SwiftUI 的开发者设计的教育工具。该应用提供详细的代码示例和对应的界面预览,帮助您高效学习并实践编程技巧。除此之外,本应用还整合了官方「Swift 编程语言」文档,支持中英文,并且可以离线阅读,确保您在没有网络的环境下也能够继续学习。 14 | 15 | ### 2. 免费版本内容 16 | 17 | 免费版本提供「Swift 编程语言」的完整官方文档和部分基础 SwiftUI 查询手册章节。这些资源旨在为初学者提供快速入门的途径,并帮助您理解基础概念。 18 | 19 | ### 3. 升级至专业版 20 | 21 | 选择升级至专业版,您将解锁所有高级章节,包括深入的特性解析和复杂项目的构建指南。此外,专业版用户将优先获得所有新功能和内容更新。 22 | 23 | ### 4. 退款政策 24 | 25 | 我们致力于提供高质量的服务。如果您在订阅过程中遇到技术问题或对我们的服务不满意,可通过应用内的支持邮箱联系我们寻求帮助。如果问题未能得到解决,您有权申请全额退款。请通过以下链接联系苹果支持团队处理退款事宜: 26 | 27 | [申请退款](https://support.apple.com/118223) 28 | 29 | 退款申请必须在购买后30天内提交,且需要提供有效的购买证明。 30 | 31 | ### 5. 使用条款的更新 32 | 33 | 本服务条款可能会根据法律规定或服务调整进行更新。任何条款的更改将在本页面公布,并自公布之日起立即生效。我们建议您定期访问本页面,以了解最新的服务条款。 34 | 35 | ### 6. 联系方式 36 | 37 | 我们非常重视用户的意见和建议。如果您对隐私政策或服务条款有任何疑问,请通过「关于我们」联系我们,我们将尽快给予回应。 38 | 39 | 我们承诺保护您的个人信息安全,不会在未经允许的情况下向第三方透露用户数据。 40 | 41 | 感谢您选择「DevTutor」,我们期待陪伴您的学习旅程。 42 | --------------------------------------------------------------------------------