diff --git a/HomeCafeRecipes/HomeCafeRecipes.xcodeproj/project.pbxproj b/HomeCafeRecipes/HomeCafeRecipes.xcodeproj/project.pbxproj index cd4d60f..9ff5243 100644 --- a/HomeCafeRecipes/HomeCafeRecipes.xcodeproj/project.pbxproj +++ b/HomeCafeRecipes/HomeCafeRecipes.xcodeproj/project.pbxproj @@ -1,1091 +1,1105 @@ // !$*UTF8*$! { - archiveVersion = 1; - classes = { - }; - objectVersion = 56; - objects = { + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { /* Begin PBXBuildFile section */ - 1D1283A22C15E94300C5A870 /* Recipe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1283A12C15E94300C5A870 /* Recipe.swift */; }; - 1D1283A42C15EA8100C5A870 /* RecipeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1283A32C15EA8100C5A870 /* RecipeType.swift */; }; - 1D1283AA2C15EBCF00C5A870 /* SearchFeedUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1283A92C15EBCF00C5A870 /* SearchFeedUseCase.swift */; }; - 1D1283AC2C15EBE600C5A870 /* FetchFeedListUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1283AB2C15EBE600C5A870 /* FetchFeedListUseCase.swift */; }; - 1D1283AF2C1697DB00C5A870 /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = 1D1283AE2C1697DB00C5A870 /* RxCocoa */; }; - 1D1283B12C1697DB00C5A870 /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 1D1283B02C1697DB00C5A870 /* RxSwift */; }; - 1D1283B42C16983900C5A870 /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 1D1283B32C16983900C5A870 /* RxSwift */; }; - 1D1283B62C16984E00C5A870 /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = 1D1283B52C16984E00C5A870 /* RxCocoa */; }; - 1D1283CA2C16D9C600C5A870 /* RecipeFetchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1283C92C16D9C600C5A870 /* RecipeFetchService.swift */; }; - 1D166D852C4BD58300A50963 /* RecipeListRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166D832C4BD58300A50963 /* RecipeListRouter.swift */; }; - 1D166D862C4BD58300A50963 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166D842C4BD58300A50963 /* Router.swift */; }; - 1D166D892C4BD59500A50963 /* DateFormatter+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166D882C4BD59500A50963 /* DateFormatter+Extensions.swift */; }; - 1D166D902C4BD5A400A50963 /* SelectImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166D8B2C4BD5A400A50963 /* SelectImageCell.swift */; }; - 1D166D912C4BD5A400A50963 /* AddRecipeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166D8C2C4BD5A400A50963 /* AddRecipeViewController.swift */; }; - 1D166D922C4BD5A400A50963 /* RecipeUploadImgaeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166D8D2C4BD5A400A50963 /* RecipeUploadImgaeCell.swift */; }; - 1D166D932C4BD5A400A50963 /* AddRecipeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166D8E2C4BD5A400A50963 /* AddRecipeView.swift */; }; - 1D166D942C4BD5A400A50963 /* AddRecipeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166D8F2C4BD5A400A50963 /* AddRecipeViewModel.swift */; }; - 1D166D982C4BD5CD00A50963 /* RecipeListItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166D972C4BD5CD00A50963 /* RecipeListItemViewModel.swift */; }; - 1D166D9A2C4BD5EF00A50963 /* AddRecipeInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166D992C4BD5EF00A50963 /* AddRecipeInteractor.swift */; }; - 1D166D9C2C4BD5F800A50963 /* AddRecipeUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166D9B2C4BD5F800A50963 /* AddRecipeUseCase.swift */; }; - 1D166D9F2C4BD66C00A50963 /* CGSize+addButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166D9D2C4BD66C00A50963 /* CGSize+addButton.swift */; }; - 1D166DA02C4BD66C00A50963 /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166D9E2C4BD66C00A50963 /* UIView+Extensions.swift */; }; - 1D166DA32C4BD69200A50963 /* Comment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166DA12C4BD69200A50963 /* Comment.swift */; }; - 1D166DA42C4BD69200A50963 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166DA22C4BD69200A50963 /* User.swift */; }; - 1D166DA62C4BD69800A50963 /* AddRecipeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166DA52C4BD69800A50963 /* AddRecipeError.swift */; }; - 1D166DA82C4BD6AF00A50963 /* AddRecipeRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166DA72C4BD6AF00A50963 /* AddRecipeRepository.swift */; }; - 1D166DAA2C4BD6B800A50963 /* MultipartFormDataRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166DA92C4BD6B800A50963 /* MultipartFormDataRequest.swift */; }; - 1D166DAC2C4BD6CD00A50963 /* RecipePostService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166DAB2C4BD6CD00A50963 /* RecipePostService.swift */; }; - 1D166DAE2C4BD6DB00A50963 /* ErrorResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166DAD2C4BD6DB00A50963 /* ErrorResponseDTO.swift */; }; - 1D166DB12C4BD6EA00A50963 /* RecipeUploadDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166DAF2C4BD6EA00A50963 /* RecipeUploadDTO.swift */; }; - 1D166DB22C4BD6EA00A50963 /* RecipeUploadResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166DB02C4BD6EA00A50963 /* RecipeUploadResponseDTO.swift */; }; - 1D166DB42C4BD70600A50963 /* RecipeUploadMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166DB32C4BD70600A50963 /* RecipeUploadMapper.swift */; }; - 1D166DB62C4BD7F600A50963 /* UserDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166DB52C4BD7F600A50963 /* UserDTO.swift */; }; - 1D2C16E62BE532B700C04508 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2C16E52BE532B700C04508 /* AppDelegate.swift */; }; - 1D2C16EA2BE532B700C04508 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2C16E92BE532B700C04508 /* ViewController.swift */; }; - 1D2C16FD2BE532B800C04508 /* HomeCafeRecipesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2C16FC2BE532B800C04508 /* HomeCafeRecipesTests.swift */; }; - 1D2C17072BE532B800C04508 /* HomeCafeRecipesUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2C17062BE532B800C04508 /* HomeCafeRecipesUITests.swift */; }; - 1D2C17092BE532B800C04508 /* HomeCafeRecipesUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2C17082BE532B800C04508 /* HomeCafeRecipesUITestsLaunchTests.swift */; }; - 1D2C6F652C2446D8004BB54E /* MainTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2C6F642C2446D8004BB54E /* MainTabBarController.swift */; }; - 1D2C6F6C2C27051D004BB54E /* CustomNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2C6F6B2C27051D004BB54E /* CustomNavigationBar.swift */; }; - 1D3972682C44185B00495014 /* RecipeListMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D3972672C44185B00495014 /* RecipeListMapper.swift */; }; - 1D39729E2C46C57A00495014 /* FetchRecipeDetailUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D39729D2C46C57A00495014 /* FetchRecipeDetailUseCaseTests.swift */; }; - 1D439E9C2C2C58DD008530A5 /* FetchRecipeDetailUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D439E9B2C2C58DD008530A5 /* FetchRecipeDetailUseCase.swift */; }; - 1D439E9E2C2C598A008530A5 /* RecipeDetailRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D439E9D2C2C598A008530A5 /* RecipeDetailRepository.swift */; }; - 1D439EA22C2C6997008530A5 /* RecipeDetailInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D439EA12C2C6997008530A5 /* RecipeDetailInteractor.swift */; }; - 1D4741D12C1B4F8D009381CE /* RecipeImageDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D4741CC2C1B4F8D009381CE /* RecipeImageDTO.swift */; }; - 1D4741D22C1B4F8D009381CE /* RecipeDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D4741CD2C1B4F8D009381CE /* RecipeDTO.swift */; }; - 1D4741D32C1B4F8D009381CE /* RecipePageDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D4741CE2C1B4F8D009381CE /* RecipePageDTO.swift */; }; - 1D4741D42C1B4F8D009381CE /* NetworkResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D4741CF2C1B4F8D009381CE /* NetworkResponseDTO.swift */; }; - 1D4741D72C1B4FF4009381CE /* RecipeListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D4741D62C1B4FF4009381CE /* RecipeListInteractor.swift */; }; - 1D60CC3D2C3E4F1600D08FA3 /* APIConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D60CC3C2C3E4F1600D08FA3 /* APIConfig.swift */; }; - 1D60CC402C3EB76600D08FA3 /* APIConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D60CC3C2C3E4F1600D08FA3 /* APIConfig.swift */; }; - 1D6958D82C3D5A80008604B3 /* RecipeDeatilInteractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6958D72C3D5A80008604B3 /* RecipeDeatilInteractorTests.swift */; }; - 1D6958D92C3D5AF7008604B3 /* RecipeDetailInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D439EA12C2C6997008530A5 /* RecipeDetailInteractor.swift */; }; - 1D6958DA2C3D5BA4008604B3 /* FetchRecipeDetailUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D439E9B2C2C58DD008530A5 /* FetchRecipeDetailUseCase.swift */; }; - 1D6958DB2C3D5C91008604B3 /* Recipe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1283A12C15E94300C5A870 /* Recipe.swift */; }; - 1D6958DC2C3D5E20008604B3 /* RecipeDetailRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D439E9D2C2C598A008530A5 /* RecipeDetailRepository.swift */; }; - 1D6958DE2C3D5E2C008604B3 /* RecipeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1283A32C15EA8100C5A870 /* RecipeType.swift */; }; - 1D6958DF2C3D5E35008604B3 /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE19EB02C1B42200031804A /* NetworkService.swift */; }; - 1D6958E02C3D5E3D008604B3 /* RecipeDetailError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D95A0A52C37C79500F09077 /* RecipeDetailError.swift */; }; - 1D6958E12C3D5E44008604B3 /* RecipeDetailDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D73686D2C305757000EF904 /* RecipeDetailDTO.swift */; }; - 1D6958E22C3D5E99008604B3 /* RecipeImageDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D4741CC2C1B4F8D009381CE /* RecipeImageDTO.swift */; }; - 1D6958E42C3D5EA6008604B3 /* NetworkResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D4741CF2C1B4F8D009381CE /* NetworkResponseDTO.swift */; }; - 1D73686E2C305757000EF904 /* RecipeDetailDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D73686D2C305757000EF904 /* RecipeDetailDTO.swift */; }; - 1D95A0A62C37C79500F09077 /* RecipeDetailError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D95A0A52C37C79500F09077 /* RecipeDetailError.swift */; }; - 1DDE90CF2C3590C40078DFD3 /* AddRecipeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDE90CE2C3590C40078DFD3 /* AddRecipeTests.swift */; }; - 1DDFFD842C1C324F0083B077 /* RecipeDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDFFD832C1C324F0083B077 /* RecipeDetailViewController.swift */; }; - 1DE19E9D2C1B3DC10031804A /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE19E9C2C1B3DC10031804A /* SceneDelegate.swift */; }; - 1DE19EA72C1B420A0031804A /* FeedListRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE19EA52C1B420A0031804A /* FeedListRepository.swift */; }; - 1DE19EA82C1B420A0031804A /* SearchFeedListRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE19EA62C1B420A0031804A /* SearchFeedListRepository.swift */; }; - 1DE19EB12C1B42200031804A /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE19EB02C1B42200031804A /* NetworkService.swift */; }; - 1DE19EBF2C1B422F0031804A /* RecipeDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE19EB42C1B422F0031804A /* RecipeDetailViewModel.swift */; }; - 1DE19EC02C1B422F0031804A /* RecipeDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE19EB62C1B422F0031804A /* RecipeDetailView.swift */; }; - 1DE19EC32C1B422F0031804A /* SearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE19EBB2C1B422F0031804A /* SearchBar.swift */; }; - 1DE19EC42C1B422F0031804A /* RecipeListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE19EBC2C1B422F0031804A /* RecipeListViewController.swift */; }; - 1DE19EC52C1B422F0031804A /* RecipeListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE19EBD2C1B422F0031804A /* RecipeListView.swift */; }; - 1DE19EC62C1B422F0031804A /* RecipeListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE19EBE2C1B422F0031804A /* RecipeListCell.swift */; }; - 1DE19EC82C1B4C2D0031804A /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 1DE19EC72C1B4C2D0031804A /* Kingfisher */; }; - 1DF829B42C2A7A7D00C337FC /* Fonts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF829B32C2A7A7D00C337FC /* Fonts.swift */; }; - 1DF829B72C2A7CDC00C337FC /* UIImageViewImageLoading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF829B62C2A7CDC00C337FC /* UIImageViewImageLoading.swift */; }; - 1DF829B92C2A818D00C337FC /* String+Validation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF829B82C2A818D00C337FC /* String+Validation.swift */; }; + 1D1283A22C15E94300C5A870 /* Recipe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1283A12C15E94300C5A870 /* Recipe.swift */; }; + 1D1283A42C15EA8100C5A870 /* RecipeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1283A32C15EA8100C5A870 /* RecipeType.swift */; }; + 1D1283A62C15EAA600C5A870 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1283A52C15EAA600C5A870 /* User.swift */; }; + 1D1283A82C15EABB00C5A870 /* Comment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1283A72C15EABB00C5A870 /* Comment.swift */; }; + 1D1283AA2C15EBCF00C5A870 /* SearchFeedUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1283A92C15EBCF00C5A870 /* SearchFeedUseCase.swift */; }; + 1D1283AC2C15EBE600C5A870 /* FetchFeedListUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1283AB2C15EBE600C5A870 /* FetchFeedListUseCase.swift */; }; + 1D1283AF2C1697DB00C5A870 /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = 1D1283AE2C1697DB00C5A870 /* RxCocoa */; }; + 1D1283B12C1697DB00C5A870 /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 1D1283B02C1697DB00C5A870 /* RxSwift */; }; + 1D1283B42C16983900C5A870 /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 1D1283B32C16983900C5A870 /* RxSwift */; }; + 1D1283B62C16984E00C5A870 /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = 1D1283B52C16984E00C5A870 /* RxCocoa */; }; + 1D1283C82C16CE7C00C5A870 /* DateFormatter+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1283C72C16CE7C00C5A870 /* DateFormatter+Extensions.swift */; }; + 1D1283CA2C16D9C600C5A870 /* RecipeFetchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1283C92C16D9C600C5A870 /* RecipeFetchService.swift */; }; + 1D166D0D2C4AD54E00A50963 /* AddRecipeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166D0C2C4AD54D00A50963 /* AddRecipeViewModel.swift */; }; + 1D166D0E2C4AD54E00A50963 /* AddRecipeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166D0C2C4AD54D00A50963 /* AddRecipeViewModel.swift */; }; + 1D166D7E2C4BBD7700A50963 /* CGSize+addButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166D7D2C4BBD7700A50963 /* CGSize+addButton.swift */; }; + 1D166D7F2C4BBD7700A50963 /* CGSize+addButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166D7D2C4BBD7700A50963 /* CGSize+addButton.swift */; }; + 1D2C16E62BE532B700C04508 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2C16E52BE532B700C04508 /* AppDelegate.swift */; }; + 1D2C16EA2BE532B700C04508 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2C16E92BE532B700C04508 /* ViewController.swift */; }; + 1D2C16FD2BE532B800C04508 /* HomeCafeRecipesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2C16FC2BE532B800C04508 /* HomeCafeRecipesTests.swift */; }; + 1D2C17072BE532B800C04508 /* HomeCafeRecipesUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2C17062BE532B800C04508 /* HomeCafeRecipesUITests.swift */; }; + 1D2C17092BE532B800C04508 /* HomeCafeRecipesUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2C17082BE532B800C04508 /* HomeCafeRecipesUITestsLaunchTests.swift */; }; + 1D2C6F652C2446D8004BB54E /* MainTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2C6F642C2446D8004BB54E /* MainTabBarController.swift */; }; + 1D2C6F682C246998004BB54E /* AddRecipeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2C6F672C246998004BB54E /* AddRecipeViewController.swift */; }; + 1D2C6F6A2C26AF9F004BB54E /* AddRecipeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2C6F692C26AF9F004BB54E /* AddRecipeView.swift */; }; + 1D2C6F6C2C27051D004BB54E /* CustomNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2C6F6B2C27051D004BB54E /* CustomNavigationBar.swift */; }; + 1D3972682C44185B00495014 /* RecipeListMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D3972672C44185B00495014 /* RecipeListMapper.swift */; }; + 1D39726A2C45610600495014 /* RecipeUploadMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D3972692C45610600495014 /* RecipeUploadMapper.swift */; }; + 1D39726C2C458CE100495014 /* MultipartFormDataRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D39726B2C458CE100495014 /* MultipartFormDataRequest.swift */; }; + 1D39729C2C45905700495014 /* MultipartFormDataRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D39726B2C458CE100495014 /* MultipartFormDataRequest.swift */; }; + 1D39729E2C46C57A00495014 /* FetchRecipeDetailUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D39729D2C46C57A00495014 /* FetchRecipeDetailUseCaseTests.swift */; }; + 1D439E9C2C2C58DD008530A5 /* FetchRecipeDetailUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D439E9B2C2C58DD008530A5 /* FetchRecipeDetailUseCase.swift */; }; + 1D439E9E2C2C598A008530A5 /* RecipeDetailRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D439E9D2C2C598A008530A5 /* RecipeDetailRepository.swift */; }; + 1D439EA22C2C6997008530A5 /* RecipeDetailInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D439EA12C2C6997008530A5 /* RecipeDetailInteractor.swift */; }; + 1D4741D12C1B4F8D009381CE /* RecipeImageDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D4741CC2C1B4F8D009381CE /* RecipeImageDTO.swift */; }; + 1D4741D22C1B4F8D009381CE /* RecipeDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D4741CD2C1B4F8D009381CE /* RecipeDTO.swift */; }; + 1D4741D32C1B4F8D009381CE /* RecipePageDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D4741CE2C1B4F8D009381CE /* RecipePageDTO.swift */; }; + 1D4741D42C1B4F8D009381CE /* NetworkResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D4741CF2C1B4F8D009381CE /* NetworkResponseDTO.swift */; }; + 1D4741D52C1B4F8D009381CE /* UserDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D4741D02C1B4F8D009381CE /* UserDTO.swift */; }; + 1D4741D72C1B4FF4009381CE /* RecipeListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D4741D62C1B4FF4009381CE /* RecipeListInteractor.swift */; }; + 1D60CC3D2C3E4F1600D08FA3 /* APIConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D60CC3C2C3E4F1600D08FA3 /* APIConfig.swift */; }; + 1D60CC402C3EB76600D08FA3 /* APIConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D60CC3C2C3E4F1600D08FA3 /* APIConfig.swift */; }; + 1D6958D22C3D0553008604B3 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6958D12C3D0553008604B3 /* Router.swift */; }; + 1D6958D42C3D059E008604B3 /* RecipeListRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6958D32C3D059E008604B3 /* RecipeListRouter.swift */; }; + 1D6958D82C3D5A80008604B3 /* RecipeDeatilInteractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6958D72C3D5A80008604B3 /* RecipeDeatilInteractorTests.swift */; }; + 1D6958D92C3D5AF7008604B3 /* RecipeDetailInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D439EA12C2C6997008530A5 /* RecipeDetailInteractor.swift */; }; + 1D6958DA2C3D5BA4008604B3 /* FetchRecipeDetailUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D439E9B2C2C58DD008530A5 /* FetchRecipeDetailUseCase.swift */; }; + 1D6958DB2C3D5C91008604B3 /* Recipe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1283A12C15E94300C5A870 /* Recipe.swift */; }; + 1D6958DC2C3D5E20008604B3 /* RecipeDetailRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D439E9D2C2C598A008530A5 /* RecipeDetailRepository.swift */; }; + 1D6958DD2C3D5E28008604B3 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1283A52C15EAA600C5A870 /* User.swift */; }; + 1D6958DE2C3D5E2C008604B3 /* RecipeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1283A32C15EA8100C5A870 /* RecipeType.swift */; }; + 1D6958DF2C3D5E35008604B3 /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE19EB02C1B42200031804A /* NetworkService.swift */; }; + 1D6958E02C3D5E3D008604B3 /* RecipeDetailError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D95A0A52C37C79500F09077 /* RecipeDetailError.swift */; }; + 1D6958E12C3D5E44008604B3 /* RecipeDetailDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D73686D2C305757000EF904 /* RecipeDetailDTO.swift */; }; + 1D6958E22C3D5E99008604B3 /* RecipeImageDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D4741CC2C1B4F8D009381CE /* RecipeImageDTO.swift */; }; + 1D6958E32C3D5E9D008604B3 /* UserDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D4741D02C1B4F8D009381CE /* UserDTO.swift */; }; + 1D6958E42C3D5EA6008604B3 /* NetworkResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D4741CF2C1B4F8D009381CE /* NetworkResponseDTO.swift */; }; + 1D6958E52C3D5F0E008604B3 /* DateFormatter+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1283C72C16CE7C00C5A870 /* DateFormatter+Extensions.swift */; }; + 1D73686C2C304D76000EF904 /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D73686B2C304D76000EF904 /* UIView+Extensions.swift */; }; + 1D73686E2C305757000EF904 /* RecipeDetailDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D73686D2C305757000EF904 /* RecipeDetailDTO.swift */; }; + 1D7368702C32BFBB000EF904 /* AddRecipeInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D73686F2C32BFBB000EF904 /* AddRecipeInteractor.swift */; }; + 1D7368722C32CEC5000EF904 /* SaveRecipeUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D7368712C32CEC5000EF904 /* SaveRecipeUseCase.swift */; }; + 1D7368742C32CF09000EF904 /* AddRecipeRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D7368732C32CF09000EF904 /* AddRecipeRepository.swift */; }; + 1D7368782C32E7FE000EF904 /* RecipePostService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D7368772C32E7FE000EF904 /* RecipePostService.swift */; }; + 1D73687A2C32EB18000EF904 /* RecipeUploadDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D7368792C32EB18000EF904 /* RecipeUploadDTO.swift */; }; + 1D7368862C33D7BE000EF904 /* RecipeUploadResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D7368852C33D7BE000EF904 /* RecipeUploadResponseDTO.swift */; }; + 1D95A0A62C37C79500F09077 /* RecipeDetailError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D95A0A52C37C79500F09077 /* RecipeDetailError.swift */; }; + 1DBC63662C47D23000DA00C2 /* AddRecipeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DBC63652C47D23000DA00C2 /* AddRecipeError.swift */; }; + 1DBC63672C47D23000DA00C2 /* AddRecipeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DBC63652C47D23000DA00C2 /* AddRecipeError.swift */; }; + 1DC7CC322C283C0200796889 /* RecipeUploadImgaeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DC7CC312C283C0200796889 /* RecipeUploadImgaeCell.swift */; }; + 1DC7CC342C294F9200796889 /* SelectImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DC7CC332C294F9200796889 /* SelectImageCell.swift */; }; + 1DDE90CF2C3590C40078DFD3 /* AddRecipeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDE90CE2C3590C40078DFD3 /* AddRecipeTests.swift */; }; + 1DDE90D12C3592E90078DFD3 /* ErrorResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDE90D02C3592E90078DFD3 /* ErrorResponseDTO.swift */; }; + 1DDFFD842C1C324F0083B077 /* RecipeDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDFFD832C1C324F0083B077 /* RecipeDetailViewController.swift */; }; + 1DE19E9D2C1B3DC10031804A /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE19E9C2C1B3DC10031804A /* SceneDelegate.swift */; }; + 1DE19EA72C1B420A0031804A /* FeedListRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE19EA52C1B420A0031804A /* FeedListRepository.swift */; }; + 1DE19EA82C1B420A0031804A /* SearchFeedListRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE19EA62C1B420A0031804A /* SearchFeedListRepository.swift */; }; + 1DE19EB12C1B42200031804A /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE19EB02C1B42200031804A /* NetworkService.swift */; }; + 1DE19EBF2C1B422F0031804A /* RecipeDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE19EB42C1B422F0031804A /* RecipeDetailViewModel.swift */; }; + 1DE19EC02C1B422F0031804A /* RecipeDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE19EB62C1B422F0031804A /* RecipeDetailView.swift */; }; + 1DE19EC22C1B422F0031804A /* RecipeListItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE19EBA2C1B422F0031804A /* RecipeListItemViewModel.swift */; }; + 1DE19EC32C1B422F0031804A /* SearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE19EBB2C1B422F0031804A /* SearchBar.swift */; }; + 1DE19EC42C1B422F0031804A /* RecipeListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE19EBC2C1B422F0031804A /* RecipeListViewController.swift */; }; + 1DE19EC52C1B422F0031804A /* RecipeListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE19EBD2C1B422F0031804A /* RecipeListView.swift */; }; + 1DE19EC62C1B422F0031804A /* RecipeListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE19EBE2C1B422F0031804A /* RecipeListCell.swift */; }; + 1DE19EC82C1B4C2D0031804A /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 1DE19EC72C1B4C2D0031804A /* Kingfisher */; }; + 1DF829B42C2A7A7D00C337FC /* Fonts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF829B32C2A7A7D00C337FC /* Fonts.swift */; }; + 1DF829B72C2A7CDC00C337FC /* UIImageViewImageLoading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF829B62C2A7CDC00C337FC /* UIImageViewImageLoading.swift */; }; + 1DF829B92C2A818D00C337FC /* String+Validation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF829B82C2A818D00C337FC /* String+Validation.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 1D2C16F92BE532B800C04508 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 1D2C16DA2BE532B700C04508 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 1D2C16E12BE532B700C04508; - remoteInfo = HomeCafeRecipes; - }; - 1D2C17032BE532B800C04508 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 1D2C16DA2BE532B700C04508 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 1D2C16E12BE532B700C04508; - remoteInfo = HomeCafeRecipes; - }; + 1D2C16F92BE532B800C04508 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1D2C16DA2BE532B700C04508 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1D2C16E12BE532B700C04508; + remoteInfo = HomeCafeRecipes; + }; + 1D2C17032BE532B800C04508 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1D2C16DA2BE532B700C04508 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1D2C16E12BE532B700C04508; + remoteInfo = HomeCafeRecipes; + }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 1D1283A12C15E94300C5A870 /* Recipe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Recipe.swift; sourceTree = ""; }; - 1D1283A32C15EA8100C5A870 /* RecipeType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeType.swift; sourceTree = ""; }; - 1D1283A92C15EBCF00C5A870 /* SearchFeedUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchFeedUseCase.swift; sourceTree = ""; }; - 1D1283AB2C15EBE600C5A870 /* FetchFeedListUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchFeedListUseCase.swift; sourceTree = ""; }; - 1D1283C92C16D9C600C5A870 /* RecipeFetchService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeFetchService.swift; sourceTree = ""; }; - 1D166D832C4BD58300A50963 /* RecipeListRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeListRouter.swift; sourceTree = ""; }; - 1D166D842C4BD58300A50963 /* Router.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = ""; }; - 1D166D882C4BD59500A50963 /* DateFormatter+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DateFormatter+Extensions.swift"; sourceTree = ""; }; - 1D166D8B2C4BD5A400A50963 /* SelectImageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectImageCell.swift; sourceTree = ""; }; - 1D166D8C2C4BD5A400A50963 /* AddRecipeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddRecipeViewController.swift; sourceTree = ""; }; - 1D166D8D2C4BD5A400A50963 /* RecipeUploadImgaeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeUploadImgaeCell.swift; sourceTree = ""; }; - 1D166D8E2C4BD5A400A50963 /* AddRecipeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddRecipeView.swift; sourceTree = ""; }; - 1D166D8F2C4BD5A400A50963 /* AddRecipeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddRecipeViewModel.swift; sourceTree = ""; }; - 1D166D972C4BD5CD00A50963 /* RecipeListItemViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeListItemViewModel.swift; sourceTree = ""; }; - 1D166D992C4BD5EF00A50963 /* AddRecipeInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddRecipeInteractor.swift; sourceTree = ""; }; - 1D166D9B2C4BD5F800A50963 /* AddRecipeUseCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddRecipeUseCase.swift; sourceTree = ""; }; - 1D166D9D2C4BD66C00A50963 /* CGSize+addButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGSize+addButton.swift"; sourceTree = ""; }; - 1D166D9E2C4BD66C00A50963 /* UIView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Extensions.swift"; sourceTree = ""; }; - 1D166DA12C4BD69200A50963 /* Comment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Comment.swift; sourceTree = ""; }; - 1D166DA22C4BD69200A50963 /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; - 1D166DA52C4BD69800A50963 /* AddRecipeError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddRecipeError.swift; sourceTree = ""; }; - 1D166DA72C4BD6AF00A50963 /* AddRecipeRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddRecipeRepository.swift; sourceTree = ""; }; - 1D166DA92C4BD6B800A50963 /* MultipartFormDataRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipartFormDataRequest.swift; sourceTree = ""; }; - 1D166DAB2C4BD6CD00A50963 /* RecipePostService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipePostService.swift; sourceTree = ""; }; - 1D166DAD2C4BD6DB00A50963 /* ErrorResponseDTO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorResponseDTO.swift; sourceTree = ""; }; - 1D166DAF2C4BD6EA00A50963 /* RecipeUploadDTO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeUploadDTO.swift; sourceTree = ""; }; - 1D166DB02C4BD6EA00A50963 /* RecipeUploadResponseDTO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeUploadResponseDTO.swift; sourceTree = ""; }; - 1D166DB32C4BD70600A50963 /* RecipeUploadMapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeUploadMapper.swift; sourceTree = ""; }; - 1D166DB52C4BD7F600A50963 /* UserDTO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDTO.swift; sourceTree = ""; }; - 1D2C16E22BE532B700C04508 /* HomeCafeRecipes.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HomeCafeRecipes.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 1D2C16E52BE532B700C04508 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 1D2C16E92BE532B700C04508 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; - 1D2C16EE2BE532B800C04508 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 1D2C16F12BE532B800C04508 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 1D2C16F32BE532B800C04508 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 1D2C16F82BE532B800C04508 /* HomeCafeRecipesTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HomeCafeRecipesTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 1D2C16FC2BE532B800C04508 /* HomeCafeRecipesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCafeRecipesTests.swift; sourceTree = ""; }; - 1D2C17022BE532B800C04508 /* HomeCafeRecipesUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HomeCafeRecipesUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 1D2C17062BE532B800C04508 /* HomeCafeRecipesUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCafeRecipesUITests.swift; sourceTree = ""; }; - 1D2C17082BE532B800C04508 /* HomeCafeRecipesUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCafeRecipesUITestsLaunchTests.swift; sourceTree = ""; }; - 1D2C6F642C2446D8004BB54E /* MainTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarController.swift; sourceTree = ""; }; - 1D2C6F6B2C27051D004BB54E /* CustomNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomNavigationBar.swift; sourceTree = ""; }; - 1D3972672C44185B00495014 /* RecipeListMapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeListMapper.swift; sourceTree = ""; }; - 1D39729D2C46C57A00495014 /* FetchRecipeDetailUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchRecipeDetailUseCaseTests.swift; sourceTree = ""; }; - 1D439E9B2C2C58DD008530A5 /* FetchRecipeDetailUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchRecipeDetailUseCase.swift; sourceTree = ""; }; - 1D439E9D2C2C598A008530A5 /* RecipeDetailRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeDetailRepository.swift; sourceTree = ""; }; - 1D439EA12C2C6997008530A5 /* RecipeDetailInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeDetailInteractor.swift; sourceTree = ""; }; - 1D4741CC2C1B4F8D009381CE /* RecipeImageDTO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeImageDTO.swift; sourceTree = ""; }; - 1D4741CD2C1B4F8D009381CE /* RecipeDTO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeDTO.swift; sourceTree = ""; }; - 1D4741CE2C1B4F8D009381CE /* RecipePageDTO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipePageDTO.swift; sourceTree = ""; }; - 1D4741CF2C1B4F8D009381CE /* NetworkResponseDTO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkResponseDTO.swift; sourceTree = ""; }; - 1D4741D62C1B4FF4009381CE /* RecipeListInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeListInteractor.swift; sourceTree = ""; }; - 1D60CC3C2C3E4F1600D08FA3 /* APIConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIConfig.swift; sourceTree = ""; }; - 1D6958D72C3D5A80008604B3 /* RecipeDeatilInteractorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeDeatilInteractorTests.swift; sourceTree = ""; }; - 1D73686D2C305757000EF904 /* RecipeDetailDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeDetailDTO.swift; sourceTree = ""; }; - 1D95A0A52C37C79500F09077 /* RecipeDetailError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeDetailError.swift; sourceTree = ""; }; - 1DDE90CE2C3590C40078DFD3 /* AddRecipeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRecipeTests.swift; sourceTree = ""; }; - 1DDFFD832C1C324F0083B077 /* RecipeDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeDetailViewController.swift; sourceTree = ""; }; - 1DE19E9C2C1B3DC10031804A /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - 1DE19EA52C1B420A0031804A /* FeedListRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedListRepository.swift; sourceTree = ""; }; - 1DE19EA62C1B420A0031804A /* SearchFeedListRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchFeedListRepository.swift; sourceTree = ""; }; - 1DE19EB02C1B42200031804A /* NetworkService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkService.swift; sourceTree = ""; }; - 1DE19EB42C1B422F0031804A /* RecipeDetailViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeDetailViewModel.swift; sourceTree = ""; }; - 1DE19EB62C1B422F0031804A /* RecipeDetailView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeDetailView.swift; sourceTree = ""; }; - 1DE19EBB2C1B422F0031804A /* SearchBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchBar.swift; sourceTree = ""; }; - 1DE19EBC2C1B422F0031804A /* RecipeListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeListViewController.swift; sourceTree = ""; }; - 1DE19EBD2C1B422F0031804A /* RecipeListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeListView.swift; sourceTree = ""; }; - 1DE19EBE2C1B422F0031804A /* RecipeListCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeListCell.swift; sourceTree = ""; }; - 1DF829B32C2A7A7D00C337FC /* Fonts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fonts.swift; sourceTree = ""; }; - 1DF829B62C2A7CDC00C337FC /* UIImageViewImageLoading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageViewImageLoading.swift; sourceTree = ""; }; - 1DF829B82C2A818D00C337FC /* String+Validation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Validation.swift"; sourceTree = ""; }; + 1D1283A12C15E94300C5A870 /* Recipe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Recipe.swift; sourceTree = ""; }; + 1D1283A32C15EA8100C5A870 /* RecipeType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeType.swift; sourceTree = ""; }; + 1D1283A52C15EAA600C5A870 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; + 1D1283A72C15EABB00C5A870 /* Comment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Comment.swift; sourceTree = ""; }; + 1D1283A92C15EBCF00C5A870 /* SearchFeedUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchFeedUseCase.swift; sourceTree = ""; }; + 1D1283AB2C15EBE600C5A870 /* FetchFeedListUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchFeedListUseCase.swift; sourceTree = ""; }; + 1D1283C72C16CE7C00C5A870 /* DateFormatter+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+Extensions.swift"; sourceTree = ""; }; + 1D1283C92C16D9C600C5A870 /* RecipeFetchService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeFetchService.swift; sourceTree = ""; }; + 1D166D0C2C4AD54D00A50963 /* AddRecipeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddRecipeViewModel.swift; sourceTree = ""; }; + 1D166D7D2C4BBD7700A50963 /* CGSize+addButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGSize+addButton.swift"; sourceTree = ""; }; + 1D2C16E22BE532B700C04508 /* HomeCafeRecipes.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HomeCafeRecipes.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 1D2C16E52BE532B700C04508 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 1D2C16E92BE532B700C04508 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 1D2C16EE2BE532B800C04508 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 1D2C16F12BE532B800C04508 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 1D2C16F32BE532B800C04508 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 1D2C16F82BE532B800C04508 /* HomeCafeRecipesTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HomeCafeRecipesTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 1D2C16FC2BE532B800C04508 /* HomeCafeRecipesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCafeRecipesTests.swift; sourceTree = ""; }; + 1D2C17022BE532B800C04508 /* HomeCafeRecipesUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HomeCafeRecipesUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 1D2C17062BE532B800C04508 /* HomeCafeRecipesUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCafeRecipesUITests.swift; sourceTree = ""; }; + 1D2C17082BE532B800C04508 /* HomeCafeRecipesUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCafeRecipesUITestsLaunchTests.swift; sourceTree = ""; }; + 1D2C6F642C2446D8004BB54E /* MainTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarController.swift; sourceTree = ""; }; + 1D2C6F672C246998004BB54E /* AddRecipeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRecipeViewController.swift; sourceTree = ""; }; + 1D2C6F692C26AF9F004BB54E /* AddRecipeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRecipeView.swift; sourceTree = ""; }; + 1D2C6F6B2C27051D004BB54E /* CustomNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomNavigationBar.swift; sourceTree = ""; }; + 1D3972672C44185B00495014 /* RecipeListMapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeListMapper.swift; sourceTree = ""; }; + 1D3972692C45610600495014 /* RecipeUploadMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeUploadMapper.swift; sourceTree = ""; }; + 1D39726B2C458CE100495014 /* MultipartFormDataRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipartFormDataRequest.swift; sourceTree = ""; }; + 1D39729D2C46C57A00495014 /* FetchRecipeDetailUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchRecipeDetailUseCaseTests.swift; sourceTree = ""; }; + 1D439E9B2C2C58DD008530A5 /* FetchRecipeDetailUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchRecipeDetailUseCase.swift; sourceTree = ""; }; + 1D439E9D2C2C598A008530A5 /* RecipeDetailRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeDetailRepository.swift; sourceTree = ""; }; + 1D439EA12C2C6997008530A5 /* RecipeDetailInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeDetailInteractor.swift; sourceTree = ""; }; + 1D4741CC2C1B4F8D009381CE /* RecipeImageDTO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeImageDTO.swift; sourceTree = ""; }; + 1D4741CD2C1B4F8D009381CE /* RecipeDTO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeDTO.swift; sourceTree = ""; }; + 1D4741CE2C1B4F8D009381CE /* RecipePageDTO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipePageDTO.swift; sourceTree = ""; }; + 1D4741CF2C1B4F8D009381CE /* NetworkResponseDTO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkResponseDTO.swift; sourceTree = ""; }; + 1D4741D02C1B4F8D009381CE /* UserDTO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDTO.swift; sourceTree = ""; }; + 1D4741D62C1B4FF4009381CE /* RecipeListInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeListInteractor.swift; sourceTree = ""; }; + 1D60CC3C2C3E4F1600D08FA3 /* APIConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIConfig.swift; sourceTree = ""; }; + 1D6958D12C3D0553008604B3 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = ""; }; + 1D6958D32C3D059E008604B3 /* RecipeListRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeListRouter.swift; sourceTree = ""; }; + 1D6958D72C3D5A80008604B3 /* RecipeDeatilInteractorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeDeatilInteractorTests.swift; sourceTree = ""; }; + 1D73686B2C304D76000EF904 /* UIView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Extensions.swift"; sourceTree = ""; }; + 1D73686D2C305757000EF904 /* RecipeDetailDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeDetailDTO.swift; sourceTree = ""; }; + 1D73686F2C32BFBB000EF904 /* AddRecipeInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRecipeInteractor.swift; sourceTree = ""; }; + 1D7368712C32CEC5000EF904 /* SaveRecipeUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveRecipeUseCase.swift; sourceTree = ""; }; + 1D7368732C32CF09000EF904 /* AddRecipeRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRecipeRepository.swift; sourceTree = ""; }; + 1D7368772C32E7FE000EF904 /* RecipePostService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipePostService.swift; sourceTree = ""; }; + 1D7368792C32EB18000EF904 /* RecipeUploadDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeUploadDTO.swift; sourceTree = ""; }; + 1D7368852C33D7BE000EF904 /* RecipeUploadResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeUploadResponseDTO.swift; sourceTree = ""; }; + 1D95A0A52C37C79500F09077 /* RecipeDetailError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeDetailError.swift; sourceTree = ""; }; + 1DBC63652C47D23000DA00C2 /* AddRecipeError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddRecipeError.swift; sourceTree = ""; }; + 1DC7CC312C283C0200796889 /* RecipeUploadImgaeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeUploadImgaeCell.swift; sourceTree = ""; }; + 1DC7CC332C294F9200796889 /* SelectImageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectImageCell.swift; sourceTree = ""; }; + 1DDE90CE2C3590C40078DFD3 /* AddRecipeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRecipeTests.swift; sourceTree = ""; }; + 1DDE90D02C3592E90078DFD3 /* ErrorResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorResponseDTO.swift; sourceTree = ""; }; + 1DDFFD832C1C324F0083B077 /* RecipeDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeDetailViewController.swift; sourceTree = ""; }; + 1DE19E9C2C1B3DC10031804A /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 1DE19EA52C1B420A0031804A /* FeedListRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedListRepository.swift; sourceTree = ""; }; + 1DE19EA62C1B420A0031804A /* SearchFeedListRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchFeedListRepository.swift; sourceTree = ""; }; + 1DE19EB02C1B42200031804A /* NetworkService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkService.swift; sourceTree = ""; }; + 1DE19EB42C1B422F0031804A /* RecipeDetailViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeDetailViewModel.swift; sourceTree = ""; }; + 1DE19EB62C1B422F0031804A /* RecipeDetailView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeDetailView.swift; sourceTree = ""; }; + 1DE19EBA2C1B422F0031804A /* RecipeListItemViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeListItemViewModel.swift; sourceTree = ""; }; + 1DE19EBB2C1B422F0031804A /* SearchBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchBar.swift; sourceTree = ""; }; + 1DE19EBC2C1B422F0031804A /* RecipeListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeListViewController.swift; sourceTree = ""; }; + 1DE19EBD2C1B422F0031804A /* RecipeListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeListView.swift; sourceTree = ""; }; + 1DE19EBE2C1B422F0031804A /* RecipeListCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipeListCell.swift; sourceTree = ""; }; + 1DF829B32C2A7A7D00C337FC /* Fonts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fonts.swift; sourceTree = ""; }; + 1DF829B62C2A7CDC00C337FC /* UIImageViewImageLoading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageViewImageLoading.swift; sourceTree = ""; }; + 1DF829B82C2A818D00C337FC /* String+Validation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Validation.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 1D2C16DF2BE532B700C04508 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 1DE19EC82C1B4C2D0031804A /* Kingfisher in Frameworks */, - 1D1283B12C1697DB00C5A870 /* RxSwift in Frameworks */, - 1D1283AF2C1697DB00C5A870 /* RxCocoa in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 1D2C16F52BE532B800C04508 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 1D2C16FF2BE532B800C04508 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 1D1283B62C16984E00C5A870 /* RxCocoa in Frameworks */, - 1D1283B42C16983900C5A870 /* RxSwift in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; + 1D2C16DF2BE532B700C04508 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1DE19EC82C1B4C2D0031804A /* Kingfisher in Frameworks */, + 1D1283B12C1697DB00C5A870 /* RxSwift in Frameworks */, + 1D1283AF2C1697DB00C5A870 /* RxCocoa in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1D2C16F52BE532B800C04508 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1D2C16FF2BE532B800C04508 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D1283B62C16984E00C5A870 /* RxCocoa in Frameworks */, + 1D1283B42C16983900C5A870 /* RxSwift in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 1D12839F2C15E7A700C5A870 /* Entities */ = { - isa = PBXGroup; - children = ( - 1D166DA12C4BD69200A50963 /* Comment.swift */, - 1D166DA22C4BD69200A50963 /* User.swift */, - 1D1283A12C15E94300C5A870 /* Recipe.swift */, - 1D1283A32C15EA8100C5A870 /* RecipeType.swift */, - 1D95A0A52C37C79500F09077 /* RecipeDetailError.swift */, - 1D166DA52C4BD69800A50963 /* AddRecipeError.swift */, - ); - path = Entities; - sourceTree = ""; - }; - 1D1283A02C15E92C00C5A870 /* UseCases */ = { - isa = PBXGroup; - children = ( - 1D166D9B2C4BD5F800A50963 /* AddRecipeUseCase.swift */, - 1D1283A92C15EBCF00C5A870 /* SearchFeedUseCase.swift */, - 1D1283AB2C15EBE600C5A870 /* FetchFeedListUseCase.swift */, - 1D439E9B2C2C58DD008530A5 /* FetchRecipeDetailUseCase.swift */, - ); - path = UseCases; - sourceTree = ""; - }; - 1D1283AD2C16974B00C5A870 /* Data */ = { - isa = PBXGroup; - children = ( - 1DE19EA42C1B420A0031804A /* Repositories */, - 1D1283BC2C16AA8100C5A870 /* Network */, - ); - path = Data; - sourceTree = ""; - }; - 1D1283B22C16983900C5A870 /* Frameworks */ = { - isa = PBXGroup; - children = ( - ); - name = Frameworks; - sourceTree = ""; - }; - 1D1283BC2C16AA8100C5A870 /* Network */ = { - isa = PBXGroup; - children = ( - 1D4741CB2C1B4F8D009381CE /* DTO */, - 1D1283C92C16D9C600C5A870 /* RecipeFetchService.swift */, - 1D166DAB2C4BD6CD00A50963 /* RecipePostService.swift */, - 1DE19EB02C1B42200031804A /* NetworkService.swift */, - 1D60CC3C2C3E4F1600D08FA3 /* APIConfig.swift */, - 1D166DA92C4BD6B800A50963 /* MultipartFormDataRequest.swift */, - ); - path = Network; - sourceTree = ""; - }; - 1D166D822C4BD58300A50963 /* Router */ = { - isa = PBXGroup; - children = ( - 1D166D832C4BD58300A50963 /* RecipeListRouter.swift */, - 1D166D842C4BD58300A50963 /* Router.swift */, - ); - path = Router; - sourceTree = ""; - }; - 1D166D872C4BD59500A50963 /* Utilities */ = { - isa = PBXGroup; - children = ( - 1D166D882C4BD59500A50963 /* DateFormatter+Extensions.swift */, - ); - path = Utilities; - sourceTree = ""; - }; - 1D166D8A2C4BD5A400A50963 /* UploadRecipe */ = { - isa = PBXGroup; - children = ( - 1D166D8B2C4BD5A400A50963 /* SelectImageCell.swift */, - 1D166D8C2C4BD5A400A50963 /* AddRecipeViewController.swift */, - 1D166D8D2C4BD5A400A50963 /* RecipeUploadImgaeCell.swift */, - 1D166D8E2C4BD5A400A50963 /* AddRecipeView.swift */, - 1D166D8F2C4BD5A400A50963 /* AddRecipeViewModel.swift */, - ); - path = UploadRecipe; - sourceTree = ""; - }; - 1D2C16D92BE532B700C04508 = { - isa = PBXGroup; - children = ( - 1D2C16E42BE532B700C04508 /* HomeCafeRecipes */, - 1D2C16FB2BE532B800C04508 /* HomeCafeRecipesTests */, - 1D2C17052BE532B800C04508 /* HomeCafeRecipesUITests */, - 1D2C16E32BE532B700C04508 /* Products */, - 1D1283B22C16983900C5A870 /* Frameworks */, - ); - sourceTree = ""; - wrapsLines = 1; - }; - 1D2C16E32BE532B700C04508 /* Products */ = { - isa = PBXGroup; - children = ( - 1D2C16E22BE532B700C04508 /* HomeCafeRecipes.app */, - 1D2C16F82BE532B800C04508 /* HomeCafeRecipesTests.xctest */, - 1D2C17022BE532B800C04508 /* HomeCafeRecipesUITests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 1D2C16E42BE532B700C04508 /* HomeCafeRecipes */ = { - isa = PBXGroup; - children = ( - 1D166D872C4BD59500A50963 /* Utilities */, - 1D166D822C4BD58300A50963 /* Router */, - 1DF829B52C2A7C8600C337FC /* Extensions */, - 1DF829B22C2A7A0B00C337FC /* Resources */, - 1DE19EB22C1B422F0031804A /* Presentation */, - 1D1283AD2C16974B00C5A870 /* Data */, - 1D740B402C15E6680001B704 /* Domain */, - 1D2C16E52BE532B700C04508 /* AppDelegate.swift */, - 1DE19E9C2C1B3DC10031804A /* SceneDelegate.swift */, - 1D2C16E92BE532B700C04508 /* ViewController.swift */, - 1D2C16EE2BE532B800C04508 /* Assets.xcassets */, - 1D2C16F02BE532B800C04508 /* LaunchScreen.storyboard */, - 1D2C16F32BE532B800C04508 /* Info.plist */, - ); - path = HomeCafeRecipes; - sourceTree = ""; - }; - 1D2C16FB2BE532B800C04508 /* HomeCafeRecipesTests */ = { - isa = PBXGroup; - children = ( - 1D2C16FC2BE532B800C04508 /* HomeCafeRecipesTests.swift */, - 1DDE90CE2C3590C40078DFD3 /* AddRecipeTests.swift */, - 1D6958D72C3D5A80008604B3 /* RecipeDeatilInteractorTests.swift */, - 1D39729D2C46C57A00495014 /* FetchRecipeDetailUseCaseTests.swift */, - ); - path = HomeCafeRecipesTests; - sourceTree = ""; - }; - 1D2C17052BE532B800C04508 /* HomeCafeRecipesUITests */ = { - isa = PBXGroup; - children = ( - 1D2C17062BE532B800C04508 /* HomeCafeRecipesUITests.swift */, - 1D2C17082BE532B800C04508 /* HomeCafeRecipesUITestsLaunchTests.swift */, - ); - path = HomeCafeRecipesUITests; - sourceTree = ""; - }; - 1D2C6F612C2446AF004BB54E /* Tabbar */ = { - isa = PBXGroup; - children = ( - 1D2C6F642C2446D8004BB54E /* MainTabBarController.swift */, - ); - path = Tabbar; - sourceTree = ""; - }; - 1D4741CB2C1B4F8D009381CE /* DTO */ = { - isa = PBXGroup; - children = ( - 1D166DB52C4BD7F600A50963 /* UserDTO.swift */, - 1D4741CC2C1B4F8D009381CE /* RecipeImageDTO.swift */, - 1D4741CD2C1B4F8D009381CE /* RecipeDTO.swift */, - 1D4741CE2C1B4F8D009381CE /* RecipePageDTO.swift */, - 1D4741CF2C1B4F8D009381CE /* NetworkResponseDTO.swift */, - 1D73686D2C305757000EF904 /* RecipeDetailDTO.swift */, - 1D166DAF2C4BD6EA00A50963 /* RecipeUploadDTO.swift */, - 1D166DB02C4BD6EA00A50963 /* RecipeUploadResponseDTO.swift */, - 1D166DAD2C4BD6DB00A50963 /* ErrorResponseDTO.swift */, - ); - path = DTO; - sourceTree = ""; - }; - 1D740B402C15E6680001B704 /* Domain */ = { - isa = PBXGroup; - children = ( - 1DE19EA12C1B41FE0031804A /* Interactor */, - 1D1283A02C15E92C00C5A870 /* UseCases */, - 1D12839F2C15E7A700C5A870 /* Entities */, - ); - path = Domain; - sourceTree = ""; - }; - 1DDFFD822C1C09AB0083B077 /* Mapper */ = { - isa = PBXGroup; - children = ( - 1D3972672C44185B00495014 /* RecipeListMapper.swift */, - 1D166DB32C4BD70600A50963 /* RecipeUploadMapper.swift */, - ); - path = Mapper; - sourceTree = ""; - }; - 1DE19EA12C1B41FE0031804A /* Interactor */ = { - isa = PBXGroup; - children = ( - 1D4741D62C1B4FF4009381CE /* RecipeListInteractor.swift */, - 1D439EA12C2C6997008530A5 /* RecipeDetailInteractor.swift */, - 1D166D992C4BD5EF00A50963 /* AddRecipeInteractor.swift */, - ); - path = Interactor; - sourceTree = ""; - }; - 1DE19EA42C1B420A0031804A /* Repositories */ = { - isa = PBXGroup; - children = ( - 1DE19EA52C1B420A0031804A /* FeedListRepository.swift */, - 1DE19EA62C1B420A0031804A /* SearchFeedListRepository.swift */, - 1D439E9D2C2C598A008530A5 /* RecipeDetailRepository.swift */, - 1D166DA72C4BD6AF00A50963 /* AddRecipeRepository.swift */, - ); - path = Repositories; - sourceTree = ""; - }; - 1DE19EB22C1B422F0031804A /* Presentation */ = { - isa = PBXGroup; - children = ( - 1D166D8A2C4BD5A400A50963 /* UploadRecipe */, - 1D2C6F612C2446AF004BB54E /* Tabbar */, - 1DDFFD822C1C09AB0083B077 /* Mapper */, - 1DE19EB32C1B422F0031804A /* Feed */, - 1DE19EB72C1B422F0031804A /* FeedList */, - 1D2C6F6B2C27051D004BB54E /* CustomNavigationBar.swift */, - ); - path = Presentation; - sourceTree = ""; - }; - 1DE19EB32C1B422F0031804A /* Feed */ = { - isa = PBXGroup; - children = ( - 1DE19EB42C1B422F0031804A /* RecipeDetailViewModel.swift */, - 1DE19EB52C1B422F0031804A /* View */, - ); - path = Feed; - sourceTree = ""; - }; - 1DE19EB52C1B422F0031804A /* View */ = { - isa = PBXGroup; - children = ( - 1DE19EB62C1B422F0031804A /* RecipeDetailView.swift */, - 1DDFFD832C1C324F0083B077 /* RecipeDetailViewController.swift */, - ); - path = View; - sourceTree = ""; - }; - 1DE19EB72C1B422F0031804A /* FeedList */ = { - isa = PBXGroup; - children = ( - 1D166D972C4BD5CD00A50963 /* RecipeListItemViewModel.swift */, - 1DE19EB92C1B422F0031804A /* View */, - ); - path = FeedList; - sourceTree = ""; - }; - 1DE19EB92C1B422F0031804A /* View */ = { - isa = PBXGroup; - children = ( - 1DE19EBB2C1B422F0031804A /* SearchBar.swift */, - 1DE19EBC2C1B422F0031804A /* RecipeListViewController.swift */, - 1DE19EBD2C1B422F0031804A /* RecipeListView.swift */, - 1DE19EBE2C1B422F0031804A /* RecipeListCell.swift */, - ); - path = View; - sourceTree = ""; - }; - 1DF829B22C2A7A0B00C337FC /* Resources */ = { - isa = PBXGroup; - children = ( - 1DF829B32C2A7A7D00C337FC /* Fonts.swift */, - ); - path = Resources; - sourceTree = ""; - }; - 1DF829B52C2A7C8600C337FC /* Extensions */ = { - isa = PBXGroup; - children = ( - 1DF829B62C2A7CDC00C337FC /* UIImageViewImageLoading.swift */, - 1DF829B82C2A818D00C337FC /* String+Validation.swift */, - 1D166D9D2C4BD66C00A50963 /* CGSize+addButton.swift */, - 1D166D9E2C4BD66C00A50963 /* UIView+Extensions.swift */, - ); - path = Extensions; - sourceTree = ""; - }; + 1D12839F2C15E7A700C5A870 /* Entities */ = { + isa = PBXGroup; + children = ( + 1D1283A12C15E94300C5A870 /* Recipe.swift */, + 1D1283A32C15EA8100C5A870 /* RecipeType.swift */, + 1D1283A52C15EAA600C5A870 /* User.swift */, + 1D1283A72C15EABB00C5A870 /* Comment.swift */, + 1D95A0A52C37C79500F09077 /* RecipeDetailError.swift */, + 1DBC63652C47D23000DA00C2 /* AddRecipeError.swift */, + ); + path = Entities; + sourceTree = ""; + }; + 1D1283A02C15E92C00C5A870 /* UseCases */ = { + isa = PBXGroup; + children = ( + 1D1283A92C15EBCF00C5A870 /* SearchFeedUseCase.swift */, + 1D1283AB2C15EBE600C5A870 /* FetchFeedListUseCase.swift */, + 1D439E9B2C2C58DD008530A5 /* FetchRecipeDetailUseCase.swift */, + 1D7368712C32CEC5000EF904 /* SaveRecipeUseCase.swift */, + ); + path = UseCases; + sourceTree = ""; + }; + 1D1283AD2C16974B00C5A870 /* Data */ = { + isa = PBXGroup; + children = ( + 1DE19EA42C1B420A0031804A /* Repositories */, + 1D1283BC2C16AA8100C5A870 /* Network */, + ); + path = Data; + sourceTree = ""; + }; + 1D1283B22C16983900C5A870 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; + 1D1283BC2C16AA8100C5A870 /* Network */ = { + isa = PBXGroup; + children = ( + 1D4741CB2C1B4F8D009381CE /* DTO */, + 1D1283C92C16D9C600C5A870 /* RecipeFetchService.swift */, + 1D7368772C32E7FE000EF904 /* RecipePostService.swift */, + 1DE19EB02C1B42200031804A /* NetworkService.swift */, + 1D60CC3C2C3E4F1600D08FA3 /* APIConfig.swift */, + 1D39726B2C458CE100495014 /* MultipartFormDataRequest.swift */, + ); + path = Network; + sourceTree = ""; + }; + 1D1283C62C16CD9200C5A870 /* Utilities */ = { + isa = PBXGroup; + children = ( + 1D1283C72C16CE7C00C5A870 /* DateFormatter+Extensions.swift */, + ); + path = Utilities; + sourceTree = ""; + }; + 1D2C16D92BE532B700C04508 = { + isa = PBXGroup; + children = ( + 1D2C16E42BE532B700C04508 /* HomeCafeRecipes */, + 1D2C16FB2BE532B800C04508 /* HomeCafeRecipesTests */, + 1D2C17052BE532B800C04508 /* HomeCafeRecipesUITests */, + 1D2C16E32BE532B700C04508 /* Products */, + 1D1283B22C16983900C5A870 /* Frameworks */, + ); + sourceTree = ""; + wrapsLines = 1; + }; + 1D2C16E32BE532B700C04508 /* Products */ = { + isa = PBXGroup; + children = ( + 1D2C16E22BE532B700C04508 /* HomeCafeRecipes.app */, + 1D2C16F82BE532B800C04508 /* HomeCafeRecipesTests.xctest */, + 1D2C17022BE532B800C04508 /* HomeCafeRecipesUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 1D2C16E42BE532B700C04508 /* HomeCafeRecipes */ = { + isa = PBXGroup; + children = ( + 1D439E972C2C5837008530A5 /* Router */, + 1DF829B52C2A7C8600C337FC /* Extensions */, + 1DF829B22C2A7A0B00C337FC /* Resources */, + 1DE19EB22C1B422F0031804A /* Presentation */, + 1D1283C62C16CD9200C5A870 /* Utilities */, + 1D1283AD2C16974B00C5A870 /* Data */, + 1D740B402C15E6680001B704 /* Domain */, + 1D2C16E52BE532B700C04508 /* AppDelegate.swift */, + 1DE19E9C2C1B3DC10031804A /* SceneDelegate.swift */, + 1D2C16E92BE532B700C04508 /* ViewController.swift */, + 1D2C16EE2BE532B800C04508 /* Assets.xcassets */, + 1D2C16F02BE532B800C04508 /* LaunchScreen.storyboard */, + 1D2C16F32BE532B800C04508 /* Info.plist */, + ); + path = HomeCafeRecipes; + sourceTree = ""; + }; + 1D2C16FB2BE532B800C04508 /* HomeCafeRecipesTests */ = { + isa = PBXGroup; + children = ( + 1D2C16FC2BE532B800C04508 /* HomeCafeRecipesTests.swift */, + 1DDE90CE2C3590C40078DFD3 /* AddRecipeTests.swift */, + 1D6958D72C3D5A80008604B3 /* RecipeDeatilInteractorTests.swift */, + 1D39729D2C46C57A00495014 /* FetchRecipeDetailUseCaseTests.swift */, + ); + path = HomeCafeRecipesTests; + sourceTree = ""; + }; + 1D2C17052BE532B800C04508 /* HomeCafeRecipesUITests */ = { + isa = PBXGroup; + children = ( + 1D2C17062BE532B800C04508 /* HomeCafeRecipesUITests.swift */, + 1D2C17082BE532B800C04508 /* HomeCafeRecipesUITestsLaunchTests.swift */, + ); + path = HomeCafeRecipesUITests; + sourceTree = ""; + }; + 1D2C6F612C2446AF004BB54E /* Tabbar */ = { + isa = PBXGroup; + children = ( + 1D2C6F642C2446D8004BB54E /* MainTabBarController.swift */, + ); + path = Tabbar; + sourceTree = ""; + }; + 1D2C6F662C24697F004BB54E /* UploadRecipe */ = { + isa = PBXGroup; + children = ( + 1D166D0C2C4AD54D00A50963 /* AddRecipeViewModel.swift */, + 1D2C6F672C246998004BB54E /* AddRecipeViewController.swift */, + 1D2C6F692C26AF9F004BB54E /* AddRecipeView.swift */, + 1DC7CC312C283C0200796889 /* RecipeUploadImgaeCell.swift */, + 1DC7CC332C294F9200796889 /* SelectImageCell.swift */, + ); + path = UploadRecipe; + sourceTree = ""; + }; + 1D439E972C2C5837008530A5 /* Router */ = { + isa = PBXGroup; + children = ( + 1D6958D12C3D0553008604B3 /* Router.swift */, + 1D6958D32C3D059E008604B3 /* RecipeListRouter.swift */, + ); + path = Router; + sourceTree = ""; + }; + 1D4741CB2C1B4F8D009381CE /* DTO */ = { + isa = PBXGroup; + children = ( + 1D4741CC2C1B4F8D009381CE /* RecipeImageDTO.swift */, + 1D4741CD2C1B4F8D009381CE /* RecipeDTO.swift */, + 1D4741CE2C1B4F8D009381CE /* RecipePageDTO.swift */, + 1D4741CF2C1B4F8D009381CE /* NetworkResponseDTO.swift */, + 1D4741D02C1B4F8D009381CE /* UserDTO.swift */, + 1D73686D2C305757000EF904 /* RecipeDetailDTO.swift */, + 1D7368792C32EB18000EF904 /* RecipeUploadDTO.swift */, + 1D7368852C33D7BE000EF904 /* RecipeUploadResponseDTO.swift */, + 1DDE90D02C3592E90078DFD3 /* ErrorResponseDTO.swift */, + ); + path = DTO; + sourceTree = ""; + }; + 1D740B402C15E6680001B704 /* Domain */ = { + isa = PBXGroup; + children = ( + 1DE19EA12C1B41FE0031804A /* Interactor */, + 1D1283A02C15E92C00C5A870 /* UseCases */, + 1D12839F2C15E7A700C5A870 /* Entities */, + ); + path = Domain; + sourceTree = ""; + }; + 1DDFFD822C1C09AB0083B077 /* Mapper */ = { + isa = PBXGroup; + children = ( + 1D3972672C44185B00495014 /* RecipeListMapper.swift */, + 1D3972692C45610600495014 /* RecipeUploadMapper.swift */, + ); + path = Mapper; + sourceTree = ""; + }; + 1DE19EA12C1B41FE0031804A /* Interactor */ = { + isa = PBXGroup; + children = ( + 1D4741D62C1B4FF4009381CE /* RecipeListInteractor.swift */, + 1D439EA12C2C6997008530A5 /* RecipeDetailInteractor.swift */, + 1D73686F2C32BFBB000EF904 /* AddRecipeInteractor.swift */, + ); + path = Interactor; + sourceTree = ""; + }; + 1DE19EA42C1B420A0031804A /* Repositories */ = { + isa = PBXGroup; + children = ( + 1DE19EA52C1B420A0031804A /* FeedListRepository.swift */, + 1DE19EA62C1B420A0031804A /* SearchFeedListRepository.swift */, + 1D439E9D2C2C598A008530A5 /* RecipeDetailRepository.swift */, + 1D7368732C32CF09000EF904 /* AddRecipeRepository.swift */, + ); + path = Repositories; + sourceTree = ""; + }; + 1DE19EB22C1B422F0031804A /* Presentation */ = { + isa = PBXGroup; + children = ( + 1D2C6F662C24697F004BB54E /* UploadRecipe */, + 1D2C6F612C2446AF004BB54E /* Tabbar */, + 1DDFFD822C1C09AB0083B077 /* Mapper */, + 1DE19EB32C1B422F0031804A /* Feed */, + 1DE19EB72C1B422F0031804A /* FeedList */, + 1D2C6F6B2C27051D004BB54E /* CustomNavigationBar.swift */, + ); + path = Presentation; + sourceTree = ""; + }; + 1DE19EB32C1B422F0031804A /* Feed */ = { + isa = PBXGroup; + children = ( + 1DE19EB42C1B422F0031804A /* RecipeDetailViewModel.swift */, + 1DE19EB52C1B422F0031804A /* View */, + ); + path = Feed; + sourceTree = ""; + }; + 1DE19EB52C1B422F0031804A /* View */ = { + isa = PBXGroup; + children = ( + 1DE19EB62C1B422F0031804A /* RecipeDetailView.swift */, + 1DDFFD832C1C324F0083B077 /* RecipeDetailViewController.swift */, + ); + path = View; + sourceTree = ""; + }; + 1DE19EB72C1B422F0031804A /* FeedList */ = { + isa = PBXGroup; + children = ( + 1DE19EBA2C1B422F0031804A /* RecipeListItemViewModel.swift */, + 1DE19EB92C1B422F0031804A /* View */, + ); + path = FeedList; + sourceTree = ""; + }; + 1DE19EB92C1B422F0031804A /* View */ = { + isa = PBXGroup; + children = ( + 1DE19EBB2C1B422F0031804A /* SearchBar.swift */, + 1DE19EBC2C1B422F0031804A /* RecipeListViewController.swift */, + 1DE19EBD2C1B422F0031804A /* RecipeListView.swift */, + 1DE19EBE2C1B422F0031804A /* RecipeListCell.swift */, + ); + path = View; + sourceTree = ""; + }; + 1DF829B22C2A7A0B00C337FC /* Resources */ = { + isa = PBXGroup; + children = ( + 1DF829B32C2A7A7D00C337FC /* Fonts.swift */, + ); + path = Resources; + sourceTree = ""; + }; + 1DF829B52C2A7C8600C337FC /* Extensions */ = { + isa = PBXGroup; + children = ( + 1DF829B62C2A7CDC00C337FC /* UIImageViewImageLoading.swift */, + 1DF829B82C2A818D00C337FC /* String+Validation.swift */, + 1D73686B2C304D76000EF904 /* UIView+Extensions.swift */, + 1D166D7D2C4BBD7700A50963 /* CGSize+addButton.swift */, + ); + path = Extensions; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 1D2C16E12BE532B700C04508 /* HomeCafeRecipes */ = { - isa = PBXNativeTarget; - buildConfigurationList = 1D2C170C2BE532B800C04508 /* Build configuration list for PBXNativeTarget "HomeCafeRecipes" */; - buildPhases = ( - 1D2C16DE2BE532B700C04508 /* Sources */, - 1D2C16DF2BE532B700C04508 /* Frameworks */, - 1D2C16E02BE532B700C04508 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = HomeCafeRecipes; - packageProductDependencies = ( - 1D1283AE2C1697DB00C5A870 /* RxCocoa */, - 1D1283B02C1697DB00C5A870 /* RxSwift */, - 1DE19EC72C1B4C2D0031804A /* Kingfisher */, - ); - productName = HomeCafeRecipes; - productReference = 1D2C16E22BE532B700C04508 /* HomeCafeRecipes.app */; - productType = "com.apple.product-type.application"; - }; - 1D2C16F72BE532B800C04508 /* HomeCafeRecipesTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 1D2C170F2BE532B800C04508 /* Build configuration list for PBXNativeTarget "HomeCafeRecipesTests" */; - buildPhases = ( - 1D2C16F42BE532B800C04508 /* Sources */, - 1D2C16F52BE532B800C04508 /* Frameworks */, - 1D2C16F62BE532B800C04508 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 1D2C16FA2BE532B800C04508 /* PBXTargetDependency */, - ); - name = HomeCafeRecipesTests; - productName = HomeCafeRecipesTests; - productReference = 1D2C16F82BE532B800C04508 /* HomeCafeRecipesTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 1D2C17012BE532B800C04508 /* HomeCafeRecipesUITests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 1D2C17122BE532B800C04508 /* Build configuration list for PBXNativeTarget "HomeCafeRecipesUITests" */; - buildPhases = ( - 1D2C16FE2BE532B800C04508 /* Sources */, - 1D2C16FF2BE532B800C04508 /* Frameworks */, - 1D2C17002BE532B800C04508 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 1D2C17042BE532B800C04508 /* PBXTargetDependency */, - ); - name = HomeCafeRecipesUITests; - packageProductDependencies = ( - 1D1283B32C16983900C5A870 /* RxSwift */, - 1D1283B52C16984E00C5A870 /* RxCocoa */, - ); - productName = HomeCafeRecipesUITests; - productReference = 1D2C17022BE532B800C04508 /* HomeCafeRecipesUITests.xctest */; - productType = "com.apple.product-type.bundle.ui-testing"; - }; + 1D2C16E12BE532B700C04508 /* HomeCafeRecipes */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1D2C170C2BE532B800C04508 /* Build configuration list for PBXNativeTarget "HomeCafeRecipes" */; + buildPhases = ( + 1D2C16DE2BE532B700C04508 /* Sources */, + 1D2C16DF2BE532B700C04508 /* Frameworks */, + 1D2C16E02BE532B700C04508 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = HomeCafeRecipes; + packageProductDependencies = ( + 1D1283AE2C1697DB00C5A870 /* RxCocoa */, + 1D1283B02C1697DB00C5A870 /* RxSwift */, + 1DE19EC72C1B4C2D0031804A /* Kingfisher */, + ); + productName = HomeCafeRecipes; + productReference = 1D2C16E22BE532B700C04508 /* HomeCafeRecipes.app */; + productType = "com.apple.product-type.application"; + }; + 1D2C16F72BE532B800C04508 /* HomeCafeRecipesTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1D2C170F2BE532B800C04508 /* Build configuration list for PBXNativeTarget "HomeCafeRecipesTests" */; + buildPhases = ( + 1D2C16F42BE532B800C04508 /* Sources */, + 1D2C16F52BE532B800C04508 /* Frameworks */, + 1D2C16F62BE532B800C04508 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 1D2C16FA2BE532B800C04508 /* PBXTargetDependency */, + ); + name = HomeCafeRecipesTests; + productName = HomeCafeRecipesTests; + productReference = 1D2C16F82BE532B800C04508 /* HomeCafeRecipesTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 1D2C17012BE532B800C04508 /* HomeCafeRecipesUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1D2C17122BE532B800C04508 /* Build configuration list for PBXNativeTarget "HomeCafeRecipesUITests" */; + buildPhases = ( + 1D2C16FE2BE532B800C04508 /* Sources */, + 1D2C16FF2BE532B800C04508 /* Frameworks */, + 1D2C17002BE532B800C04508 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 1D2C17042BE532B800C04508 /* PBXTargetDependency */, + ); + name = HomeCafeRecipesUITests; + packageProductDependencies = ( + 1D1283B32C16983900C5A870 /* RxSwift */, + 1D1283B52C16984E00C5A870 /* RxCocoa */, + ); + productName = HomeCafeRecipesUITests; + productReference = 1D2C17022BE532B800C04508 /* HomeCafeRecipesUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ - 1D2C16DA2BE532B700C04508 /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1500; - LastUpgradeCheck = 1500; - TargetAttributes = { - 1D2C16E12BE532B700C04508 = { - CreatedOnToolsVersion = 15.0; - }; - 1D2C16F72BE532B800C04508 = { - CreatedOnToolsVersion = 15.0; - TestTargetID = 1D2C16E12BE532B700C04508; - }; - 1D2C17012BE532B800C04508 = { - CreatedOnToolsVersion = 15.0; - TestTargetID = 1D2C16E12BE532B700C04508; - }; - }; - }; - buildConfigurationList = 1D2C16DD2BE532B700C04508 /* Build configuration list for PBXProject "HomeCafeRecipes" */; - compatibilityVersion = "Xcode 14.0"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 1D2C16D92BE532B700C04508; - packageReferences = ( - 1D740B3F2C15E1EC0001B704 /* XCRemoteSwiftPackageReference "RxSwift" */, - 1D6C5ACD2C1A8C580052A36C /* XCRemoteSwiftPackageReference "Kingfisher" */, - ); - productRefGroup = 1D2C16E32BE532B700C04508 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 1D2C16E12BE532B700C04508 /* HomeCafeRecipes */, - 1D2C16F72BE532B800C04508 /* HomeCafeRecipesTests */, - 1D2C17012BE532B800C04508 /* HomeCafeRecipesUITests */, - ); - }; + 1D2C16DA2BE532B700C04508 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1500; + LastUpgradeCheck = 1500; + TargetAttributes = { + 1D2C16E12BE532B700C04508 = { + CreatedOnToolsVersion = 15.0; + }; + 1D2C16F72BE532B800C04508 = { + CreatedOnToolsVersion = 15.0; + TestTargetID = 1D2C16E12BE532B700C04508; + }; + 1D2C17012BE532B800C04508 = { + CreatedOnToolsVersion = 15.0; + TestTargetID = 1D2C16E12BE532B700C04508; + }; + }; + }; + buildConfigurationList = 1D2C16DD2BE532B700C04508 /* Build configuration list for PBXProject "HomeCafeRecipes" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 1D2C16D92BE532B700C04508; + packageReferences = ( + 1D740B3F2C15E1EC0001B704 /* XCRemoteSwiftPackageReference "RxSwift" */, + 1D6C5ACD2C1A8C580052A36C /* XCRemoteSwiftPackageReference "Kingfisher" */, + ); + productRefGroup = 1D2C16E32BE532B700C04508 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 1D2C16E12BE532B700C04508 /* HomeCafeRecipes */, + 1D2C16F72BE532B800C04508 /* HomeCafeRecipesTests */, + 1D2C17012BE532B800C04508 /* HomeCafeRecipesUITests */, + ); + }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - 1D2C16E02BE532B700C04508 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 1D2C16F62BE532B800C04508 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 1D2C17002BE532B800C04508 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; + 1D2C16E02BE532B700C04508 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1D2C16F62BE532B800C04508 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1D2C17002BE532B800C04508 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 1D2C16DE2BE532B700C04508 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 1D439E9E2C2C598A008530A5 /* RecipeDetailRepository.swift in Sources */, - 1D166DB22C4BD6EA00A50963 /* RecipeUploadResponseDTO.swift in Sources */, - 1D2C6F6C2C27051D004BB54E /* CustomNavigationBar.swift in Sources */, - 1D3972682C44185B00495014 /* RecipeListMapper.swift in Sources */, - 1D166D902C4BD5A400A50963 /* SelectImageCell.swift in Sources */, - 1D2C16EA2BE532B700C04508 /* ViewController.swift in Sources */, - 1DE19EC52C1B422F0031804A /* RecipeListView.swift in Sources */, - 1D166D852C4BD58300A50963 /* RecipeListRouter.swift in Sources */, - 1D4741D32C1B4F8D009381CE /* RecipePageDTO.swift in Sources */, - 1D2C6F652C2446D8004BB54E /* MainTabBarController.swift in Sources */, - 1DDFFD842C1C324F0083B077 /* RecipeDetailViewController.swift in Sources */, - 1D2C16E62BE532B700C04508 /* AppDelegate.swift in Sources */, - 1DE19EB12C1B42200031804A /* NetworkService.swift in Sources */, - 1D166D932C4BD5A400A50963 /* AddRecipeView.swift in Sources */, - 1D166D892C4BD59500A50963 /* DateFormatter+Extensions.swift in Sources */, - 1D166D9C2C4BD5F800A50963 /* AddRecipeUseCase.swift in Sources */, - 1D95A0A62C37C79500F09077 /* RecipeDetailError.swift in Sources */, - 1D1283AC2C15EBE600C5A870 /* FetchFeedListUseCase.swift in Sources */, - 1D166D912C4BD5A400A50963 /* AddRecipeViewController.swift in Sources */, - 1D166DB42C4BD70600A50963 /* RecipeUploadMapper.swift in Sources */, - 1D166D862C4BD58300A50963 /* Router.swift in Sources */, - 1D166D982C4BD5CD00A50963 /* RecipeListItemViewModel.swift in Sources */, - 1D166DAC2C4BD6CD00A50963 /* RecipePostService.swift in Sources */, - 1DF829B72C2A7CDC00C337FC /* UIImageViewImageLoading.swift in Sources */, - 1D60CC3D2C3E4F1600D08FA3 /* APIConfig.swift in Sources */, - 1D1283A42C15EA8100C5A870 /* RecipeType.swift in Sources */, - 1D166D9F2C4BD66C00A50963 /* CGSize+addButton.swift in Sources */, - 1DF829B42C2A7A7D00C337FC /* Fonts.swift in Sources */, - 1D4741D22C1B4F8D009381CE /* RecipeDTO.swift in Sources */, - 1DE19EC02C1B422F0031804A /* RecipeDetailView.swift in Sources */, - 1D166D922C4BD5A400A50963 /* RecipeUploadImgaeCell.swift in Sources */, - 1D1283AA2C15EBCF00C5A870 /* SearchFeedUseCase.swift in Sources */, - 1D166DA82C4BD6AF00A50963 /* AddRecipeRepository.swift in Sources */, - 1DE19EA82C1B420A0031804A /* SearchFeedListRepository.swift in Sources */, - 1DE19EC32C1B422F0031804A /* SearchBar.swift in Sources */, - 1D439EA22C2C6997008530A5 /* RecipeDetailInteractor.swift in Sources */, - 1D166D9A2C4BD5EF00A50963 /* AddRecipeInteractor.swift in Sources */, - 1D73686E2C305757000EF904 /* RecipeDetailDTO.swift in Sources */, - 1D166DA32C4BD69200A50963 /* Comment.swift in Sources */, - 1D166D942C4BD5A400A50963 /* AddRecipeViewModel.swift in Sources */, - 1D4741D72C1B4FF4009381CE /* RecipeListInteractor.swift in Sources */, - 1DE19E9D2C1B3DC10031804A /* SceneDelegate.swift in Sources */, - 1D4741D12C1B4F8D009381CE /* RecipeImageDTO.swift in Sources */, - 1DE19EA72C1B420A0031804A /* FeedListRepository.swift in Sources */, - 1D166DA42C4BD69200A50963 /* User.swift in Sources */, - 1DE19EC62C1B422F0031804A /* RecipeListCell.swift in Sources */, - 1D166DAA2C4BD6B800A50963 /* MultipartFormDataRequest.swift in Sources */, - 1D166DB12C4BD6EA00A50963 /* RecipeUploadDTO.swift in Sources */, - 1D166DB62C4BD7F600A50963 /* UserDTO.swift in Sources */, - 1DF829B92C2A818D00C337FC /* String+Validation.swift in Sources */, - 1DE19EC42C1B422F0031804A /* RecipeListViewController.swift in Sources */, - 1DE19EBF2C1B422F0031804A /* RecipeDetailViewModel.swift in Sources */, - 1D166DAE2C4BD6DB00A50963 /* ErrorResponseDTO.swift in Sources */, - 1D166DA02C4BD66C00A50963 /* UIView+Extensions.swift in Sources */, - 1D1283A22C15E94300C5A870 /* Recipe.swift in Sources */, - 1D1283CA2C16D9C600C5A870 /* RecipeFetchService.swift in Sources */, - 1D4741D42C1B4F8D009381CE /* NetworkResponseDTO.swift in Sources */, - 1D166DA62C4BD69800A50963 /* AddRecipeError.swift in Sources */, - 1D439E9C2C2C58DD008530A5 /* FetchRecipeDetailUseCase.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 1D2C16F42BE532B800C04508 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 1D6958DF2C3D5E35008604B3 /* NetworkService.swift in Sources */, - 1D6958DC2C3D5E20008604B3 /* RecipeDetailRepository.swift in Sources */, - 1D6958E12C3D5E44008604B3 /* RecipeDetailDTO.swift in Sources */, - 1D6958D82C3D5A80008604B3 /* RecipeDeatilInteractorTests.swift in Sources */, - 1D60CC402C3EB76600D08FA3 /* APIConfig.swift in Sources */, - 1D6958DE2C3D5E2C008604B3 /* RecipeType.swift in Sources */, - 1D6958D92C3D5AF7008604B3 /* RecipeDetailInteractor.swift in Sources */, - 1D2C16FD2BE532B800C04508 /* HomeCafeRecipesTests.swift in Sources */, - 1D6958E42C3D5EA6008604B3 /* NetworkResponseDTO.swift in Sources */, - 1D6958DB2C3D5C91008604B3 /* Recipe.swift in Sources */, - 1D6958E02C3D5E3D008604B3 /* RecipeDetailError.swift in Sources */, - 1DDE90CF2C3590C40078DFD3 /* AddRecipeTests.swift in Sources */, - 1D6958DA2C3D5BA4008604B3 /* FetchRecipeDetailUseCase.swift in Sources */, - 1D39729E2C46C57A00495014 /* FetchRecipeDetailUseCaseTests.swift in Sources */, - 1D6958E22C3D5E99008604B3 /* RecipeImageDTO.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 1D2C16FE2BE532B800C04508 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 1D2C17092BE532B800C04508 /* HomeCafeRecipesUITestsLaunchTests.swift in Sources */, - 1D2C17072BE532B800C04508 /* HomeCafeRecipesUITests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; + 1D2C16DE2BE532B700C04508 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D439E9E2C2C598A008530A5 /* RecipeDetailRepository.swift in Sources */, + 1D2C6F6C2C27051D004BB54E /* CustomNavigationBar.swift in Sources */, + 1D3972682C44185B00495014 /* RecipeListMapper.swift in Sources */, + 1D2C16EA2BE532B700C04508 /* ViewController.swift in Sources */, + 1DE19EC52C1B422F0031804A /* RecipeListView.swift in Sources */, + 1D39726C2C458CE100495014 /* MultipartFormDataRequest.swift in Sources */, + 1D6958D22C3D0553008604B3 /* Router.swift in Sources */, + 1D4741D32C1B4F8D009381CE /* RecipePageDTO.swift in Sources */, + 1D166D7E2C4BBD7700A50963 /* CGSize+addButton.swift in Sources */, + 1D2C6F652C2446D8004BB54E /* MainTabBarController.swift in Sources */, + 1DDFFD842C1C324F0083B077 /* RecipeDetailViewController.swift in Sources */, + 1D2C16E62BE532B700C04508 /* AppDelegate.swift in Sources */, + 1D7368702C32BFBB000EF904 /* AddRecipeInteractor.swift in Sources */, + 1DE19EB12C1B42200031804A /* NetworkService.swift in Sources */, + 1DC7CC322C283C0200796889 /* RecipeUploadImgaeCell.swift in Sources */, + 1D7368782C32E7FE000EF904 /* RecipePostService.swift in Sources */, + 1D73686C2C304D76000EF904 /* UIView+Extensions.swift in Sources */, + 1DBC63662C47D23000DA00C2 /* AddRecipeError.swift in Sources */, + 1D95A0A62C37C79500F09077 /* RecipeDetailError.swift in Sources */, + 1D1283A62C15EAA600C5A870 /* User.swift in Sources */, + 1D1283AC2C15EBE600C5A870 /* FetchFeedListUseCase.swift in Sources */, + 1D1283A82C15EABB00C5A870 /* Comment.swift in Sources */, + 1D73687A2C32EB18000EF904 /* RecipeUploadDTO.swift in Sources */, + 1D2C6F6A2C26AF9F004BB54E /* AddRecipeView.swift in Sources */, + 1D39726A2C45610600495014 /* RecipeUploadMapper.swift in Sources */, + 1D1283C82C16CE7C00C5A870 /* DateFormatter+Extensions.swift in Sources */, + 1D2C6F682C246998004BB54E /* AddRecipeViewController.swift in Sources */, + 1DF829B72C2A7CDC00C337FC /* UIImageViewImageLoading.swift in Sources */, + 1D60CC3D2C3E4F1600D08FA3 /* APIConfig.swift in Sources */, + 1D1283A42C15EA8100C5A870 /* RecipeType.swift in Sources */, + 1DF829B42C2A7A7D00C337FC /* Fonts.swift in Sources */, + 1D4741D22C1B4F8D009381CE /* RecipeDTO.swift in Sources */, + 1DE19EC02C1B422F0031804A /* RecipeDetailView.swift in Sources */, + 1D1283AA2C15EBCF00C5A870 /* SearchFeedUseCase.swift in Sources */, + 1DE19EA82C1B420A0031804A /* SearchFeedListRepository.swift in Sources */, + 1D4741D52C1B4F8D009381CE /* UserDTO.swift in Sources */, + 1DE19EC22C1B422F0031804A /* RecipeListItemViewModel.swift in Sources */, + 1DE19EC32C1B422F0031804A /* SearchBar.swift in Sources */, + 1D7368722C32CEC5000EF904 /* SaveRecipeUseCase.swift in Sources */, + 1D439EA22C2C6997008530A5 /* RecipeDetailInteractor.swift in Sources */, + 1D73686E2C305757000EF904 /* RecipeDetailDTO.swift in Sources */, + 1D4741D72C1B4FF4009381CE /* RecipeListInteractor.swift in Sources */, + 1DC7CC342C294F9200796889 /* SelectImageCell.swift in Sources */, + 1DE19E9D2C1B3DC10031804A /* SceneDelegate.swift in Sources */, + 1D4741D12C1B4F8D009381CE /* RecipeImageDTO.swift in Sources */, + 1D7368742C32CF09000EF904 /* AddRecipeRepository.swift in Sources */, + 1DE19EA72C1B420A0031804A /* FeedListRepository.swift in Sources */, + 1DE19EC62C1B422F0031804A /* RecipeListCell.swift in Sources */, + 1D166D0D2C4AD54E00A50963 /* AddRecipeViewModel.swift in Sources */, + 1DF829B92C2A818D00C337FC /* String+Validation.swift in Sources */, + 1D7368862C33D7BE000EF904 /* RecipeUploadResponseDTO.swift in Sources */, + 1DE19EC42C1B422F0031804A /* RecipeListViewController.swift in Sources */, + 1DE19EBF2C1B422F0031804A /* RecipeDetailViewModel.swift in Sources */, + 1D1283A22C15E94300C5A870 /* Recipe.swift in Sources */, + 1D1283CA2C16D9C600C5A870 /* RecipeFetchService.swift in Sources */, + 1D6958D42C3D059E008604B3 /* RecipeListRouter.swift in Sources */, + 1DDE90D12C3592E90078DFD3 /* ErrorResponseDTO.swift in Sources */, + 1D4741D42C1B4F8D009381CE /* NetworkResponseDTO.swift in Sources */, + 1D439E9C2C2C58DD008530A5 /* FetchRecipeDetailUseCase.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1D2C16F42BE532B800C04508 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D6958DF2C3D5E35008604B3 /* NetworkService.swift in Sources */, + 1D6958DC2C3D5E20008604B3 /* RecipeDetailRepository.swift in Sources */, + 1D6958E12C3D5E44008604B3 /* RecipeDetailDTO.swift in Sources */, + 1D6958D82C3D5A80008604B3 /* RecipeDeatilInteractorTests.swift in Sources */, + 1D60CC402C3EB76600D08FA3 /* APIConfig.swift in Sources */, + 1D6958DE2C3D5E2C008604B3 /* RecipeType.swift in Sources */, + 1DBC63672C47D23000DA00C2 /* AddRecipeError.swift in Sources */, + 1D6958D92C3D5AF7008604B3 /* RecipeDetailInteractor.swift in Sources */, + 1D166D0E2C4AD54E00A50963 /* AddRecipeViewModel.swift in Sources */, + 1D6958E52C3D5F0E008604B3 /* DateFormatter+Extensions.swift in Sources */, + 1D39729C2C45905700495014 /* MultipartFormDataRequest.swift in Sources */, + 1D2C16FD2BE532B800C04508 /* HomeCafeRecipesTests.swift in Sources */, + 1D6958DD2C3D5E28008604B3 /* User.swift in Sources */, + 1D6958E42C3D5EA6008604B3 /* NetworkResponseDTO.swift in Sources */, + 1D166D7F2C4BBD7700A50963 /* CGSize+addButton.swift in Sources */, + 1D6958DB2C3D5C91008604B3 /* Recipe.swift in Sources */, + 1D6958E02C3D5E3D008604B3 /* RecipeDetailError.swift in Sources */, + 1DDE90CF2C3590C40078DFD3 /* AddRecipeTests.swift in Sources */, + 1D6958DA2C3D5BA4008604B3 /* FetchRecipeDetailUseCase.swift in Sources */, + 1D6958E32C3D5E9D008604B3 /* UserDTO.swift in Sources */, + 1D39729E2C46C57A00495014 /* FetchRecipeDetailUseCaseTests.swift in Sources */, + 1D6958E22C3D5E99008604B3 /* RecipeImageDTO.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1D2C16FE2BE532B800C04508 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D2C17092BE532B800C04508 /* HomeCafeRecipesUITestsLaunchTests.swift in Sources */, + 1D2C17072BE532B800C04508 /* HomeCafeRecipesUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 1D2C16FA2BE532B800C04508 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 1D2C16E12BE532B700C04508 /* HomeCafeRecipes */; - targetProxy = 1D2C16F92BE532B800C04508 /* PBXContainerItemProxy */; - }; - 1D2C17042BE532B800C04508 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 1D2C16E12BE532B700C04508 /* HomeCafeRecipes */; - targetProxy = 1D2C17032BE532B800C04508 /* PBXContainerItemProxy */; - }; + 1D2C16FA2BE532B800C04508 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 1D2C16E12BE532B700C04508 /* HomeCafeRecipes */; + targetProxy = 1D2C16F92BE532B800C04508 /* PBXContainerItemProxy */; + }; + 1D2C17042BE532B800C04508 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 1D2C16E12BE532B700C04508 /* HomeCafeRecipes */; + targetProxy = 1D2C17032BE532B800C04508 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ - 1D2C16F02BE532B800C04508 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 1D2C16F12BE532B800C04508 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; + 1D2C16F02BE532B800C04508 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 1D2C16F12BE532B800C04508 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ - 1D2C170A2BE532B800C04508 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - 1D2C170B2BE532B800C04508 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 1D2C170D2BE532B800C04508 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 6763324T69; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = HomeCafeRecipes/Info.plist; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = GeonH0.HomeCafeRecipes; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 1D2C170E2BE532B800C04508 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 6763324T69; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = HomeCafeRecipes/Info.plist; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = GeonH0.HomeCafeRecipes; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; - 1D2C17102BE532B800C04508 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 6763324T69; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = GeonH0.HomeCafeRecipesTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HomeCafeRecipes.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/HomeCafeRecipes"; - }; - name = Debug; - }; - 1D2C17112BE532B800C04508 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 6763324T69; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = GeonH0.HomeCafeRecipesTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HomeCafeRecipes.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/HomeCafeRecipes"; - }; - name = Release; - }; - 1D2C17132BE532B800C04508 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 6763324T69; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = GeonH0.HomeCafeRecipesUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = HomeCafeRecipes; - }; - name = Debug; - }; - 1D2C17142BE532B800C04508 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 6763324T69; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = GeonH0.HomeCafeRecipesUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = HomeCafeRecipes; - }; - name = Release; - }; + 1D2C170A2BE532B800C04508 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 1D2C170B2BE532B800C04508 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 1D2C170D2BE532B800C04508 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6763324T69; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = HomeCafeRecipes/Info.plist; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = GeonH0.HomeCafeRecipes; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 1D2C170E2BE532B800C04508 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6763324T69; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = HomeCafeRecipes/Info.plist; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = GeonH0.HomeCafeRecipes; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 1D2C17102BE532B800C04508 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6763324T69; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = GeonH0.HomeCafeRecipesTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HomeCafeRecipes.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/HomeCafeRecipes"; + }; + name = Debug; + }; + 1D2C17112BE532B800C04508 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6763324T69; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = GeonH0.HomeCafeRecipesTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HomeCafeRecipes.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/HomeCafeRecipes"; + }; + name = Release; + }; + 1D2C17132BE532B800C04508 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6763324T69; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = GeonH0.HomeCafeRecipesUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = HomeCafeRecipes; + }; + name = Debug; + }; + 1D2C17142BE532B800C04508 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6763324T69; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = GeonH0.HomeCafeRecipesUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = HomeCafeRecipes; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 1D2C16DD2BE532B700C04508 /* Build configuration list for PBXProject "HomeCafeRecipes" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 1D2C170A2BE532B800C04508 /* Debug */, - 1D2C170B2BE532B800C04508 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 1D2C170C2BE532B800C04508 /* Build configuration list for PBXNativeTarget "HomeCafeRecipes" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 1D2C170D2BE532B800C04508 /* Debug */, - 1D2C170E2BE532B800C04508 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 1D2C170F2BE532B800C04508 /* Build configuration list for PBXNativeTarget "HomeCafeRecipesTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 1D2C17102BE532B800C04508 /* Debug */, - 1D2C17112BE532B800C04508 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 1D2C17122BE532B800C04508 /* Build configuration list for PBXNativeTarget "HomeCafeRecipesUITests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 1D2C17132BE532B800C04508 /* Debug */, - 1D2C17142BE532B800C04508 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; + 1D2C16DD2BE532B700C04508 /* Build configuration list for PBXProject "HomeCafeRecipes" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1D2C170A2BE532B800C04508 /* Debug */, + 1D2C170B2BE532B800C04508 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1D2C170C2BE532B800C04508 /* Build configuration list for PBXNativeTarget "HomeCafeRecipes" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1D2C170D2BE532B800C04508 /* Debug */, + 1D2C170E2BE532B800C04508 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1D2C170F2BE532B800C04508 /* Build configuration list for PBXNativeTarget "HomeCafeRecipesTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1D2C17102BE532B800C04508 /* Debug */, + 1D2C17112BE532B800C04508 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1D2C17122BE532B800C04508 /* Build configuration list for PBXNativeTarget "HomeCafeRecipesUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1D2C17132BE532B800C04508 /* Debug */, + 1D2C17142BE532B800C04508 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 1D6C5ACD2C1A8C580052A36C /* XCRemoteSwiftPackageReference "Kingfisher" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/onevcat/Kingfisher.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 7.12.0; - }; - }; - 1D740B3F2C15E1EC0001B704 /* XCRemoteSwiftPackageReference "RxSwift" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/ReactiveX/RxSwift"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 6.7.1; - }; - }; + 1D6C5ACD2C1A8C580052A36C /* XCRemoteSwiftPackageReference "Kingfisher" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/onevcat/Kingfisher.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 7.12.0; + }; + }; + 1D740B3F2C15E1EC0001B704 /* XCRemoteSwiftPackageReference "RxSwift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/ReactiveX/RxSwift"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 6.7.1; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 1D1283AE2C1697DB00C5A870 /* RxCocoa */ = { - isa = XCSwiftPackageProductDependency; - package = 1D740B3F2C15E1EC0001B704 /* XCRemoteSwiftPackageReference "RxSwift" */; - productName = RxCocoa; - }; - 1D1283B02C1697DB00C5A870 /* RxSwift */ = { - isa = XCSwiftPackageProductDependency; - package = 1D740B3F2C15E1EC0001B704 /* XCRemoteSwiftPackageReference "RxSwift" */; - productName = RxSwift; - }; - 1D1283B32C16983900C5A870 /* RxSwift */ = { - isa = XCSwiftPackageProductDependency; - package = 1D740B3F2C15E1EC0001B704 /* XCRemoteSwiftPackageReference "RxSwift" */; - productName = RxSwift; - }; - 1D1283B52C16984E00C5A870 /* RxCocoa */ = { - isa = XCSwiftPackageProductDependency; - package = 1D740B3F2C15E1EC0001B704 /* XCRemoteSwiftPackageReference "RxSwift" */; - productName = RxCocoa; - }; - 1DE19EC72C1B4C2D0031804A /* Kingfisher */ = { - isa = XCSwiftPackageProductDependency; - package = 1D6C5ACD2C1A8C580052A36C /* XCRemoteSwiftPackageReference "Kingfisher" */; - productName = Kingfisher; - }; + 1D1283AE2C1697DB00C5A870 /* RxCocoa */ = { + isa = XCSwiftPackageProductDependency; + package = 1D740B3F2C15E1EC0001B704 /* XCRemoteSwiftPackageReference "RxSwift" */; + productName = RxCocoa; + }; + 1D1283B02C1697DB00C5A870 /* RxSwift */ = { + isa = XCSwiftPackageProductDependency; + package = 1D740B3F2C15E1EC0001B704 /* XCRemoteSwiftPackageReference "RxSwift" */; + productName = RxSwift; + }; + 1D1283B32C16983900C5A870 /* RxSwift */ = { + isa = XCSwiftPackageProductDependency; + package = 1D740B3F2C15E1EC0001B704 /* XCRemoteSwiftPackageReference "RxSwift" */; + productName = RxSwift; + }; + 1D1283B52C16984E00C5A870 /* RxCocoa */ = { + isa = XCSwiftPackageProductDependency; + package = 1D740B3F2C15E1EC0001B704 /* XCRemoteSwiftPackageReference "RxSwift" */; + productName = RxCocoa; + }; + 1DE19EC72C1B4C2D0031804A /* Kingfisher */ = { + isa = XCSwiftPackageProductDependency; + package = 1D6C5ACD2C1A8C580052A36C /* XCRemoteSwiftPackageReference "Kingfisher" */; + productName = Kingfisher; + }; /* End XCSwiftPackageProductDependency section */ - }; - rootObject = 1D2C16DA2BE532B700C04508 /* Project object */; + }; + rootObject = 1D2C16DA2BE532B700C04508 /* Project object */; } diff --git a/HomeCafeRecipes/HomeCafeRecipes/Domain/Entities/Recipe.swift b/HomeCafeRecipes/HomeCafeRecipes/Domain/Entities/Recipe.swift index 06e9520..5da605c 100644 --- a/HomeCafeRecipes/HomeCafeRecipes/Domain/Entities/Recipe.swift +++ b/HomeCafeRecipes/HomeCafeRecipes/Domain/Entities/Recipe.swift @@ -18,3 +18,25 @@ struct Recipe { let likeCount: Int let createdAt: Date } + +extension Recipe { + + static func dummyRecipe() -> Recipe { + .init( + id: 1, + type: .coffee, + name: "", + description: "", + writer: .init( + id: 1, + profileImage: "", + nickname: "", + createdAt: Date() + ), + imageUrls: [], + isLikedByCurrentUser: false, + likeCount: 0, + createdAt: Date() + ) + } +} diff --git a/HomeCafeRecipes/HomeCafeRecipes/Domain/Interactor/RecipeDetailInteractor.swift b/HomeCafeRecipes/HomeCafeRecipes/Domain/Interactor/RecipeDetailInteractor.swift index 187863f..ee47406 100644 --- a/HomeCafeRecipes/HomeCafeRecipes/Domain/Interactor/RecipeDetailInteractor.swift +++ b/HomeCafeRecipes/HomeCafeRecipes/Domain/Interactor/RecipeDetailInteractor.swift @@ -6,32 +6,24 @@ // import Foundation + import RxSwift protocol RecipeDetailInteractorDelegate: AnyObject { func fetchedRecipe(result: Result) } -protocol InputRecipeDetailInteractor { - func viewDidLoad() -} - -protocol OutputRecipeDetailInteractor { - var recipe: Observable> { get } +protocol RecipeDetailInteractor { + func viewDidLoad() } -class RecipeDetailInteractor: InputRecipeDetailInteractor, OutputRecipeDetailInteractor { - +class RecipeDetailInteractorImpl: RecipeDetailInteractor { + private let fetchRecipeDetailUseCase: FetchRecipeDetailUseCase private let recipeID: Int private let disposeBag = DisposeBag() - private let recipeDetailSubject = PublishSubject>() weak var delegate: RecipeDetailInteractorDelegate? - - var recipe: Observable> { - return recipeDetailSubject.asObservable() - } - + init( fetchRecipeDetailUseCase: FetchRecipeDetailUseCase, recipeID: Int @@ -39,38 +31,18 @@ class RecipeDetailInteractor: InputRecipeDetailInteractor, OutputRecipeDetailInt self.fetchRecipeDetailUseCase = fetchRecipeDetailUseCase self.recipeID = recipeID } - - func setDelegate(_ delegate: RecipeDetailInteractorDelegate) { - self.delegate = delegate - bindOutputs() - } - - private func bindOutputs() { - recipe - .subscribe(onNext: { [weak self] result in - self?.delegate?.fetchedRecipe(result: result) - }) - .disposed(by: disposeBag) - } - + func viewDidLoad() { fetchRecipeDetail() } - + private func fetchRecipeDetail() { fetchRecipeDetailUseCase.execute(recipeID: recipeID) - .subscribe { [weak self] result in - self?.handleResult(result) - } + .subscribe(onSuccess: { [weak self] result in + self?.delegate?.fetchedRecipe(result: result) + }, onFailure: { [weak self] error in + self?.delegate?.fetchedRecipe(result: .failure(error)) + }) .disposed(by: disposeBag) } - - private func handleResult(_ result: Result) { - switch result { - case .success(let recipe): - self.recipeDetailSubject.onNext(.success(recipe)) - case .failure(let error): - self.recipeDetailSubject.onNext(.failure(error)) - } - } } diff --git a/HomeCafeRecipes/HomeCafeRecipes/Domain/Interactor/RecipeListInteractor.swift b/HomeCafeRecipes/HomeCafeRecipes/Domain/Interactor/RecipeListInteractor.swift index b02de71..8ee8f01 100755 --- a/HomeCafeRecipes/HomeCafeRecipes/Domain/Interactor/RecipeListInteractor.swift +++ b/HomeCafeRecipes/HomeCafeRecipes/Domain/Interactor/RecipeListInteractor.swift @@ -6,6 +6,7 @@ // import Foundation + import RxSwift protocol RecipeListInteractorDelegate: AnyObject { @@ -13,7 +14,7 @@ protocol RecipeListInteractorDelegate: AnyObject { func showRecipeDetail(ID: Int) } -protocol InputRecipeListInteractor { +protocol RecipeListInteractor { func viewDidLoad() func fetchNextPage() func didSelectItem(ID: Int) @@ -21,16 +22,12 @@ protocol InputRecipeListInteractor { func resetSearch() } -protocol OutputRecipeListInteractor { - var recipes: Observable> { get } -} - -class RecipeListInteractor: InputRecipeListInteractor, OutputRecipeListInteractor { +class RecipeListInteractorImpl: RecipeListInteractor { private let disposeBag = DisposeBag() private let fetchFeedListUseCase: FetchFeedListUseCase private let searchFeedListUseCase: SearchFeedListUseCase - private weak var delegate: RecipeListInteractorDelegate? + weak var delegate: RecipeListInteractorDelegate? private var currentPage: Int = 1 private var isFetching = false @@ -40,34 +37,18 @@ class RecipeListInteractor: InputRecipeListInteractor, OutputRecipeListInteracto private let recipesSubject = BehaviorSubject>(value: .success([])) - var recipes: Observable> { - return recipesSubject.asObservable() - } - init(fetchFeedListUseCase: FetchFeedListUseCase, searchFeedListUseCase: SearchFeedListUseCase) { self.fetchFeedListUseCase = fetchFeedListUseCase self.searchFeedListUseCase = searchFeedListUseCase } - - func setDelegate(_ delegate: RecipeListInteractorDelegate) { - self.delegate = delegate - bindOutputs() - } - - private func bindOutputs() { - recipes - .subscribe(onNext: { [weak self] result in - self?.delegate?.fetchedRecipes(result: result) - }) - .disposed(by: disposeBag) - } - + func viewDidLoad() { fetchRecipes() } func fetchNextPage() { - fetchNextRecipes(nextPage: currentPage) + guard !isFetching else { return } + fetchNextRecipes() } func didSelectItem(ID: Int) { @@ -78,6 +59,7 @@ class RecipeListInteractor: InputRecipeListInteractor, OutputRecipeListInteracto isSearching = false currentSearchQuery = nil currentPage = 1 + allRecipes.removeAll() recipesSubject.onNext(.success([])) fetchRecipes() } @@ -89,9 +71,11 @@ class RecipeListInteractor: InputRecipeListInteractor, OutputRecipeListInteracto isSearching = true currentPage = 1 searchFeedListUseCase.execute(title: title, pageNumber: currentPage) - .subscribe { [weak self] result in + .subscribe(onSuccess: { [weak self] result in self?.handleResult(result) - } + }, onFailure: { [weak self] error in + self?.handleResult(.failure(error)) + }) .disposed(by: disposeBag) } @@ -99,19 +83,23 @@ class RecipeListInteractor: InputRecipeListInteractor, OutputRecipeListInteracto guard !isFetching else { return } isFetching = true fetchFeedListUseCase.execute(pageNumber: currentPage) - .subscribe { [weak self] result in + .subscribe(onSuccess: { [weak self] result in self?.handleResult(result) - } + }, onFailure: { [weak self] error in + self?.handleResult(.failure(error)) + }) .disposed(by: disposeBag) } - private func fetchNextRecipes(nextPage: Int) { + private func fetchNextRecipes() { guard !isFetching else { return } isFetching = true - fetchFeedListUseCase.execute(pageNumber: nextPage) - .subscribe { [weak self] result in + fetchFeedListUseCase.execute(pageNumber: currentPage) + .subscribe(onSuccess: { [weak self] result in self?.handleResult(result) - } + }, onFailure: { [weak self] error in + self?.handleResult(.failure(error)) + }) .disposed(by: disposeBag) } @@ -127,11 +115,10 @@ class RecipeListInteractor: InputRecipeListInteractor, OutputRecipeListInteracto } else { allRecipes.append(contentsOf: recipes) } - self.recipesSubject.onNext(.success(allRecipes)) - self.currentPage += 1 + delegate?.fetchedRecipes(result: .success(allRecipes)) + currentPage += 1 case .failure(let error): - recipesSubject.onNext(.failure(error)) + delegate?.fetchedRecipes(result: .failure(error)) } } - } diff --git a/HomeCafeRecipes/HomeCafeRecipes/Presentation/Feed/View/RecipeDetailViewController.swift b/HomeCafeRecipes/HomeCafeRecipes/Presentation/Feed/View/RecipeDetailViewController.swift index c6ec16d..1ae1610 100644 --- a/HomeCafeRecipes/HomeCafeRecipes/Presentation/Feed/View/RecipeDetailViewController.swift +++ b/HomeCafeRecipes/HomeCafeRecipes/Presentation/Feed/View/RecipeDetailViewController.swift @@ -20,8 +20,7 @@ final class RecipeDetailViewController: UIViewController { init(interactor: RecipeDetailInteractor) { self.interactor = interactor - super.init(nibName: nil, bundle: nil) - self.interactor.setDelegate(self) + super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { @@ -37,7 +36,7 @@ final class RecipeDetailViewController: UIViewController { interactor.viewDidLoad() contentView.customNavigationBar.backButton.addTarget(self, action: #selector(backButtonTapped), for: .touchUpInside) } - + private func displayError(_ error: Error) { let alert = UIAlertController(title: "해당 레시피를 로드하는데 실패했습니다.", message: error.localizedDescription, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default)) @@ -60,7 +59,17 @@ extension RecipeDetailViewController: RecipeDetailInteractorDelegate { self.contentView.configure(with: recipeItemViewModel) } case .failure(let error): - self.displayError(error) + DispatchQueue.main.async { + self.displayError(error) + } } } } + + +extension RecipeDetailViewController: Drawable { + var viewController: UIViewController? { + return self + } +} + diff --git a/HomeCafeRecipes/HomeCafeRecipes/Presentation/FeedList/View/RecipeListView.swift b/HomeCafeRecipes/HomeCafeRecipes/Presentation/FeedList/View/RecipeListView.swift index 28ab024..e3bac63 100644 --- a/HomeCafeRecipes/HomeCafeRecipes/Presentation/FeedList/View/RecipeListView.swift +++ b/HomeCafeRecipes/HomeCafeRecipes/Presentation/FeedList/View/RecipeListView.swift @@ -23,7 +23,6 @@ final class RecipeListView: UIView { private let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) private var recipes: [RecipeListItemViewModel] = [] - weak var coordinator: RecipeDetailCoordinatorProtocol? weak var delegate: RecipeListViewDelegate? override init(frame: CGRect) { diff --git a/HomeCafeRecipes/HomeCafeRecipes/Presentation/FeedList/View/RecipeListViewController.swift b/HomeCafeRecipes/HomeCafeRecipes/Presentation/FeedList/View/RecipeListViewController.swift index 710f217..367a188 100644 --- a/HomeCafeRecipes/HomeCafeRecipes/Presentation/FeedList/View/RecipeListViewController.swift +++ b/HomeCafeRecipes/HomeCafeRecipes/Presentation/FeedList/View/RecipeListViewController.swift @@ -13,12 +13,14 @@ final class RecipeListViewController: UIViewController { private var recipes: [RecipeListItemViewModel] = [] private let searchBar = SearchBar() private let recipeListView = RecipeListView() - - init(interactor: RecipeListInteractor) { - self.interactor = interactor - super.init(nibName: nil, bundle: nil) + private let recipeListMapper = RecipeListMapper() + private let router: RecipeListRouter + + init(interactor: RecipeListInteractor, router: RecipeListRouter) { self.interactor.setDelegate(self) - + self.interactor = interactor + self.router = router + super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { @@ -27,7 +29,6 @@ final class RecipeListViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - recipeListView.delegate = self setupUI() interactor.viewDidLoad() } @@ -36,16 +37,16 @@ final class RecipeListViewController: UIViewController { view.backgroundColor = .white view.addSubview(searchBar) view.addSubview(recipeListView) - + searchBar.translatesAutoresizingMaskIntoConstraints = false recipeListView.translatesAutoresizingMaskIntoConstraints = false - + NSLayoutConstraint.activate([ searchBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), searchBar.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10), searchBar.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10), searchBar.heightAnchor.constraint(equalToConstant: 50), - + recipeListView.topAnchor.constraint(equalTo: searchBar.bottomAnchor), recipeListView.leadingAnchor.constraint(equalTo: view.leadingAnchor), recipeListView.trailingAnchor.constraint(equalTo: view.trailingAnchor), @@ -54,10 +55,10 @@ final class RecipeListViewController: UIViewController { searchBar.setDelegate(self) } - } // MARK: - UISearchBarDelegate + extension RecipeListViewController: UISearchBarDelegate { func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { if searchText.isBlank { @@ -75,6 +76,7 @@ extension RecipeListViewController: UISearchBarDelegate { } // MARK: - RecipeListInteractorDelegate + extension RecipeListViewController: RecipeListInteractorDelegate { func fetchedRecipes(result: Result<[Recipe], Error>) { switch result { @@ -90,7 +92,7 @@ extension RecipeListViewController: RecipeListInteractorDelegate { } func showRecipeDetail(ID: Int) { - coordinator.showRecipeDetail(from: self, recipeID: ID) + router.navigateToRecipeDetail(from: self, recipeID: ID) } } @@ -100,7 +102,7 @@ extension RecipeListViewController: RecipeListViewDelegate { interactor.didSelectItem(ID: ID) } - func ScrollToBottom() { + func scrollToBottom() { interactor.fetchNextPage() } } diff --git a/HomeCafeRecipes/HomeCafeRecipes/Presentation/Tabbar/MainTabBarController.swift b/HomeCafeRecipes/HomeCafeRecipes/Presentation/Tabbar/MainTabBarController.swift index e592ca4..a57e50c 100644 --- a/HomeCafeRecipes/HomeCafeRecipes/Presentation/Tabbar/MainTabBarController.swift +++ b/HomeCafeRecipes/HomeCafeRecipes/Presentation/Tabbar/MainTabBarController.swift @@ -11,6 +11,16 @@ class MainTabBarController: UITabBarController, UITabBarControllerDelegate { private let addButton = UIButton(type: .custom) private let buttonSize = CGSize(all: 64.0) + private let router = Router() + + init(router: Router) { + self.router = router + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } override func viewDidLoad() { super.viewDidLoad() @@ -33,23 +43,26 @@ class MainTabBarController: UITabBarController, UITabBarControllerDelegate { } private func setupTabBar() { - let baseneworkServie = BaseNetworkService() - let networkService = RecipeFetchServiceImpl(networkService: baseneworkServie) - let repository = FeedListRepositoryImpl(networkService: networkService) - let searchrepository = SearchFeedRepositoryImpl(networkService: networkService) - let fetchFeedListUseCase = FetchFeedListUseCaseImpl(repository: repository) - let searchFeedListUsecase = SearchFeedListUseCaseImpl(repository: searchrepository) - - let recipeListViewModel = RecipeListInteractor(fetchFeedListUseCase: fetchFeedListUseCase, searchFeedListUseCase: searchFeedListUsecase) - - let recipeListVC = RecipeListViewController(interactor: recipeListViewModel) - recipeListVC.tabBarItem = UITabBarItem(title: "Recipes", image: UIImage(systemName: "list.bullet"), tag: 0) + let recipeListVC = router.createRecipeListDependencies() + let favoritesVC = createFavoritesViewController() + recipeListVC.tabBarItem = UITabBarItem( + title: "Recipes", + image: UIImage(systemName: "list.bullet"), + tag: 0 + ) + favoritesVC.tabBarItem = UITabBarItem( + title: "Favorites", + image: UIImage(systemName: "bookmark"), + tag: 1 + ) + viewControllers = [recipeListVC, favoritesVC] + } + + private func createFavoritesViewController() -> UIViewController { let favoritesVC = UIViewController() favoritesVC.view.backgroundColor = .white - favoritesVC.tabBarItem = UITabBarItem(title: "Favorites", image: UIImage(systemName: "bookmark"), tag: 1) - - viewControllers = [recipeListVC, favoritesVC] + return favoritesVC } private func setupActionButton() { @@ -68,12 +81,12 @@ class MainTabBarController: UITabBarController, UITabBarControllerDelegate { let alert = UIAlertController(title: "게시물 작성", message: "어떤 게시물을 작성하실 건가요?", preferredStyle: .actionSheet) alert.addAction(UIAlertAction(title: "Coffee", style: .default, handler: { [weak self] _ in guard let self else { return } - let addRecipeVC = AddRecipeViewController(recipeType: .coffee) + let addRecipeVC = router.makeAddRecipeViewController(recipeType: .coffee) self.navigationController?.pushViewController(addRecipeVC, animated: true) })) alert.addAction(UIAlertAction(title: "Dessert", style: .default, handler: { [weak self] _ in guard let self else { return } - let addRecipeVC = AddRecipeViewController(recipeType: .dessert) + let addRecipeVC = router.makeAddRecipeViewController(recipeType: .dessert) self.navigationController?.pushViewController(addRecipeVC, animated: true) })) alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) diff --git a/HomeCafeRecipes/HomeCafeRecipes/Presentation/UploadRecipe/AddRecipeView.swift b/HomeCafeRecipes/HomeCafeRecipes/Presentation/UploadRecipe/AddRecipeView.swift new file mode 100644 index 0000000..803151c --- /dev/null +++ b/HomeCafeRecipes/HomeCafeRecipes/Presentation/UploadRecipe/AddRecipeView.swift @@ -0,0 +1,240 @@ +// +// AddRecipeView.swift +// HomeCafeRecipes +// +// Created by 김건호 on 6/22/24. +// + +import UIKit + +protocol AddRecipeViewDelegate: AnyObject { + func selectImageButtonTapped() + func didTapDeleteButton(at index: Int) + func didTapSubmitButton() + func numberOfImages() -> Int + func recipeImage(at index: Int) -> UIImage? +} + +final class AddRecipeView: UIView { + + private let imageCounterLabel: UILabel = { + let label = UILabel() + label.font = Fonts.bodyFont + label.textColor = .gray + label.text = "0/5" + return label + }() + + private let titleLabel: UILabel = { + let label = UILabel() + label.text = "제목" + return label + }() + + private let descriptionLabel: UILabel = { + let label = UILabel() + label.text = "내용" + return label + }() + + private let collectionView: UICollectionView + + private let titleTextField: UITextField = { + let textField = UITextField() + textField.placeholder = "제목을 입력하세요" + textField.borderStyle = .roundedRect + return textField + }() + + private let descriptionTextView: UITextView = { + let textView = UITextView() + textView.layer.borderColor = UIColor.lightGray.cgColor + textView.layer.borderWidth = 1 + textView.layer.cornerRadius = 5 + textView.font = Fonts.titleFont + return textView + }() + + private lazy var submitButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("레시피 등록", for: .normal) + button.setTitleColor(.white, for: .normal) + button.backgroundColor = .blue + button.layer.cornerRadius = 5 + button.addAction( + UIAction( + handler: { [weak self] _ in + self?.delegate?.didTapSubmitButton() + }), + for: .touchUpInside + ) + return button + }() + + let customNavigationBar = CustomNavigationBar() + + weak var delegate: AddRecipeViewDelegate? + + override init(frame: CGRect) { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + layout.minimumLineSpacing = 10 + layout.itemSize = CGSize(width: 100, height: 100) + collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.backgroundColor = .clear + + super.init(frame: frame) + setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupUI() { + backgroundColor = .white + setupCollectionView() + setupCustomNavigationBar() + addSubviews() + setupConstraints() + } + + private func setupCollectionView() { + collectionView.dataSource = self + collectionView.delegate = self + collectionView.register(RecipeUploadImgaeCell.self, forCellWithReuseIdentifier: "ImageCell") + collectionView.register(SelectImageCell.self, forCellWithReuseIdentifier: "SelectImageCell") + } + + private func setupCustomNavigationBar() { + customNavigationBar.translatesAutoresizingMaskIntoConstraints = false + } + + private func addSubviews() { + addSubview(customNavigationBar) + addSubview(titleLabel) + addSubview(descriptionLabel) + addSubview(collectionView) + addSubview(imageCounterLabel) + addSubview(titleTextField) + addSubview(descriptionTextView) + addSubview(submitButton) + } + + private func setupConstraints() { + customNavigationBar.translatesAutoresizingMaskIntoConstraints = false + titleLabel.translatesAutoresizingMaskIntoConstraints = false + descriptionLabel.translatesAutoresizingMaskIntoConstraints = false + collectionView.translatesAutoresizingMaskIntoConstraints = false + imageCounterLabel.translatesAutoresizingMaskIntoConstraints = false + titleTextField.translatesAutoresizingMaskIntoConstraints = false + descriptionTextView.translatesAutoresizingMaskIntoConstraints = false + submitButton.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + customNavigationBar.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), + customNavigationBar.leadingAnchor.constraint(equalTo: leadingAnchor), + customNavigationBar.trailingAnchor.constraint(equalTo: trailingAnchor), + customNavigationBar.heightAnchor.constraint(equalToConstant: 44), + + collectionView.topAnchor.constraint(equalTo: customNavigationBar.bottomAnchor, constant: 20), + collectionView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), + collectionView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20), + collectionView.heightAnchor.constraint(equalToConstant: 120), + + imageCounterLabel.topAnchor.constraint(equalTo: collectionView.bottomAnchor, constant: 10), + imageCounterLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), + + titleLabel.topAnchor.constraint(equalTo: imageCounterLabel.bottomAnchor, constant: 40), + titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), + titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20), + + titleTextField.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 15), + titleTextField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), + titleTextField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20), + titleTextField.heightAnchor.constraint(equalToConstant: 40), + + descriptionLabel.topAnchor.constraint(equalTo: titleTextField.bottomAnchor, constant: 20), + descriptionLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), + descriptionLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20), + + descriptionTextView.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: 15), + descriptionTextView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), + descriptionTextView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20), + descriptionTextView.heightAnchor.constraint(equalToConstant: 200), + + submitButton.topAnchor.constraint(equalTo: descriptionTextView.bottomAnchor, constant: 20), + submitButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), + submitButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20), + submitButton.heightAnchor.constraint(equalToConstant: 50) + ]) + } + + func updateImageView(count: Int) { + imageCounterLabel.text = "\(count)/5" + collectionView.reloadData() + } + + var titleText: String { + return titleTextField.text ?? "" + } + + var descriptionText: String { + return descriptionTextView.text + } + + func indexPathForCell(_ cell: UICollectionViewCell) -> IndexPath? { + return collectionView.indexPath(for: cell) + } +} + +// MARK: UICollectionViewDataSource + +extension AddRecipeView: UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, + numberOfItemsInSection section: Int) -> Int { + return (delegate?.numberOfImages() ?? 0) + 1 + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + if indexPath.item == 0 { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SelectImageCell", for: indexPath) as! SelectImageCell + cell.selectImageButton.addAction( + UIAction( + handler: { [weak self] _ in + self?.delegate?.selectImageButtonTapped() + } + ), + for: .touchUpInside + ) + return cell + } else { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath) as! RecipeUploadImgaeCell + if let image = delegate?.recipeImage(at: indexPath.item - 1) { + cell.configure(with: image, isRepresentative: indexPath.item == 1) + } + cell.delegate = self + return cell + } + } +} + +// MARK: UICollectionViewDelegate + +extension AddRecipeView: UICollectionViewDelegate { + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + if indexPath.item == 0 { + delegate?.selectImageButtonTapped() + } + } +} + +// MARK: ImageCollectionViewCellDelegate + +extension AddRecipeView: ImageCollectionViewCellDelegate { + func didTapDeleteButton(_ cell: RecipeUploadImgaeCell) { + if let indexPath = collectionView.indexPath(for: cell) { + delegate?.didTapDeleteButton(at: indexPath.item - 1) + } + } +} diff --git a/HomeCafeRecipes/HomeCafeRecipes/Presentation/UploadRecipe/AddRecipeViewController.swift b/HomeCafeRecipes/HomeCafeRecipes/Presentation/UploadRecipe/AddRecipeViewController.swift new file mode 100644 index 0000000..1422bce --- /dev/null +++ b/HomeCafeRecipes/HomeCafeRecipes/Presentation/UploadRecipe/AddRecipeViewController.swift @@ -0,0 +1,213 @@ +// +// AddRecipeViewController.swift +// HomeCafeRecipes +// +// Created by 김건호 on 6/20/24. +// + +import UIKit +import PhotosUI + +import RxSwift + +final class AddRecipeViewController: UIViewController { + + private let contentView = AddRecipeView() + private let recipeType: RecipeType + private let addRecipeInteractor: AddRecipeInteractor + private let disposeBag = DisposeBag() + private var addRecipeViewModel: AddRecipeViewModel? + + init(recipeType: RecipeType, addRecipeInteractor: AddRecipeInteractor) { + self.recipeType = recipeType + self.addRecipeInteractor = addRecipeInteractor + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + view = contentView + } + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + contentView.delegate = self + setupNavigationBar() + addRecipeInteractor.loadRecipeData() + } + + private func setupUI() { + view.backgroundColor = .white + NSLayoutConstraint.activate([ + contentView.topAnchor.constraint(equalTo: view.topAnchor), + contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + contentView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + contentView.bottomAnchor.constraint(equalTo: view.bottomAnchor) + ]) + } + + private func setupNavigationBar() { + switch recipeType { + case .coffee: + contentView.customNavigationBar.setTitle("커피 레시피 작성") + case .dessert: + contentView.customNavigationBar.setTitle("디저트 레시피 작성") + } + contentView.customNavigationBar.backButton.addTarget(self, action: #selector(backButtonTapped), for: .touchUpInside) + } + + @objc private func backButtonTapped() { + navigationController?.popViewController(animated: true) + } + + private func saveRecipeToServer() { + // MARK: 임시 userID 설정 + let userID = 6 + + addRecipeInteractor.saveRecipe(userID: userID, recipeType: recipeType) + .subscribe(onSuccess: { [weak self] result in + DispatchQueue.main.async { + switch result { + case .success: + self?.showCompletedAlert(title: "업로드 성공", message: "레시피가 성공적으로 업로드되었습니다.", success: true) + case .failure(let error): + self?.showCompletedAlert(title: error.title, message: error.localizedDescription, success: false) + } + } + }) + .disposed(by: disposeBag) + } + + private func showCompletedAlert(title: String, message: String, success: Bool) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + let confirmAction = UIAlertAction(title: "확인", style: .default) { _ in + if success { + self.navigationController?.popViewController(animated: true) + } + } + alert.addAction(confirmAction) + present(alert, animated: true, completion: nil) + } + + private func checkPhotoLibraryPermission(completion: @escaping (Bool) -> Void) { + let status = PHPhotoLibrary.authorizationStatus() + switch status { + case .authorized: + completion(true) + case .denied, .restricted: + completion(false) + case .notDetermined: + PHPhotoLibrary.requestAuthorization { status in + DispatchQueue.main.async { + completion(status == .authorized) + } + } + @unknown default: + completion(false) + } + } +} + +// MARK: AddRecipeViewDelegate + +extension AddRecipeViewController: AddRecipeViewDelegate { + func selectImageButtonTapped() { + checkPhotoLibraryPermission { granted in + if granted { + var config = PHPickerConfiguration() + config.selectionLimit = 5 + config.filter = .images + + let picker = PHPickerViewController(configuration: config) + picker.delegate = self + self.present(picker, animated: true, completion: nil) + } else { + let alert = UIAlertController(title: "권한 필요", message: "사진 라이브러리에 접근하려면 권한이 필요합니다.", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "설정", style: .default) { _ in + if let appSettings = URL(string: UIApplication.openSettingsURLString) { + UIApplication.shared.open(appSettings, options: [:], completionHandler: nil) + } + }) + alert.addAction(UIAlertAction(title: "취소", style: .cancel, handler: nil)) + self.present(alert, animated: true, completion: nil) + } + } + } + + func didTapDeleteButton(at index: Int) { + addRecipeInteractor.removeRecipeImage(at: index) + addRecipeInteractor.loadRecipeData() + contentView.updateImageView(count: numberOfImages()) + } + + func didTapSubmitButton() { + let title = contentView.titleText + let description = contentView.descriptionText + addRecipeInteractor.updateRecipeTitle(title) + addRecipeInteractor.updateRecipeDescription(description) + saveRecipeToServer() + } + + func numberOfImages() -> Int { + return addRecipeViewModel?.images.count ?? 0 + } + + func recipeImage(at index: Int) -> UIImage? { + return addRecipeViewModel?.images[index] + } +} + +// MARK: PHPickerViewControllerDelegate + +extension AddRecipeViewController: PHPickerViewControllerDelegate { + func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { + picker.dismiss(animated: true, completion: nil) + + let dispatchGroup = DispatchGroup() + var newImages: [UIImage] = [] + + for result in results { + dispatchGroup.enter() + if result.itemProvider.canLoadObject(ofClass: UIImage.self) { + result.itemProvider.loadObject(ofClass: UIImage.self) { (object, error) in + if let image = object as? UIImage { + newImages.append(image) + } + dispatchGroup.leave() + } + } else { + dispatchGroup.leave() + } + } + + dispatchGroup.notify(queue: .main) { [weak self] in + guard let self else { return } + newImages.forEach { self.addRecipeInteractor.addRecipeImage($0) } + addRecipeInteractor.loadRecipeData() + contentView.updateImageView(count: numberOfImages()) + } + } +} + +// MARK: ImageCollectionViewCellDelegate + +extension AddRecipeViewController: ImageCollectionViewCellDelegate { + func didTapDeleteButton(_ cell: RecipeUploadImgaeCell) { + guard let indexPath = contentView.indexPathForCell(cell) else { return } + addRecipeInteractor.removeRecipeImage(at: indexPath.item - 1) + contentView.updateImageView(count: self.numberOfImages()) + } +} + +// MARK: AddRecipeInteractorDelegate + +extension AddRecipeViewController: AddRecipeInteractorDelegate { + func didLoadRecipeData(viewModel: AddRecipeViewModel) { + self.addRecipeViewModel = viewModel + self.contentView.updateImageView(count: numberOfImages()) + } +} diff --git a/HomeCafeRecipes/HomeCafeRecipes/Presentation/UploadRecipe/AddRecipeViewModel.swift b/HomeCafeRecipes/HomeCafeRecipes/Presentation/UploadRecipe/AddRecipeViewModel.swift new file mode 100644 index 0000000..721ef1b --- /dev/null +++ b/HomeCafeRecipes/HomeCafeRecipes/Presentation/UploadRecipe/AddRecipeViewModel.swift @@ -0,0 +1,14 @@ +// +// AddRecipeViewModel.swift +// HomeCafeRecipes +// +// Created by 김건호 on 7/19/24. +// + +import UIKit + +struct AddRecipeViewModel { + var images: [UIImage] + var title: String + var description: String +} diff --git a/HomeCafeRecipes/HomeCafeRecipes/Presentation/UploadRecipe/RecipeUploadImgaeCell.swift b/HomeCafeRecipes/HomeCafeRecipes/Presentation/UploadRecipe/RecipeUploadImgaeCell.swift new file mode 100644 index 0000000..c89e6e2 --- /dev/null +++ b/HomeCafeRecipes/HomeCafeRecipes/Presentation/UploadRecipe/RecipeUploadImgaeCell.swift @@ -0,0 +1,100 @@ +// +// RecipeUploadImgaeCell.swift +// HomeCafeRecipes +// +// Created by 김건호 on 6/23/24. +// + +import UIKit + +protocol ImageCollectionViewCellDelegate: AnyObject { + func didTapDeleteButton(_ cell: RecipeUploadImgaeCell) +} + +final class RecipeUploadImgaeCell: UICollectionViewCell { + + private let imageView: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFill + imageView.clipsToBounds = true + imageView.layer.cornerRadius = 10 + return imageView + }() + + private let representativeLabel: UILabel = { + let label = UILabel() + label.font = Fonts.bodyFont + label.textColor = .white + label.backgroundColor = .gray + label.text = "대표 사진" + label.textAlignment = .center + label.isHidden = true + label.layer.cornerRadius = 10 + label.clipsToBounds = true + return label + }() + + private lazy var deleteButton: UIButton = { + let button = UIButton(type: .system) + button.setImage(UIImage(systemName: "xmark.circle.fill"), for: .normal) + button.tintColor = .white + button.addAction( + UIAction(handler: { [weak self] _ in + guard let self = self else { return } + self.delegate?.didTapDeleteButton(self) + }), + for: .touchUpInside + ) + return button + }() + + weak var delegate: ImageCollectionViewCellDelegate? + + override init(frame: CGRect) { + super.init(frame: frame) + setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupUI() { + addSubviews() + setupConstraints() + } + + private func addSubviews() { + contentView.addSubview(imageView) + contentView.addSubview(representativeLabel) + contentView.addSubview(deleteButton) + } + + private func setupConstraints() { + imageView.translatesAutoresizingMaskIntoConstraints = false + representativeLabel.translatesAutoresizingMaskIntoConstraints = false + deleteButton.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + imageView.topAnchor.constraint(equalTo: contentView.topAnchor), + imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + + representativeLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + representativeLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + representativeLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + representativeLabel.heightAnchor.constraint(equalToConstant: 20), + + deleteButton.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5), + deleteButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -5), + deleteButton.widthAnchor.constraint(equalToConstant: 20), + deleteButton.heightAnchor.constraint(equalToConstant: 20) + ]) + } + + func configure(with image: UIImage, isRepresentative: Bool) { + imageView.image = image + representativeLabel.isHidden = !isRepresentative + } +} diff --git a/HomeCafeRecipes/HomeCafeRecipes/Presentation/UploadRecipe/SelectImageCell.swift b/HomeCafeRecipes/HomeCafeRecipes/Presentation/UploadRecipe/SelectImageCell.swift new file mode 100644 index 0000000..09ed425 --- /dev/null +++ b/HomeCafeRecipes/HomeCafeRecipes/Presentation/UploadRecipe/SelectImageCell.swift @@ -0,0 +1,42 @@ +// +// SelectImageCell.swift +// HomeCafeRecipes +// +// Created by 김건호 on 6/24/24. +// + +import UIKit + +final class SelectImageCell: UICollectionViewCell { + + let selectImageButton = UIButton(type: .system) + + override init(frame: CGRect) { + super.init(frame: frame) + setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupUI() { + contentView.addSubview(selectImageButton) + + selectImageButton.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + selectImageButton.topAnchor.constraint(equalTo: contentView.topAnchor), + selectImageButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + selectImageButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + selectImageButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) + ]) + + selectImageButton.setImage(UIImage(systemName: "camera"), for: .normal) + selectImageButton.tintColor = .gray + selectImageButton.layer.borderColor = UIColor.gray.cgColor + selectImageButton.layer.borderWidth = 1 + selectImageButton.layer.cornerRadius = 10 + } +} + diff --git a/HomeCafeRecipes/HomeCafeRecipes/Router/RecipeListRouter.swift b/HomeCafeRecipes/HomeCafeRecipes/Router/RecipeListRouter.swift new file mode 100644 index 0000000..bf15166 --- /dev/null +++ b/HomeCafeRecipes/HomeCafeRecipes/Router/RecipeListRouter.swift @@ -0,0 +1,25 @@ +// +// RecipeListRouter.swift +// HomeCafeRecipes +// +// Created by 김건호 on 7/9/24. +// + +import UIKit + +protocol RecipeListRouter { + func navigateToRecipeDetail(from viewController: UIViewController, recipeID: Int) +} + +class RecipeListRouterImpl: RecipeListRouter { + private let router: Router + + init(router: Router) { + self.router = router + } + + func navigateToRecipeDetail(from viewController: UIViewController, recipeID: Int) { + let detailVC = router.makeRecipeDetailViewController(recipeID: recipeID) + router.push(detailVC, from: viewController, isAnimated: true, onNavigateBack: nil) + } +} diff --git a/HomeCafeRecipes/HomeCafeRecipes/Router/Router.swift b/HomeCafeRecipes/HomeCafeRecipes/Router/Router.swift new file mode 100644 index 0000000..570dbf0 --- /dev/null +++ b/HomeCafeRecipes/HomeCafeRecipes/Router/Router.swift @@ -0,0 +1,108 @@ +// +// Router.swift +// HomeCafeRecipes +// +// Created by 김건호 on 7/9/24. +// + +import UIKit + +public typealias NavigationBackClosure = () -> Void + +public protocol Drawable { + var viewController: UIViewController? { get } +} + +public protocol RouterProtocol { + func push ( + _ drawable: Drawable, + from viewController: UIViewController, + isAnimated: Bool, + onNavigateBack closure: NavigationBackClosure? + ) +} + +class Router: NSObject, RouterProtocol { + private var closures: [String: NavigationBackClosure] = [:] + + func push( + _ drawable: Drawable, + from viewController: UIViewController, + isAnimated: Bool, + onNavigateBack closure: NavigationBackClosure? + ) { + guard let targetViewController = drawable.viewController else { + return + } + + if let closure = closure { + closures.updateValue(closure, forKey: targetViewController.description) + } + viewController.navigationController?.pushViewController(targetViewController, animated: isAnimated) + } + + private func executeClosure(_ viewController: UIViewController) { + guard let closure = closures.removeValue(forKey: viewController.description) else { return } + closure() + } +} + +extension Router { + func makeRecipeListViewController() -> RecipeListViewController { + let recipeListInteractor = RecipeListInteractorImpl( + fetchFeedListUseCase: FetchFeedListUseCaseImpl( + repository: FeedListRepositoryImpl( + networkService: RecipeFetchServiceImpl( + networkService: BaseNetworkService() + ) + ) + ), + searchFeedListUseCase: SearchFeedListUseCaseImpl( + repository: SearchFeedRepositoryImpl( + networkService: RecipeFetchServiceImpl( + networkService: BaseNetworkService() + ) + ) + ) + ) + let recipeListRouter = RecipeListRouterImpl(router: self) + let recipeListVC = RecipeListViewController( + interactor: recipeListInteractor, + router: recipeListRouter + ) + recipeListInteractor.delegate = recipeListVC + return recipeListVC + } + + func makeAddRecipeViewController(recipeType: RecipeType) -> AddRecipeViewController { + let addRecipeInteractor = AddRecipeInteractorImpl( + saveRecipeUseCase: AddRecipeUseCaseImpl( + repository: AddRecipeRepositoryImpl( + recipePostService: RecipePostServiceImpl( + networkService: BaseNetworkService() + ) + ) + ) + ) + let addRecipeVC = AddRecipeViewController( + recipeType: recipeType, + addRecipeInteractor: addRecipeInteractor + ) + addRecipeInteractor.delegate = addRecipeVC + return addRecipeVC + } + + func makeRecipeDetailViewController(recipeID: Int) -> RecipeDetailViewController { + let detailInteractor = RecipeDetailInteractorImpl( + fetchRecipeDetailUseCase: FetchRecipeDetailUseCaseImpl( + repository: RecipeDetailRepositoryImpl( + networkService: BaseNetworkService() + ) + ), + recipeID: recipeID + ) + let detailVC = RecipeDetailViewController(interactor: detailInteractor) + detailInteractor.delegate = detailVC + return detailVC + } +} diff --git a/HomeCafeRecipes/HomeCafeRecipes/SceneDelegate.swift b/HomeCafeRecipes/HomeCafeRecipes/SceneDelegate.swift index aa44d9a..1e8615b 100644 --- a/HomeCafeRecipes/HomeCafeRecipes/SceneDelegate.swift +++ b/HomeCafeRecipes/HomeCafeRecipes/SceneDelegate.swift @@ -10,7 +10,7 @@ import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? - + var router: Router? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. @@ -18,8 +18,12 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). guard let windowScene = (scene as? UIWindowScene) else { return } window = UIWindow(windowScene: windowScene) - let mainViewController = ViewController() - let navigationController = UINavigationController(rootViewController : mainViewController) + router = Router() + guard let router else { return } + + let tabBarController = MainTabBarController(router: router) + let navigationController = UINavigationController(rootViewController: tabBarController) + navigationController.isNavigationBarHidden = true window?.rootViewController = navigationController window?.makeKeyAndVisible() } diff --git a/HomeCafeRecipes/HomeCafeRecipesTests/FetchRecipeDetailUseCaseTests.swift b/HomeCafeRecipes/HomeCafeRecipesTests/FetchRecipeDetailUseCaseTests.swift new file mode 100644 index 0000000..0a70e08 --- /dev/null +++ b/HomeCafeRecipes/HomeCafeRecipesTests/FetchRecipeDetailUseCaseTests.swift @@ -0,0 +1,125 @@ +// +// FetchRecipeDetailUseCaseTests.swift +// HomeCafeRecipesTests +// +// Created by 김건호 on 7/17/24. +// + +import Foundation +import XCTest + +import RxSwift + +@testable +import HomeCafeRecipes + +final class FetchRecipeDetailUseCaseTests: XCTestCase { + + var fetchRecipeDetailRepository: FetchRecipeRepositoryMock! + var disposeBag: DisposeBag! + + final class FetchRecipeRepositoryMock: RecipeDetailRepository { + var fetchRecipeDetailCallCount: Int = 0 + var fetchRecipeDetailStub: Single = .just(Recipe.dummyRecipe()) + func fetchRecipeDetail(recipeID: Int) -> Single { + fetchRecipeDetailCallCount += 1 + return fetchRecipeDetailStub + } + } + + func createUseCase() -> FetchRecipeDetailUseCase { + let usecase = FetchRecipeDetailUseCaseImpl( + repository: fetchRecipeDetailRepository + ) + return usecase + } + + override func setUpWithError() throws { + fetchRecipeDetailRepository = .init() + disposeBag = .init() + } +} + +extension FetchRecipeDetailUseCaseTests { + + func test_execute를_호출하면_RecipeDetailRepository의_fetchRecipeDetail을_호출합니다(){ + + // Given + + let usecase = createUseCase() + fetchRecipeDetailRepository.fetchRecipeDetailStub = .just(Recipe.dummyRecipe()) + + // When + + usecase.execute(recipeID: 1) + .subscribe() + .disposed(by: disposeBag) + + // Then + + XCTAssertEqual(fetchRecipeDetailRepository.fetchRecipeDetailCallCount, 1) + } + + func test_RecipeDetailRepository의_성공응답이오면_Recipe를_반환합니다(){ + + // Given + + let usecase = createUseCase() + let recipe = Recipe.dummyRecipe() + fetchRecipeDetailRepository.fetchRecipeDetailStub = .just(recipe) + let expectation = self.expectation(description: "Fetch Recipe Success") + + // When + + usecase.execute(recipeID: 1) + .subscribe(onSuccess: { result in + if case .success(let fetchedRecipe) = result { + // Then + + XCTAssertEqual(fetchedRecipe.id, recipe.id) + expectation.fulfill() + } else { + XCTFail("Expected success but got failure") + } + }, onFailure: { error in + XCTFail("Expected success but got error: \(error)") + }) + .disposed(by: disposeBag) + + wait(for: [expectation], timeout: 1.0) + XCTAssertEqual(fetchRecipeDetailRepository.fetchRecipeDetailCallCount, 1) + } + + func test_RecipeDetailRepository의_실패응답이오면_Error를_반환합니다() { + + // Given + + let usecase = createUseCase() + let error = NSError(domain: "TestError", code: -1) + fetchRecipeDetailRepository.fetchRecipeDetailStub = .error(error) + let expectation = self.expectation(description: "Fetch Recipe Failure") + + // When + + usecase.execute(recipeID: 1) + .subscribe(onSuccess: { result in + if case .failure(let receivedError as NSError) = result { + // Then + + XCTAssertEqual(receivedError.domain, error.domain) + XCTAssertEqual(receivedError.code, error.code) + expectation.fulfill() + } else { + XCTFail("Expected failure but got success") + } + }, onFailure: { error in + XCTFail("Expected failure but got error: \(error)") + }) + .disposed(by: disposeBag) + + wait(for: [expectation], timeout: 1.0) + XCTAssertEqual(fetchRecipeDetailRepository.fetchRecipeDetailCallCount, 1) + + } + +} diff --git a/HomeCafeRecipes/HomeCafeRecipesTests/RecipeDeatilInteractorTests.swift b/HomeCafeRecipes/HomeCafeRecipesTests/RecipeDeatilInteractorTests.swift new file mode 100644 index 0000000..9c15b98 --- /dev/null +++ b/HomeCafeRecipes/HomeCafeRecipesTests/RecipeDeatilInteractorTests.swift @@ -0,0 +1,111 @@ +// +// RecipeDeatilInteractorTests.swift +// HomeCafeRecipesTests +// +// Created by 김건호 on 7/9/24. +// + +import Foundation +import XCTest + +import RxSwift + +@testable +import HomeCafeRecipes + +final class RecipeDetailInteractorTests: XCTestCase { + var fetchRecipeDetailUsecase: FetchRecipeDetailUseCaseMock! + var delegate: RecipeDetailInteractorDelegateMock! + + final class FetchRecipeDetailUseCaseMock: FetchRecipeDetailUseCase { + var executeCallCount: Int = 0 + var executeStub: Single> = .just(.failure(NSError())) + func execute(recipeID: Int) -> Single> { + executeCallCount += 1 + return executeStub + } + } + + final class RecipeDetailInteractorDelegateMock: RecipeDetailInteractorDelegate { + var fetchedCallCount: Int = 0 + var fetchedRecipeResult: Result? + func fetchedRecipe(result: Result) { + fetchedCallCount += 1 + fetchedRecipeResult = result + } + } + + func createInteractor(recipeID: Int = 0) -> RecipeDetailInteractor { + let interactor = RecipeDetailInteractorImpl( + fetchRecipeDetailUseCase: fetchRecipeDetailUsecase, + recipeID: recipeID + ) + + interactor.delegate = delegate + return interactor + } + + override func setUpWithError() throws { + fetchRecipeDetailUsecase = .init() + delegate = .init() + } +} + +// MARK: viewDidLoad + +extension RecipeDetailInteractorTests { + + func test_화면이_로드될때_FetchRecipeDetailUsecase를_호출합니다() { + // given + let interactor = createInteractor() + + // when + interactor.viewDidLoad() + + // then + XCTAssertEqual(self.fetchRecipeDetailUsecase.executeCallCount, 1) + } + + func test_FetchRecipeDetailUsecase의_성공응답이오면_Delegate로_성공을_전달합니다() { + // given + let interactor = createInteractor() + let recipe = Recipe.dummyRecipe() + fetchRecipeDetailUsecase.executeStub = .just(.success(recipe)) + + // when + interactor.viewDidLoad() + + // then + XCTAssertEqual(self.fetchRecipeDetailUsecase.executeCallCount, 1) + XCTAssertEqual(self.delegate.fetchedCallCount, 1) + + if case .success(let fetchedRecipe)? = delegate.fetchedRecipeResult { + XCTAssertEqual(fetchedRecipe.id, recipe.id) + } else { + XCTFail("Expected success but got failure or nil") + } + } + + func test_FetchRecipeDetailUsecase의_실패응답이오면_Delegate로_실패를_전달합니다() { + + // given + let interactor = createInteractor() + let error = NSError(domain: "TestError", code: -1) + fetchRecipeDetailUsecase.executeStub = .just(.failure(error)) + + // when + interactor.viewDidLoad() + + // then + XCTAssertEqual(self.fetchRecipeDetailUsecase.executeCallCount, 1) + XCTAssertEqual(self.delegate.fetchedCallCount, 1) + + if case .failure(let fetchedError as NSError) = delegate.fetchedRecipeResult { + XCTAssertEqual(fetchedError.domain, error.domain) + XCTAssertEqual(fetchedError.code, error.code) + } else { + XCTFail("Expected success but got failure or nil") + } + + } +}