├── DINetworkManager.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── xcuserdata │ └── fatihdurmaz.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── DINetworkManager ├── ContentView.swift ├── DINetworkManagerApp.swift ├── Helpers │ └── ApiContants.swift ├── Model │ └── Product.swift ├── Resources │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ └── Preview Content │ │ └── Preview Assets.xcassets │ │ └── Contents.json ├── Service │ ├── BaseService │ │ ├── AlamofireApiService.swift │ │ └── URLSessionApiService.swift │ ├── ProductApiService.swift │ └── Protocol │ │ └── ApiServiceProtocol.swift ├── View │ └── Product │ │ ├── ProductDetailView.swift │ │ ├── ProductListView.swift │ │ └── SingleProductView.swift └── ViewModel │ └── ProductViewModel.swift ├── LICENSE ├── README.md └── README.tr-TR.md /DINetworkManager.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | DE2CC6062B5E655200213707 /* Product.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE2CC6052B5E655200213707 /* Product.swift */; }; 11 | DE2CC6082B5E659B00213707 /* ProductApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE2CC6072B5E659B00213707 /* ProductApiService.swift */; }; 12 | DE2CC60A2B5E835200213707 /* ProductViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE2CC6092B5E835200213707 /* ProductViewModel.swift */; }; 13 | DE2CC60D2B5E853A00213707 /* ProductListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE2CC60C2B5E853A00213707 /* ProductListView.swift */; }; 14 | DE2CC60F2B5E874000213707 /* ProductDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE2CC60E2B5E874000213707 /* ProductDetailView.swift */; }; 15 | DE4A1EDD2B50327600C198BC /* ApiServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE4A1EDC2B50327600C198BC /* ApiServiceProtocol.swift */; }; 16 | DE614B222B5EA41E00A1181A /* SDWebImageSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = DE614B212B5EA41E00A1181A /* SDWebImageSwiftUI */; }; 17 | DE614B242B5EA4A700A1181A /* SDWebImageSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = DE614B232B5EA4A700A1181A /* SDWebImageSwiftUI */; }; 18 | DEC97CE42B4F60BA0059F398 /* DINetworkManagerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC97CE32B4F60BA0059F398 /* DINetworkManagerApp.swift */; }; 19 | DEC97CE62B4F60BA0059F398 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC97CE52B4F60BA0059F398 /* ContentView.swift */; }; 20 | DEC97CE82B4F60BB0059F398 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DEC97CE72B4F60BB0059F398 /* Assets.xcassets */; }; 21 | DEC97CEB2B4F60BB0059F398 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DEC97CEA2B4F60BB0059F398 /* Preview Assets.xcassets */; }; 22 | DEC97CFD2B4F60E70059F398 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = DEC97CFC2B4F60E70059F398 /* Alamofire */; }; 23 | DEC97D012B4F615B0059F398 /* ApiContants.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC97D002B4F615B0059F398 /* ApiContants.swift */; }; 24 | DEC97D032B4F61600059F398 /* AlamofireApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC97D022B4F61600059F398 /* AlamofireApiService.swift */; }; 25 | DEC97D072B4F61670059F398 /* URLSessionApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC97D062B4F61670059F398 /* URLSessionApiService.swift */; }; 26 | DEF28AA12B569E6900249813 /* SwipeCell in Frameworks */ = {isa = PBXBuildFile; productRef = DEF28AA02B569E6900249813 /* SwipeCell */; }; 27 | DEF6523B2B51802900B173B7 /* SingleProductView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF6523A2B51802900B173B7 /* SingleProductView.swift */; }; 28 | DEFAF74E2B566B0F008EF720 /* SwiftUISnackbar in Frameworks */ = {isa = PBXBuildFile; productRef = DEFAF74D2B566B0F008EF720 /* SwiftUISnackbar */; }; 29 | DEFEBC2C2B5F0FE50082419C /* StarRating in Frameworks */ = {isa = PBXBuildFile; productRef = DEFEBC2B2B5F0FE50082419C /* StarRating */; }; 30 | /* End PBXBuildFile section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | DE2CC6052B5E655200213707 /* Product.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Product.swift; sourceTree = ""; }; 34 | DE2CC6072B5E659B00213707 /* ProductApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductApiService.swift; sourceTree = ""; }; 35 | DE2CC6092B5E835200213707 /* ProductViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductViewModel.swift; sourceTree = ""; }; 36 | DE2CC60C2B5E853A00213707 /* ProductListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductListView.swift; sourceTree = ""; }; 37 | DE2CC60E2B5E874000213707 /* ProductDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductDetailView.swift; sourceTree = ""; }; 38 | DE4A1EDC2B50327600C198BC /* ApiServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiServiceProtocol.swift; sourceTree = ""; }; 39 | DEC97CE02B4F60BA0059F398 /* DINetworkManager.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DINetworkManager.app; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | DEC97CE32B4F60BA0059F398 /* DINetworkManagerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DINetworkManagerApp.swift; sourceTree = ""; }; 41 | DEC97CE52B4F60BA0059F398 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 42 | DEC97CE72B4F60BB0059F398 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | DEC97CEA2B4F60BB0059F398 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 44 | DEC97D002B4F615B0059F398 /* ApiContants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiContants.swift; sourceTree = ""; }; 45 | DEC97D022B4F61600059F398 /* AlamofireApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlamofireApiService.swift; sourceTree = ""; }; 46 | DEC97D062B4F61670059F398 /* URLSessionApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionApiService.swift; sourceTree = ""; }; 47 | DEF6523A2B51802900B173B7 /* SingleProductView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleProductView.swift; sourceTree = ""; }; 48 | /* End PBXFileReference section */ 49 | 50 | /* Begin PBXFrameworksBuildPhase section */ 51 | DEC97CDD2B4F60BA0059F398 /* Frameworks */ = { 52 | isa = PBXFrameworksBuildPhase; 53 | buildActionMask = 2147483647; 54 | files = ( 55 | DE614B242B5EA4A700A1181A /* SDWebImageSwiftUI in Frameworks */, 56 | DE614B222B5EA41E00A1181A /* SDWebImageSwiftUI in Frameworks */, 57 | DEC97CFD2B4F60E70059F398 /* Alamofire in Frameworks */, 58 | DEFEBC2C2B5F0FE50082419C /* StarRating in Frameworks */, 59 | DEFAF74E2B566B0F008EF720 /* SwiftUISnackbar in Frameworks */, 60 | DEF28AA12B569E6900249813 /* SwipeCell in Frameworks */, 61 | ); 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | /* End PBXFrameworksBuildPhase section */ 65 | 66 | /* Begin PBXGroup section */ 67 | DE2CC60B2B5E852800213707 /* Product */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | DE2CC60C2B5E853A00213707 /* ProductListView.swift */, 71 | DE2CC60E2B5E874000213707 /* ProductDetailView.swift */, 72 | DEF6523A2B51802900B173B7 /* SingleProductView.swift */, 73 | ); 74 | path = Product; 75 | sourceTree = ""; 76 | }; 77 | DE4A1EDB2B50326300C198BC /* Protocol */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | DE4A1EDC2B50327600C198BC /* ApiServiceProtocol.swift */, 81 | ); 82 | path = Protocol; 83 | sourceTree = ""; 84 | }; 85 | DE5F53092B50285A0009C443 /* Model */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | DE2CC6052B5E655200213707 /* Product.swift */, 89 | ); 90 | path = Model; 91 | sourceTree = ""; 92 | }; 93 | DE5F530A2B5028670009C443 /* View */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | DE2CC60B2B5E852800213707 /* Product */, 97 | ); 98 | path = View; 99 | sourceTree = ""; 100 | }; 101 | DE5F530B2B50286C0009C443 /* ViewModel */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | DE2CC6092B5E835200213707 /* ProductViewModel.swift */, 105 | ); 106 | path = ViewModel; 107 | sourceTree = ""; 108 | }; 109 | DE5F530C2B5028720009C443 /* Service */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | DE4A1EDB2B50326300C198BC /* Protocol */, 113 | DE5F530D2B5028810009C443 /* BaseService */, 114 | DE2CC6072B5E659B00213707 /* ProductApiService.swift */, 115 | ); 116 | path = Service; 117 | sourceTree = ""; 118 | }; 119 | DE5F530D2B5028810009C443 /* BaseService */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | DEC97D022B4F61600059F398 /* AlamofireApiService.swift */, 123 | DEC97D062B4F61670059F398 /* URLSessionApiService.swift */, 124 | ); 125 | path = BaseService; 126 | sourceTree = ""; 127 | }; 128 | DE5F530E2B5029000009C443 /* Resources */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | DEC97CE72B4F60BB0059F398 /* Assets.xcassets */, 132 | DEC97CE92B4F60BB0059F398 /* Preview Content */, 133 | ); 134 | path = Resources; 135 | sourceTree = ""; 136 | }; 137 | DE5F530F2B50294F0009C443 /* Helpers */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | DEC97D002B4F615B0059F398 /* ApiContants.swift */, 141 | ); 142 | path = Helpers; 143 | sourceTree = ""; 144 | }; 145 | DEC97CD72B4F60BA0059F398 = { 146 | isa = PBXGroup; 147 | children = ( 148 | DEC97CE22B4F60BA0059F398 /* DINetworkManager */, 149 | DEC97CE12B4F60BA0059F398 /* Products */, 150 | ); 151 | sourceTree = ""; 152 | }; 153 | DEC97CE12B4F60BA0059F398 /* Products */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | DEC97CE02B4F60BA0059F398 /* DINetworkManager.app */, 157 | ); 158 | name = Products; 159 | sourceTree = ""; 160 | }; 161 | DEC97CE22B4F60BA0059F398 /* DINetworkManager */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | DE5F53092B50285A0009C443 /* Model */, 165 | DE5F530A2B5028670009C443 /* View */, 166 | DE5F530B2B50286C0009C443 /* ViewModel */, 167 | DE5F530C2B5028720009C443 /* Service */, 168 | DE5F530F2B50294F0009C443 /* Helpers */, 169 | DE5F530E2B5029000009C443 /* Resources */, 170 | DEC97CE52B4F60BA0059F398 /* ContentView.swift */, 171 | DEC97CE32B4F60BA0059F398 /* DINetworkManagerApp.swift */, 172 | ); 173 | path = DINetworkManager; 174 | sourceTree = ""; 175 | }; 176 | DEC97CE92B4F60BB0059F398 /* Preview Content */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | DEC97CEA2B4F60BB0059F398 /* Preview Assets.xcassets */, 180 | ); 181 | path = "Preview Content"; 182 | sourceTree = ""; 183 | }; 184 | /* End PBXGroup section */ 185 | 186 | /* Begin PBXNativeTarget section */ 187 | DEC97CDF2B4F60BA0059F398 /* DINetworkManager */ = { 188 | isa = PBXNativeTarget; 189 | buildConfigurationList = DEC97CEE2B4F60BB0059F398 /* Build configuration list for PBXNativeTarget "DINetworkManager" */; 190 | buildPhases = ( 191 | DEC97CDC2B4F60BA0059F398 /* Sources */, 192 | DEC97CDD2B4F60BA0059F398 /* Frameworks */, 193 | DEC97CDE2B4F60BA0059F398 /* Resources */, 194 | ); 195 | buildRules = ( 196 | ); 197 | dependencies = ( 198 | ); 199 | name = DINetworkManager; 200 | packageProductDependencies = ( 201 | DEC97CFC2B4F60E70059F398 /* Alamofire */, 202 | DEFAF74D2B566B0F008EF720 /* SwiftUISnackbar */, 203 | DEF28AA02B569E6900249813 /* SwipeCell */, 204 | DE614B212B5EA41E00A1181A /* SDWebImageSwiftUI */, 205 | DE614B232B5EA4A700A1181A /* SDWebImageSwiftUI */, 206 | DEFEBC2B2B5F0FE50082419C /* StarRating */, 207 | ); 208 | productName = DINetworkManager; 209 | productReference = DEC97CE02B4F60BA0059F398 /* DINetworkManager.app */; 210 | productType = "com.apple.product-type.application"; 211 | }; 212 | /* End PBXNativeTarget section */ 213 | 214 | /* Begin PBXProject section */ 215 | DEC97CD82B4F60BA0059F398 /* Project object */ = { 216 | isa = PBXProject; 217 | attributes = { 218 | BuildIndependentTargetsInParallel = 1; 219 | LastSwiftUpdateCheck = 1520; 220 | LastUpgradeCheck = 1520; 221 | TargetAttributes = { 222 | DEC97CDF2B4F60BA0059F398 = { 223 | CreatedOnToolsVersion = 15.2; 224 | }; 225 | }; 226 | }; 227 | buildConfigurationList = DEC97CDB2B4F60BA0059F398 /* Build configuration list for PBXProject "DINetworkManager" */; 228 | compatibilityVersion = "Xcode 14.0"; 229 | developmentRegion = en; 230 | hasScannedForEncodings = 0; 231 | knownRegions = ( 232 | en, 233 | Base, 234 | ); 235 | mainGroup = DEC97CD72B4F60BA0059F398; 236 | packageReferences = ( 237 | DEC97CFB2B4F60E70059F398 /* XCRemoteSwiftPackageReference "Alamofire" */, 238 | DEFAF74C2B566B0F008EF720 /* XCRemoteSwiftPackageReference "SwiftUISnackbar" */, 239 | DEF28A9F2B569E6900249813 /* XCRemoteSwiftPackageReference "SwipeCell" */, 240 | DE614B202B5EA41E00A1181A /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */, 241 | DEFEBC2A2B5F0FE50082419C /* XCRemoteSwiftPackageReference "StarRating" */, 242 | ); 243 | productRefGroup = DEC97CE12B4F60BA0059F398 /* Products */; 244 | projectDirPath = ""; 245 | projectRoot = ""; 246 | targets = ( 247 | DEC97CDF2B4F60BA0059F398 /* DINetworkManager */, 248 | ); 249 | }; 250 | /* End PBXProject section */ 251 | 252 | /* Begin PBXResourcesBuildPhase section */ 253 | DEC97CDE2B4F60BA0059F398 /* Resources */ = { 254 | isa = PBXResourcesBuildPhase; 255 | buildActionMask = 2147483647; 256 | files = ( 257 | DEC97CEB2B4F60BB0059F398 /* Preview Assets.xcassets in Resources */, 258 | DEC97CE82B4F60BB0059F398 /* Assets.xcassets in Resources */, 259 | ); 260 | runOnlyForDeploymentPostprocessing = 0; 261 | }; 262 | /* End PBXResourcesBuildPhase section */ 263 | 264 | /* Begin PBXSourcesBuildPhase section */ 265 | DEC97CDC2B4F60BA0059F398 /* Sources */ = { 266 | isa = PBXSourcesBuildPhase; 267 | buildActionMask = 2147483647; 268 | files = ( 269 | DEC97CE62B4F60BA0059F398 /* ContentView.swift in Sources */, 270 | DE2CC60F2B5E874000213707 /* ProductDetailView.swift in Sources */, 271 | DEC97CE42B4F60BA0059F398 /* DINetworkManagerApp.swift in Sources */, 272 | DEC97D072B4F61670059F398 /* URLSessionApiService.swift in Sources */, 273 | DEF6523B2B51802900B173B7 /* SingleProductView.swift in Sources */, 274 | DEC97D012B4F615B0059F398 /* ApiContants.swift in Sources */, 275 | DE2CC60D2B5E853A00213707 /* ProductListView.swift in Sources */, 276 | DE2CC6082B5E659B00213707 /* ProductApiService.swift in Sources */, 277 | DE4A1EDD2B50327600C198BC /* ApiServiceProtocol.swift in Sources */, 278 | DEC97D032B4F61600059F398 /* AlamofireApiService.swift in Sources */, 279 | DE2CC6062B5E655200213707 /* Product.swift in Sources */, 280 | DE2CC60A2B5E835200213707 /* ProductViewModel.swift in Sources */, 281 | ); 282 | runOnlyForDeploymentPostprocessing = 0; 283 | }; 284 | /* End PBXSourcesBuildPhase section */ 285 | 286 | /* Begin XCBuildConfiguration section */ 287 | DEC97CEC2B4F60BB0059F398 /* Debug */ = { 288 | isa = XCBuildConfiguration; 289 | buildSettings = { 290 | ALWAYS_SEARCH_USER_PATHS = NO; 291 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 292 | CLANG_ANALYZER_NONNULL = YES; 293 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 294 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 295 | CLANG_ENABLE_MODULES = YES; 296 | CLANG_ENABLE_OBJC_ARC = YES; 297 | CLANG_ENABLE_OBJC_WEAK = YES; 298 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 299 | CLANG_WARN_BOOL_CONVERSION = YES; 300 | CLANG_WARN_COMMA = YES; 301 | CLANG_WARN_CONSTANT_CONVERSION = YES; 302 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 303 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 304 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 305 | CLANG_WARN_EMPTY_BODY = YES; 306 | CLANG_WARN_ENUM_CONVERSION = YES; 307 | CLANG_WARN_INFINITE_RECURSION = YES; 308 | CLANG_WARN_INT_CONVERSION = YES; 309 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 310 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 311 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 312 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 313 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 314 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 315 | CLANG_WARN_STRICT_PROTOTYPES = YES; 316 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 317 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 318 | CLANG_WARN_UNREACHABLE_CODE = YES; 319 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 320 | COPY_PHASE_STRIP = NO; 321 | DEBUG_INFORMATION_FORMAT = dwarf; 322 | ENABLE_STRICT_OBJC_MSGSEND = YES; 323 | ENABLE_TESTABILITY = YES; 324 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 325 | GCC_C_LANGUAGE_STANDARD = gnu17; 326 | GCC_DYNAMIC_NO_PIC = NO; 327 | GCC_NO_COMMON_BLOCKS = YES; 328 | GCC_OPTIMIZATION_LEVEL = 0; 329 | GCC_PREPROCESSOR_DEFINITIONS = ( 330 | "DEBUG=1", 331 | "$(inherited)", 332 | ); 333 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 334 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 335 | GCC_WARN_UNDECLARED_SELECTOR = YES; 336 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 337 | GCC_WARN_UNUSED_FUNCTION = YES; 338 | GCC_WARN_UNUSED_VARIABLE = YES; 339 | IPHONEOS_DEPLOYMENT_TARGET = 17.2; 340 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 341 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 342 | MTL_FAST_MATH = YES; 343 | ONLY_ACTIVE_ARCH = YES; 344 | SDKROOT = iphoneos; 345 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 346 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 347 | }; 348 | name = Debug; 349 | }; 350 | DEC97CED2B4F60BB0059F398 /* Release */ = { 351 | isa = XCBuildConfiguration; 352 | buildSettings = { 353 | ALWAYS_SEARCH_USER_PATHS = NO; 354 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 355 | CLANG_ANALYZER_NONNULL = YES; 356 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 357 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 358 | CLANG_ENABLE_MODULES = YES; 359 | CLANG_ENABLE_OBJC_ARC = YES; 360 | CLANG_ENABLE_OBJC_WEAK = YES; 361 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 362 | CLANG_WARN_BOOL_CONVERSION = YES; 363 | CLANG_WARN_COMMA = YES; 364 | CLANG_WARN_CONSTANT_CONVERSION = YES; 365 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 366 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 367 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 368 | CLANG_WARN_EMPTY_BODY = YES; 369 | CLANG_WARN_ENUM_CONVERSION = YES; 370 | CLANG_WARN_INFINITE_RECURSION = YES; 371 | CLANG_WARN_INT_CONVERSION = YES; 372 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 373 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 374 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 375 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 376 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 377 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 378 | CLANG_WARN_STRICT_PROTOTYPES = YES; 379 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 380 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 381 | CLANG_WARN_UNREACHABLE_CODE = YES; 382 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 383 | COPY_PHASE_STRIP = NO; 384 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 385 | ENABLE_NS_ASSERTIONS = NO; 386 | ENABLE_STRICT_OBJC_MSGSEND = YES; 387 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 388 | GCC_C_LANGUAGE_STANDARD = gnu17; 389 | GCC_NO_COMMON_BLOCKS = YES; 390 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 391 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 392 | GCC_WARN_UNDECLARED_SELECTOR = YES; 393 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 394 | GCC_WARN_UNUSED_FUNCTION = YES; 395 | GCC_WARN_UNUSED_VARIABLE = YES; 396 | IPHONEOS_DEPLOYMENT_TARGET = 17.2; 397 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 398 | MTL_ENABLE_DEBUG_INFO = NO; 399 | MTL_FAST_MATH = YES; 400 | SDKROOT = iphoneos; 401 | SWIFT_COMPILATION_MODE = wholemodule; 402 | VALIDATE_PRODUCT = YES; 403 | }; 404 | name = Release; 405 | }; 406 | DEC97CEF2B4F60BB0059F398 /* Debug */ = { 407 | isa = XCBuildConfiguration; 408 | buildSettings = { 409 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 410 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 411 | CODE_SIGN_STYLE = Automatic; 412 | CURRENT_PROJECT_VERSION = 1; 413 | DEVELOPMENT_ASSET_PATHS = "\"DINetworkManager/Resources/Preview Content\""; 414 | DEVELOPMENT_TEAM = 5RM3B24YU6; 415 | ENABLE_PREVIEWS = YES; 416 | GENERATE_INFOPLIST_FILE = YES; 417 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 418 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 419 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 420 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 421 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 422 | LD_RUNPATH_SEARCH_PATHS = ( 423 | "$(inherited)", 424 | "@executable_path/Frameworks", 425 | ); 426 | MARKETING_VERSION = 1.0; 427 | PRODUCT_BUNDLE_IDENTIFIER = com.yuumamobile.DINetworkManager; 428 | PRODUCT_NAME = "$(TARGET_NAME)"; 429 | SWIFT_EMIT_LOC_STRINGS = YES; 430 | SWIFT_VERSION = 5.0; 431 | TARGETED_DEVICE_FAMILY = "1,2"; 432 | }; 433 | name = Debug; 434 | }; 435 | DEC97CF02B4F60BB0059F398 /* Release */ = { 436 | isa = XCBuildConfiguration; 437 | buildSettings = { 438 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 439 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 440 | CODE_SIGN_STYLE = Automatic; 441 | CURRENT_PROJECT_VERSION = 1; 442 | DEVELOPMENT_ASSET_PATHS = "\"DINetworkManager/Resources/Preview Content\""; 443 | DEVELOPMENT_TEAM = 5RM3B24YU6; 444 | ENABLE_PREVIEWS = YES; 445 | GENERATE_INFOPLIST_FILE = YES; 446 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 447 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 448 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 449 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 450 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 451 | LD_RUNPATH_SEARCH_PATHS = ( 452 | "$(inherited)", 453 | "@executable_path/Frameworks", 454 | ); 455 | MARKETING_VERSION = 1.0; 456 | PRODUCT_BUNDLE_IDENTIFIER = com.yuumamobile.DINetworkManager; 457 | PRODUCT_NAME = "$(TARGET_NAME)"; 458 | SWIFT_EMIT_LOC_STRINGS = YES; 459 | SWIFT_VERSION = 5.0; 460 | TARGETED_DEVICE_FAMILY = "1,2"; 461 | }; 462 | name = Release; 463 | }; 464 | /* End XCBuildConfiguration section */ 465 | 466 | /* Begin XCConfigurationList section */ 467 | DEC97CDB2B4F60BA0059F398 /* Build configuration list for PBXProject "DINetworkManager" */ = { 468 | isa = XCConfigurationList; 469 | buildConfigurations = ( 470 | DEC97CEC2B4F60BB0059F398 /* Debug */, 471 | DEC97CED2B4F60BB0059F398 /* Release */, 472 | ); 473 | defaultConfigurationIsVisible = 0; 474 | defaultConfigurationName = Release; 475 | }; 476 | DEC97CEE2B4F60BB0059F398 /* Build configuration list for PBXNativeTarget "DINetworkManager" */ = { 477 | isa = XCConfigurationList; 478 | buildConfigurations = ( 479 | DEC97CEF2B4F60BB0059F398 /* Debug */, 480 | DEC97CF02B4F60BB0059F398 /* Release */, 481 | ); 482 | defaultConfigurationIsVisible = 0; 483 | defaultConfigurationName = Release; 484 | }; 485 | /* End XCConfigurationList section */ 486 | 487 | /* Begin XCRemoteSwiftPackageReference section */ 488 | DE614B202B5EA41E00A1181A /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */ = { 489 | isa = XCRemoteSwiftPackageReference; 490 | repositoryURL = "https://github.com/SDWebImage/SDWebImageSwiftUI"; 491 | requirement = { 492 | kind = upToNextMajorVersion; 493 | minimumVersion = 2.2.6; 494 | }; 495 | }; 496 | DEC97CFB2B4F60E70059F398 /* XCRemoteSwiftPackageReference "Alamofire" */ = { 497 | isa = XCRemoteSwiftPackageReference; 498 | repositoryURL = "https://github.com/Alamofire/Alamofire.git"; 499 | requirement = { 500 | kind = upToNextMajorVersion; 501 | minimumVersion = 5.8.1; 502 | }; 503 | }; 504 | DEF28A9F2B569E6900249813 /* XCRemoteSwiftPackageReference "SwipeCell" */ = { 505 | isa = XCRemoteSwiftPackageReference; 506 | repositoryURL = "https://github.com/fatbobman/SwipeCell.git"; 507 | requirement = { 508 | branch = main; 509 | kind = branch; 510 | }; 511 | }; 512 | DEFAF74C2B566B0F008EF720 /* XCRemoteSwiftPackageReference "SwiftUISnackbar" */ = { 513 | isa = XCRemoteSwiftPackageReference; 514 | repositoryURL = "https://github.com/zaniluca/SwiftUISnackbar"; 515 | requirement = { 516 | branch = main; 517 | kind = branch; 518 | }; 519 | }; 520 | DEFEBC2A2B5F0FE50082419C /* XCRemoteSwiftPackageReference "StarRating" */ = { 521 | isa = XCRemoteSwiftPackageReference; 522 | repositoryURL = "https://github.com/dkk/StarRating"; 523 | requirement = { 524 | kind = upToNextMajorVersion; 525 | minimumVersion = 2.0.0; 526 | }; 527 | }; 528 | /* End XCRemoteSwiftPackageReference section */ 529 | 530 | /* Begin XCSwiftPackageProductDependency section */ 531 | DE614B212B5EA41E00A1181A /* SDWebImageSwiftUI */ = { 532 | isa = XCSwiftPackageProductDependency; 533 | package = DE614B202B5EA41E00A1181A /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */; 534 | productName = SDWebImageSwiftUI; 535 | }; 536 | DE614B232B5EA4A700A1181A /* SDWebImageSwiftUI */ = { 537 | isa = XCSwiftPackageProductDependency; 538 | package = DE614B202B5EA41E00A1181A /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */; 539 | productName = SDWebImageSwiftUI; 540 | }; 541 | DEC97CFC2B4F60E70059F398 /* Alamofire */ = { 542 | isa = XCSwiftPackageProductDependency; 543 | package = DEC97CFB2B4F60E70059F398 /* XCRemoteSwiftPackageReference "Alamofire" */; 544 | productName = Alamofire; 545 | }; 546 | DEF28AA02B569E6900249813 /* SwipeCell */ = { 547 | isa = XCSwiftPackageProductDependency; 548 | package = DEF28A9F2B569E6900249813 /* XCRemoteSwiftPackageReference "SwipeCell" */; 549 | productName = SwipeCell; 550 | }; 551 | DEFAF74D2B566B0F008EF720 /* SwiftUISnackbar */ = { 552 | isa = XCSwiftPackageProductDependency; 553 | package = DEFAF74C2B566B0F008EF720 /* XCRemoteSwiftPackageReference "SwiftUISnackbar" */; 554 | productName = SwiftUISnackbar; 555 | }; 556 | DEFEBC2B2B5F0FE50082419C /* StarRating */ = { 557 | isa = XCSwiftPackageProductDependency; 558 | package = DEFEBC2A2B5F0FE50082419C /* XCRemoteSwiftPackageReference "StarRating" */; 559 | productName = StarRating; 560 | }; 561 | /* End XCSwiftPackageProductDependency section */ 562 | }; 563 | rootObject = DEC97CD82B4F60BA0059F398 /* Project object */; 564 | } 565 | -------------------------------------------------------------------------------- /DINetworkManager.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DINetworkManager.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DINetworkManager.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "alamofire", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/Alamofire/Alamofire.git", 7 | "state" : { 8 | "revision" : "3dc6a42c7727c49bf26508e29b0a0b35f9c7e1ad", 9 | "version" : "5.8.1" 10 | } 11 | }, 12 | { 13 | "identity" : "sdwebimage", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/SDWebImage/SDWebImage.git", 16 | "state" : { 17 | "revision" : "59730af512c06fb569c119d737df4c1c499e001d", 18 | "version" : "5.18.10" 19 | } 20 | }, 21 | { 22 | "identity" : "sdwebimageswiftui", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/SDWebImage/SDWebImageSwiftUI", 25 | "state" : { 26 | "revision" : "261b6cec35686d2dc192b809ab50742b4502a73b", 27 | "version" : "2.2.6" 28 | } 29 | }, 30 | { 31 | "identity" : "starrating", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/dkk/StarRating", 34 | "state" : { 35 | "revision" : "89dba504097ddd4166bb118d615e201619a1602f", 36 | "version" : "2.0.0" 37 | } 38 | }, 39 | { 40 | "identity" : "swiftui-introspect", 41 | "kind" : "remoteSourceControl", 42 | "location" : "https://github.com/timbersoftware/SwiftUI-Introspect.git", 43 | "state" : { 44 | "revision" : "121c146fe591b1320238d054ae35c81ffa45f45a", 45 | "version" : "0.12.0" 46 | } 47 | }, 48 | { 49 | "identity" : "swiftuisnackbar", 50 | "kind" : "remoteSourceControl", 51 | "location" : "https://github.com/zaniluca/SwiftUISnackbar", 52 | "state" : { 53 | "branch" : "main", 54 | "revision" : "ff5132c91e3a3855af679e648b95c8ab60c5eeb1" 55 | } 56 | }, 57 | { 58 | "identity" : "swipecell", 59 | "kind" : "remoteSourceControl", 60 | "location" : "https://github.com/fatbobman/SwipeCell.git", 61 | "state" : { 62 | "branch" : "main", 63 | "revision" : "adb1a55d12e7160b4776035fed0a4f7d3cb3ad6b" 64 | } 65 | } 66 | ], 67 | "version" : 2 68 | } 69 | -------------------------------------------------------------------------------- /DINetworkManager.xcodeproj/xcuserdata/fatihdurmaz.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | DINetworkManager.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /DINetworkManager/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // DINetworkManager 4 | // 5 | // Created by Fatih Durmaz on 11.01.2024. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | var body: some View { 12 | NavigationStack { 13 | VStack { 14 | ProductListView(apiService: AlamofireApiService.shared) 15 | } 16 | .navigationTitle("Products") 17 | } 18 | } 19 | } 20 | 21 | #Preview { 22 | ContentView() 23 | } 24 | -------------------------------------------------------------------------------- /DINetworkManager/DINetworkManagerApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DINetworkManagerApp.swift 3 | // DINetworkManager 4 | // 5 | // Created by Fatih Durmaz on 11.01.2024. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct DINetworkManagerApp: App { 12 | 13 | init() { 14 | let appareance = UINavigationBarAppearance() 15 | appareance.configureWithDefaultBackground() 16 | UINavigationBar.appearance().standardAppearance = appareance 17 | UINavigationBar.appearance().scrollEdgeAppearance = appareance 18 | } 19 | var body: some Scene { 20 | WindowGroup { 21 | ContentView() 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /DINetworkManager/Helpers/ApiContants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Contants.swift 3 | // AlamofireGenericNetworkService 4 | // 5 | // Created by Fatih Durmaz on 11.01.2024. 6 | // 7 | 8 | import Foundation 9 | 10 | final class APIConstants { 11 | static let baseURL = URL(string: "https://dummyjson.com")! 12 | static let getProductEndpoint = baseURL.appending(path: "products") 13 | } 14 | -------------------------------------------------------------------------------- /DINetworkManager/Model/Product.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Product.swift 3 | // DINetworkManager 4 | // 5 | // Created by Fatih Durmaz on 22.01.2024. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ProductResponse: Codable { 11 | let products: [Product] 12 | } 13 | 14 | struct Product: Codable, Identifiable { 15 | let id: Int? 16 | let title, description: String 17 | let price: Int 18 | let discountPercentage, rating: Double 19 | let stock: Int 20 | let brand, category: String 21 | let thumbnail: String 22 | let images: [String] 23 | } 24 | -------------------------------------------------------------------------------- /DINetworkManager/Resources/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 | -------------------------------------------------------------------------------- /DINetworkManager/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /DINetworkManager/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DINetworkManager/Resources/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DINetworkManager/Service/BaseService/AlamofireApiService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseService.swift 3 | // AlamofireGenericNetworkService 4 | // 5 | // Created by Fatih Durmaz on 11.01.2024. 6 | // 7 | 8 | import Foundation 9 | import Alamofire 10 | 11 | class AlamofireApiService: ApiServiceProtocol { 12 | 13 | private init() { } 14 | 15 | static let shared = AlamofireApiService() 16 | 17 | func getRequest(endpoint: URL, parameters: [String: Any]?, completion: @escaping (Result) -> Void) { 18 | AF.request(endpoint, method: .get, parameters: parameters) 19 | .validate() 20 | .responseDecodable(of: T.self) { response in 21 | switch response.result { 22 | case .success(let value): 23 | completion(.success(value)) 24 | case .failure(let error): 25 | completion(.failure(error)) 26 | } 27 | } 28 | } 29 | 30 | func addRequest(endpoint: URL, data: T, completion: @escaping (Result) -> Void) { 31 | AF.request(endpoint, method: .post, parameters: data, encoder: JSONParameterEncoder.default) 32 | .validate() 33 | .responseData { response in 34 | switch response.result { 35 | case .success: 36 | completion(.success(())) 37 | case .failure(let error): 38 | completion(.failure(error)) 39 | } 40 | } 41 | } 42 | 43 | func updateRequest(endpoint: URL, data: T, completion: @escaping(Result) -> Void) { 44 | AF.request(endpoint, method: .put, parameters: data, encoder: JSONParameterEncoder.default) 45 | .validate() 46 | .responseData { response in 47 | switch response.result { 48 | case .success: 49 | completion(.success(())) 50 | case .failure(let error): 51 | completion(.failure(error)) 52 | } 53 | } 54 | } 55 | 56 | func deleteRequest(endpoint: URL, completion: @escaping (Result) -> Void) { 57 | AF.request(endpoint, method: .delete) 58 | .validate() 59 | .responseData { response in 60 | switch response.result { 61 | case .success: 62 | completion(.success(())) 63 | case .failure(let error): 64 | completion(.failure(error)) 65 | } 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /DINetworkManager/Service/BaseService/URLSessionApiService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLSessionApiService.swift 3 | // AlamofireGenericNetworkService 4 | // 5 | // Created by Fatih Durmaz on 11.01.2024. 6 | // 7 | 8 | import Foundation 9 | 10 | class URLSessionApiService { // URLSession için Protocol implemente et. 11 | 12 | private init() { } 13 | 14 | static let shared = URLSessionApiService() 15 | 16 | func getRequest(endpoint: String, completion: @escaping (Result) -> Void) { 17 | guard let url = URL(string: endpoint) else { 18 | completion(.failure(NSError(domain: "Invalid URL", code: 0, userInfo: nil))) 19 | return 20 | } 21 | 22 | URLSession.shared.dataTask(with: url) { data, response, error in 23 | if let error = error { 24 | completion(.failure(error)) 25 | return 26 | } 27 | 28 | guard let data = data else { 29 | completion(.failure(NSError(domain: "Data not found", code: 0, userInfo: nil))) 30 | return 31 | } 32 | 33 | do { 34 | let decodedData = try JSONDecoder().decode(T.self, from: data) 35 | completion(.success(decodedData)) 36 | } catch { 37 | completion(.failure(error)) 38 | } 39 | }.resume() 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /DINetworkManager/Service/ProductApiService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProductApiService.swift 3 | // DINetworkManager 4 | // 5 | // Created by Fatih Durmaz on 22.01.2024. 6 | // 7 | 8 | import Foundation 9 | 10 | class ProductApiService{ 11 | 12 | let apiService: ApiServiceProtocol 13 | 14 | init(apiService: ApiServiceProtocol) { 15 | self.apiService = apiService 16 | } 17 | 18 | func getAllProducts(parameters: [String: Any]?, completion: @escaping (Result<[Product], Error>) -> Void ){ // tüm postları getirmek için 19 | apiService.getRequest(endpoint: APIConstants.getProductEndpoint, parameters: parameters) { (result: Result) in 20 | switch result { 21 | case .success(let data): 22 | completion(.success(data.products)) 23 | case .failure(let error): 24 | completion(.failure(error)) 25 | } 26 | } 27 | } 28 | 29 | func getProductById(productId: Int, completion: @escaping (Result) -> Void ){ // yalnızca bir post'u getirmek için 30 | apiService.getRequest(endpoint: APIConstants.getProductEndpoint.appending(path: "\(productId)"), parameters: nil) { (result: Result) in 31 | switch result { 32 | case .success(let data): 33 | completion(.success(data)) 34 | case .failure(let error): 35 | completion(.failure(error)) 36 | } 37 | } 38 | } 39 | 40 | func createProduct(product: Product, completion: @escaping (Result) -> Void) { 41 | apiService.addRequest(endpoint: APIConstants.getProductEndpoint.appending(path: "add"), data: product) { result in 42 | switch result { 43 | case .success(let value): 44 | completion(.success(value)) 45 | case .failure(let error): 46 | completion(.failure(error)) 47 | } 48 | } 49 | } 50 | 51 | func updateProduct(productId: Int, product: Product, completion: @escaping (Result) -> Void) { 52 | apiService.updateRequest(endpoint: APIConstants.getProductEndpoint.appending(path: "\(productId)"), data: product) { result in 53 | switch result { 54 | case .success: 55 | completion(.success(())) 56 | case .failure(let error): 57 | completion(.failure(error)) 58 | } 59 | } 60 | } 61 | 62 | func deleteProduct(productId: Int, completion: @escaping (Result) -> Void) { // parametrik olarak da gerçekleştirebilirizi. 63 | apiService.deleteRequest(endpoint: APIConstants.getProductEndpoint.appending(path: "\(productId)")) { result in 64 | switch result { 65 | case .success(let value): 66 | completion(.success(value)) 67 | case .failure(let error): 68 | completion(.failure(error)) 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /DINetworkManager/Service/Protocol/ApiServiceProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ApiServiceProtocol.swift 3 | // DINetworkManager 4 | // 5 | // Created by Fatih Durmaz on 11.01.2024. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol ApiServiceProtocol { 11 | 12 | func getRequest(endpoint: URL, parameters: [String: Any]?, completion: @escaping(Result) -> Void ) 13 | func addRequest(endpoint: URL, data: T, completion: @escaping(Result) -> Void) 14 | func updateRequest(endpoint: URL, data: T, completion: @escaping(Result) -> Void) 15 | func deleteRequest(endpoint: URL, completion: @escaping(Result) -> Void) 16 | } 17 | -------------------------------------------------------------------------------- /DINetworkManager/View/Product/ProductDetailView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProductDetailView.swift 3 | // DINetworkManager 4 | // 5 | // Created by Fatih Durmaz on 22.01.2024. 6 | // 7 | 8 | import SwiftUI 9 | import SDWebImageSwiftUI 10 | import StarRating 11 | 12 | struct ProductDetailView: View { 13 | 14 | let product: Product 15 | @State var customConfig = StarRatingConfiguration(shadowRadius: 0) 16 | 17 | var body: some View { 18 | NavigationStack { 19 | VStack { 20 | TabView { 21 | ForEach(product.images, id: \.self) { imageUrl in 22 | WebImage(url: URL(string: imageUrl)) 23 | .resizable() // Resizable like SwiftUI.Image, you must use this 24 | .placeholder { 25 | Rectangle().foregroundColor(.gray) 26 | } 27 | .indicator(.activity) // Activity Indicator 28 | .transition(.fade(duration: 0.5)) // Fade Transition with duration 29 | .scaledToFit() 30 | .cornerRadius(8) 31 | .shadow(radius: 10) 32 | .padding() 33 | .frame(width: UIScreen.main.bounds.width * 0.9) 34 | } 35 | } 36 | .tabViewStyle(.page) 37 | .indexViewStyle(.page(backgroundDisplayMode: .always)) 38 | 39 | Divider() 40 | 41 | VStack(alignment: .leading) { 42 | Text(product.title) 43 | .font(.title) 44 | .lineLimit(2) 45 | .multilineTextAlignment(.leading) 46 | 47 | Text(product.description) 48 | .font(.body) 49 | .foregroundColor(.secondary) 50 | .lineLimit(nil) 51 | .multilineTextAlignment(.leading) 52 | 53 | Divider() 54 | 55 | HStack (alignment: .center) { 56 | Text("Price: $\(String(format: "%.2f", product.discountPercentage))") 57 | .font(.headline) 58 | .fontWeight(.bold) 59 | 60 | Spacer() 61 | 62 | StarRating(initialRating: product.rating, configuration: $customConfig) 63 | .frame(width: 200,height: 30) 64 | 65 | } 66 | 67 | Divider() 68 | 69 | HStack { 70 | Text("In Stock: \(product.stock) units") 71 | .foregroundColor(product.stock > 0 ? .green : .red) 72 | .font(.headline) 73 | .fontWeight(.bold) 74 | Spacer() 75 | 76 | Text("Category: \(product.category)") 77 | .bold() 78 | .foregroundStyle(.orange) 79 | } 80 | Spacer() 81 | } 82 | } 83 | .padding() 84 | .navigationTitle(product.title) 85 | .navigationBarTitleDisplayMode(.inline) 86 | } 87 | } 88 | } 89 | 90 | #Preview { 91 | ProductDetailView(product: Product(id: 1, title: "", description: "", price: 100, discountPercentage: 100, rating: 4, stock: 100, brand: "", category: "", thumbnail: "", images: ["",""])) 92 | } 93 | -------------------------------------------------------------------------------- /DINetworkManager/View/Product/ProductListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProductListView.swift 3 | // DINetworkManager 4 | // 5 | // Created by Fatih Durmaz on 22.01.2024. 6 | // 7 | 8 | import SwiftUI 9 | import SwiftUISnackbar 10 | import SwipeCell 11 | import SDWebImageSwiftUI 12 | 13 | struct ProductListView: View { 14 | 15 | @Bindable var viewModel: ProductViewModel 16 | init(apiService: ApiServiceProtocol) { 17 | _viewModel = Bindable(wrappedValue: ProductViewModel(productApiService: .init(apiService: apiService))) 18 | } 19 | let fakeProduct = Product(id: nil, title: "Deneme", description: "", price: 10, discountPercentage: 10, rating: 10, stock: 10, brand: "", category: "", thumbnail: "", images: [""]) 20 | 21 | 22 | // Alternatif kullanım 23 | // @StateObject var viewModel = PostViewModel(postApiService: .init(apiService: AlamofireApiService.shared)) 24 | 25 | var body: some View { 26 | 27 | Group { 28 | if viewModel.isLoading { 29 | ProgressView() 30 | .controlSize(.extraLarge) 31 | .padding() 32 | .background(.gray) 33 | .tint(.white) 34 | .clipShape(.rect(cornerRadius: 10)) 35 | 36 | 37 | } else { 38 | List(viewModel.filteredProducts) { product in 39 | 40 | let button1 = SwipeCellButton( 41 | buttonStyle: .titleAndImage, 42 | title: "Delete", 43 | systemImage: "trash", 44 | titleColor: .white, 45 | imageColor: .white, 46 | view: nil, 47 | backgroundColor: .red, 48 | action: { 49 | viewModel.deleteProduct(productId: product.id!) 50 | }, 51 | feedback: true 52 | ) 53 | 54 | let button2 = SwipeCellButton( 55 | buttonStyle: .titleAndImage, 56 | title: "Update", 57 | systemImage: "arrow.circlepath", 58 | titleColor: .white, 59 | imageColor: .white, 60 | view: nil, 61 | backgroundColor: .green, 62 | action: { 63 | viewModel.updateProduct(productId: product.id!, product: fakeProduct) 64 | }, 65 | feedback: true 66 | ) 67 | 68 | let slot1 = SwipeCellSlot(slots: [button2,button1]) 69 | 70 | NavigationLink(destination: ProductDetailView(product: product)) { 71 | HStack { 72 | WebImage(url: URL(string: product.thumbnail)) 73 | .resizable() 74 | .placeholder { 75 | Rectangle().foregroundColor(.gray) 76 | } 77 | .scaledToFit() 78 | .cornerRadius(8) 79 | .frame(width: 75, height: 75) 80 | 81 | 82 | VStack(alignment: .leading) { 83 | Text(product.title) 84 | .font(.title3) 85 | 86 | Text("Price: $\(String(format: "%.2f", product.discountPercentage))") 87 | .italic() 88 | } 89 | } 90 | .swipeCell(cellPosition: .right, leftSlot: nil, rightSlot: slot1) 91 | } 92 | } 93 | .searchable(text: $viewModel.searchText) 94 | .textInputAutocapitalization(.never) 95 | .overlay { 96 | if viewModel.filteredProducts.isEmpty { 97 | ContentUnavailableView.search(text: viewModel.searchText) 98 | } 99 | } 100 | .toolbar { 101 | Button(action: { 102 | viewModel.addProduct(product: fakeProduct) 103 | }, label: { 104 | Image(systemName: "plus.circle") 105 | .tint(.black) 106 | }) 107 | } 108 | 109 | } 110 | } 111 | .onAppear { 112 | if viewModel.isLoading { 113 | viewModel.fetchAllProducts() 114 | } 115 | } 116 | .snackbar(isShowing: $viewModel.isShowingSnackBar, title: viewModel.title, text: viewModel.message, style: viewModel.isError ? .error : .default) 117 | } 118 | } 119 | 120 | #Preview { 121 | ProductListView(apiService: AlamofireApiService.shared) 122 | } 123 | -------------------------------------------------------------------------------- /DINetworkManager/View/Product/SingleProductView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SinglePostView.swift 3 | // DINetworkManager 4 | // 5 | // Created by Fatih Durmaz on 12.01.2024. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SingleProductView: View { 11 | var viewModel = ProductViewModel(productApiService: .init(apiService: AlamofireApiService.shared)) 12 | 13 | var body: some View { 14 | NavigationStack { 15 | List { 16 | HStack { 17 | Image(systemName: "book.pages") 18 | Text(viewModel.product?.title ?? "Veri Yok") 19 | .bold() 20 | .font(.headline) 21 | } 22 | 23 | HStack { 24 | Image(systemName: "paragraphsign") 25 | Text(viewModel.product?.description ?? "Veri Yok") 26 | } 27 | } 28 | .onAppear{ 29 | viewModel.fetchProductById(productId: 1) 30 | } 31 | .navigationTitle("Single Product") 32 | } 33 | } 34 | } 35 | 36 | #Preview { 37 | SingleProductView() 38 | } 39 | -------------------------------------------------------------------------------- /DINetworkManager/ViewModel/ProductViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProductViewModel.swift 3 | // DINetworkManager 4 | // 5 | // Created by Fatih Durmaz on 22.01.2024. 6 | // 7 | 8 | import Foundation 9 | 10 | @Observable 11 | class ProductViewModel{ 12 | 13 | var products: [Product] = [] 14 | var product: Product? 15 | var isShowingSnackBar: Bool = false 16 | var isError: Bool = false 17 | var title = "" 18 | var message = "" 19 | var searchText:String = "" 20 | var isLoading: Bool = true 21 | 22 | let productApiService: ProductApiService 23 | 24 | init(productApiService: ProductApiService) { 25 | self.productApiService = productApiService 26 | } 27 | 28 | func fetchAllProducts(parameters: [String: Any]? = nil) { 29 | productApiService.getAllProducts(parameters: parameters) { result in 30 | switch result { 31 | case .success(let products): 32 | DispatchQueue.main.async { 33 | self.products = products 34 | self.isLoading = false 35 | } 36 | case .failure(let error): 37 | self.title = "Error" 38 | self.message = error.localizedDescription 39 | self.isError = true 40 | self.isShowingSnackBar = true 41 | } 42 | 43 | } 44 | } 45 | 46 | func fetchProductById(productId: Int) { 47 | productApiService.getProductById(productId: productId ) { result in 48 | switch result { 49 | case .success(let product): 50 | DispatchQueue.main.async { 51 | self.product = product 52 | } 53 | case .failure(let error): 54 | print(error.localizedDescription) 55 | } 56 | } 57 | } 58 | 59 | func addProduct(product: Product) { 60 | productApiService.createProduct(product: product) { result in 61 | switch result { 62 | case .success(): 63 | self.title = "Success" 64 | self.message = "Product added." 65 | self.isError = false 66 | case .failure(let error): 67 | print(error.localizedDescription) 68 | self.title = "Error" 69 | self.message = error.localizedDescription 70 | self.isError = true 71 | 72 | } 73 | self.isShowingSnackBar = true 74 | } 75 | } 76 | 77 | func updateProduct(productId: Int, product: Product) { 78 | productApiService.updateProduct(productId: productId, product: product) { result in 79 | switch result { 80 | case .success: 81 | self.title = "Success" 82 | self.message = "Product \(productId) updated." 83 | self.isError = false 84 | 85 | case .failure(let error): 86 | print(error.localizedDescription) 87 | self.title = "Error" 88 | self.message = error.localizedDescription 89 | self.isError = true 90 | } 91 | self.isShowingSnackBar = true 92 | } 93 | } 94 | 95 | func deleteProduct(productId: Int) { 96 | productApiService.deleteProduct(productId: productId) { result in 97 | switch result { 98 | case .success(): 99 | self.title = "Success" 100 | self.message = "Product \(productId) deleted." 101 | self.isError = false 102 | 103 | if let index = self.products.firstIndex(where: { $0.id == productId }) { 104 | self.products.remove(at: index) 105 | } 106 | 107 | case .failure(let error): 108 | print(error.localizedDescription) 109 | self.title = "Error" 110 | self.message = error.localizedDescription 111 | self.isError = true 112 | } 113 | self.isShowingSnackBar = true 114 | } 115 | } 116 | 117 | var filteredProducts: [Product] { 118 | guard !searchText.isEmpty else { return self.products } 119 | 120 | return products.filter { product in 121 | product.title.lowercased().contains(searchText.lowercased()) 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Fatih Durmaz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftUI Generic Service with Alamofire and URLSession 2 | ![Swift](https://img.shields.io/badge/Swift-5.9-orange.svg) 3 | ![Platform](https://img.shields.io/badge/Platform-iOS%2017-red.svg) 4 | ![Platform](https://img.shields.io/badge/SwiftUI-4-green.svg) 5 | ![License](https://img.shields.io/badge/License-MIT-blue.svg) 6 | 7 | This example project demonstrates Dependency Injection (DI) and Singleton design patterns using SwiftUI, along with creating a Generic Network Manager with MVVM. 8 | 9 | The project aims to fetch data from a DummyJSON API using Alamofire or URLSession. 10 | 11 | - [x] Desing Patterns 12 | - [x] MVVM 13 | - [x] Alamofire Api Service 14 | - [ ] URLSession Api Service 15 | - [ ] Auth 16 | - [x] Generic Network Layer 17 | - [x] UI Design 18 | - [x] 3rd libraries 19 | 20 | ## Product Model 21 | 22 | ```swift 23 | struct ProductResponse: Codable { 24 | let products: [Product] 25 | } 26 | 27 | struct Product: Codable, Identifiable { 28 | let id: Int? 29 | let title, description: String 30 | let price: Int 31 | let discountPercentage, rating: Double 32 | let stock: Int 33 | let brand, category: String 34 | let thumbnail: String 35 | let images: [String] 36 | } 37 | ``` 38 | 39 | ## ApiServiceProtocol Protocol 40 | 41 | The protocol named ApiServiceProtocol defines the necessary functionality for making network calls. 42 | 43 | ```swift 44 | protocol ApiServiceProtocol { 45 | func getRequest(endpoint: URL, parameters: [String: Any]?, completion: @escaping(Result) -> Void ) 46 | func addRequest(endpoint: URL, data: T, completion: @escaping(Result) -> Void) 47 | func updateRequest(endpoint: URL, data: T, completion: @escaping(Result) -> Void) 48 | func deleteRequest(endpoint: URL, completion: @escaping(Result) -> Void) 49 | } 50 | ``` 51 | ## AlamofireApiService 52 | The class named AlamofireApiService implements the ApiService protocol and designs this class as a singleton. 53 | 54 | ```swift 55 | class AlamofireApiService: ApiServiceProtocol { 56 | 57 | private init() { } 58 | 59 | static let shared = AlamofireApiService() 60 | 61 | func getRequest(endpoint: URL, parameters: [String: Any]?, completion: @escaping (Result) -> Void) { 62 | AF.request(endpoint, method: .get, parameters: parameters) 63 | .validate() 64 | .responseDecodable(of: T.self) { response in 65 | switch response.result { 66 | case .success(let value): 67 | completion(.success(value)) 68 | case .failure(let error): 69 | completion(.failure(error)) 70 | } 71 | } 72 | } 73 | 74 | // postRequest() 75 | // updateReques() 76 | // deleteRequest() 77 | ``` 78 | 79 | ## ProductApiService 80 | The class named ProductApiService performs network operations with the API service defined through Dependency Injection (DI), which is an instance implementing the ApiService protocol. 81 | 82 | ```swift 83 | class ProductApiService{ 84 | 85 | let apiService: ApiServiceProtocol 86 | 87 | init(apiService: ApiServiceProtocol) { 88 | self.apiService = apiService 89 | } 90 | 91 | func getAllProducts(parameters: [String: Any]?, completion: @escaping (Result<[Product], Error>) -> Void ){ // tüm postları getirmek için 92 | apiService.getRequest(endpoint: APIConstants.getProductEndpoint, parameters: parameters) { (result: Result) in 93 | switch result { 94 | case .success(let data): 95 | completion(.success(data.products)) 96 | case .failure(let error): 97 | completion(.failure(error)) 98 | } 99 | } 100 | } 101 | } 102 | // getProductById() 103 | // createProduct() 104 | // updateProduct() 105 | // deleteProduct() 106 | ``` 107 | 108 | ## ProductViewModel 109 | The ProductViewModel is used to fetch products and update the product interface. It injects the ApiServiceProtocol using Dependency Injection. 110 | 111 | ```swift 112 | class ProductViewModel: ObservableObject { 113 | 114 | @Published var products: [Product] = [] 115 | @Published var product: Product? 116 | @Published var isShowing: Bool = false 117 | @Published var isError: Bool = false 118 | @Published var title = "" 119 | @Published var message = "" 120 | 121 | let productApiService: ProductApiService 122 | 123 | init(productApiService: ProductApiService) { 124 | self.productApiService = productApiService 125 | } 126 | 127 | func fetchAllProducts(parameters: [String: Any]? = nil) { 128 | productApiService.getAllProducts(parameters: parameters) { result in 129 | switch result { 130 | case .success(let products): 131 | DispatchQueue.main.async { 132 | self.products = products 133 | } 134 | case .failure(let error): 135 | print(error.localizedDescription) 136 | } 137 | } 138 | } 139 | } 140 | 141 | // fetchProductById() 142 | // addProduct() 143 | // updateProduct() 144 | // deleteProduct() 145 | 146 | ``` 147 | ## View ile Kullanımı 148 | The ApiServiceProtocol is injected into ProductViewModel as a singleton using Dependency Injection, and this ViewModel is used by the ProductListView. 149 | 150 | ```swift 151 | struct ProductListView: View { 152 | 153 | @StateObject var viewModel: ProductViewModel 154 | init(apiService: ApiServiceProtocol) { 155 | _viewModel = StateObject(wrappedValue: ProductViewModel(productApiService: .init(apiService: apiService))) 156 | } 157 | 158 | var body: some View { 159 | // ... 160 | } 161 | } 162 | 163 | struct ContentView: View { 164 | var body: some View { 165 | NavigationStack { 166 | VStack { 167 | ProductListView(apiService: AlamofireApiService.shared) 168 | } 169 | .navigationTitle("Products") 170 | } 171 | } 172 | } 173 | ``` 174 | ## Özellikler 175 | 176 | - MVVM 177 | - Singleton 178 | - Dependency Injection 179 | - Generic Network Layer 180 | - Protocol Oriented Programming (POP) 181 | - Alamofire & URLSession 182 | - ContentUnavailableView 183 | - SDWebImage 184 | - SwipeActions 185 | - .searchable modifier 186 | - NavigationStack 187 | -------------------------------------------------------------------------------- /README.tr-TR.md: -------------------------------------------------------------------------------- 1 | # SwiftUI Generic Service with Alamofire and URLSession 2 | ![Swift](https://img.shields.io/badge/Swift-5.9-orange.svg) 3 | ![Platform](https://img.shields.io/badge/Platform-iOS%2017-red.svg) 4 | ![Platform](https://img.shields.io/badge/SwiftUI-4-green.svg) 5 | ![License](https://img.shields.io/badge/License-MIT-blue.svg) 6 | 7 | Bu örnek proje, SwiftUI kullanarak Dependency Injection (DI) ve Singleton tasarım desenini, MVVM ile Generic Network Manager oluşturmayı gösterir. 8 | 9 | Proje, Alamofire veya URLSession ile DummyJSON API'den verileri çekmeyi amaçlar. 10 | 11 | - [x] Desing Patterns 12 | - [x] MVVM 13 | - [x] Alamofire Api Servisi 14 | - [ ] URLSession Servisi 15 | - [ ] Auth Mekanizması 16 | - [x] Generic Network Layer 17 | - [x] UI Tasarımı 18 | - [x] 3. parti kütüphane entegrasyonları 19 | 20 | ## Product Model 21 | 22 | ```swift 23 | struct ProductResponse: Codable { 24 | let products: [Product] 25 | } 26 | 27 | struct Product: Codable, Identifiable { 28 | let id: Int? 29 | let title, description: String 30 | let price: Int 31 | let discountPercentage, rating: Double 32 | let stock: Int 33 | let brand, category: String 34 | let thumbnail: String 35 | let images: [String] 36 | } 37 | ``` 38 | 39 | ## ApiServiceProtocol Protocol 40 | 41 | `ApiServiceProtocol` adlı bir protocol, ağ çağrıları yapmak için gerekli işlevselliği tanımlar. 42 | 43 | ```swift 44 | protocol ApiServiceProtocol { 45 | func getRequest(endpoint: URL, parameters: [String: Any]?, completion: @escaping(Result) -> Void ) 46 | func addRequest(endpoint: URL, data: T, completion: @escaping(Result) -> Void) 47 | func updateRequest(endpoint: URL, data: T, completion: @escaping(Result) -> Void) 48 | func deleteRequest(endpoint: URL, completion: @escaping(Result) -> Void) 49 | } 50 | ``` 51 | ## AlamofireApiService 52 | AlamofireApiService adlı sınıf, ApiService protocolünü uygular ve bu sınıfı singleton olarak tasarlar. 53 | 54 | ```swift 55 | class AlamofireApiService: ApiServiceProtocol { 56 | 57 | private init() { } 58 | 59 | static let shared = AlamofireApiService() 60 | 61 | func getRequest(endpoint: URL, parameters: [String: Any]?, completion: @escaping (Result) -> Void) { 62 | AF.request(endpoint, method: .get, parameters: parameters) 63 | .validate() 64 | .responseDecodable(of: T.self) { response in 65 | switch response.result { 66 | case .success(let value): 67 | completion(.success(value)) 68 | case .failure(let error): 69 | completion(.failure(error)) 70 | } 71 | } 72 | } 73 | 74 | // postRequest() 75 | // updateReques() 76 | // deleteRequest() 77 | ``` 78 | 79 | ## ProductApiService 80 | ProductApiService adlı sınıf, ağ işlemleri için DI ile kendisine tanımlanacak olan (ApiService protokülü uygulayan) api servis ile işlemlerini gerçekleştirir. 81 | 82 | ```swift 83 | class ProductApiService{ 84 | 85 | let apiService: ApiServiceProtocol 86 | 87 | init(apiService: ApiServiceProtocol) { 88 | self.apiService = apiService 89 | } 90 | 91 | func getAllProducts(parameters: [String: Any]?, completion: @escaping (Result<[Product], Error>) -> Void ){ // tüm postları getirmek için 92 | apiService.getRequest(endpoint: APIConstants.getProductEndpoint, parameters: parameters) { (result: Result) in 93 | switch result { 94 | case .success(let data): 95 | completion(.success(data.products)) 96 | case .failure(let error): 97 | completion(.failure(error)) 98 | } 99 | } 100 | } 101 | } 102 | // getProductById() 103 | // createProduct() 104 | // updateProduct() 105 | // deleteProduct() 106 | ``` 107 | 108 | ## ProductViewModel 109 | ProductViewModel, ürünleri çekmek ve ürün arayüzünü güncellemek için kullanılır. Dependency Injection kullanılarak ApiServiceProtocol enjekte edilir. 110 | 111 | ```swift 112 | class ProductViewModel: ObservableObject { 113 | 114 | @Published var products: [Product] = [] 115 | @Published var product: Product? 116 | @Published var isShowing: Bool = false 117 | @Published var isError: Bool = false 118 | @Published var title = "" 119 | @Published var message = "" 120 | 121 | let productApiService: ProductApiService 122 | 123 | init(productApiService: ProductApiService) { 124 | self.productApiService = productApiService 125 | } 126 | 127 | func fetchAllProducts(parameters: [String: Any]? = nil) { 128 | productApiService.getAllProducts(parameters: parameters) { result in 129 | switch result { 130 | case .success(let products): 131 | DispatchQueue.main.async { 132 | self.products = products 133 | } 134 | case .failure(let error): 135 | print(error.localizedDescription) 136 | } 137 | } 138 | } 139 | } 140 | 141 | // fetchProductById() 142 | // addProduct() 143 | // updateProduct() 144 | // deleteProduct() 145 | 146 | ``` 147 | ## View ile Kullanımı 148 | Dependency Injection kullanılarak ApiServiceProtocol singleton olarak ProductViewModel'e enjekte edilir ve bu ViewModel, ProductListView tarafından kullanılır. 149 | 150 | ```swift 151 | struct ProductListView: View { 152 | 153 | @StateObject var viewModel: ProductViewModel 154 | init(apiService: ApiServiceProtocol) { 155 | _viewModel = StateObject(wrappedValue: ProductViewModel(productApiService: .init(apiService: apiService))) 156 | } 157 | 158 | var body: some View { 159 | // ... 160 | } 161 | } 162 | 163 | struct ContentView: View { 164 | var body: some View { 165 | NavigationStack { 166 | VStack { 167 | ProductListView(apiService: AlamofireApiService.shared) 168 | } 169 | .navigationTitle("Products") 170 | } 171 | } 172 | } 173 | ``` 174 | ## Özellikler 175 | 176 | - MVVM 177 | - Singleton 178 | - Dependency Injection 179 | - Generic Network Layer 180 | - Protocol Oriented Programming (POP) 181 | - Alamofire & URLSession 182 | - ContentUnavailableView 183 | - SDWebImage 184 | - SwipeActions 185 | - .searchable modifier 186 | - NavigationStack 187 | --------------------------------------------------------------------------------