├── AmazingRecipes ├── .DS_Store ├── Assets.xcassets │ ├── Contents.json │ ├── AppIcon.appiconset │ │ ├── appstore.png │ │ └── Contents.json │ └── AccentColor.colorset │ │ └── Contents.json ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── Persistence │ ├── Recipe+CoreDataClass.swift │ ├── Ingredient+CoreDataClass.swift │ ├── AmazingRecipes.xcdatamodeld │ │ ├── .xccurrentversion │ │ ├── AmazingRecipes.xcdatamodel │ │ │ └── contents │ │ └── AmazingRecipes 2.xcdatamodel │ │ │ └── contents │ ├── Ingredient+CoreDataProperties.swift │ ├── Recipe+CoreDataProperties.swift │ ├── RecipeDataSource.swift │ └── Persistence.swift ├── AppCore │ └── AmazingRecipesApp.swift ├── ImagePicker │ └── ImagePickerView.swift ├── RecipesList │ └── RecipesListView.swift ├── RecipeInfos │ └── RecipeInfoView.swift ├── CreateRecipe │ └── CreateRecipeView.swift └── EditRecipe │ └── EditRecipeView.swift ├── AmazingRecipes.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcuserdata │ │ └── igorsilva.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcuserdata │ └── igorsilva.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── xcshareddata │ └── xcschemes │ │ └── AmazingRecipes.xcscheme └── project.pbxproj ├── LICENSE └── README.md /AmazingRecipes/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorsilvadev/amazing-recipes/HEAD/AmazingRecipes/.DS_Store -------------------------------------------------------------------------------- /AmazingRecipes/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AmazingRecipes/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AmazingRecipes/Assets.xcassets/AppIcon.appiconset/appstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorsilvadev/amazing-recipes/HEAD/AmazingRecipes/Assets.xcassets/AppIcon.appiconset/appstore.png -------------------------------------------------------------------------------- /AmazingRecipes.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AmazingRecipes.xcodeproj/xcuserdata/igorsilva.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /AmazingRecipes.xcodeproj/project.xcworkspace/xcuserdata/igorsilva.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorsilvadev/amazing-recipes/HEAD/AmazingRecipes.xcodeproj/project.xcworkspace/xcuserdata/igorsilva.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /AmazingRecipes/Persistence/Recipe+CoreDataClass.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Recipe+CoreDataClass.swift 3 | // AmazingRecipes 4 | // 5 | // Created by Igor Silva on 27/07/23. 6 | // 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | @objc(Recipe) 13 | public class Recipe: NSManagedObject { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /AmazingRecipes/Persistence/Ingredient+CoreDataClass.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Ingredient+CoreDataClass.swift 3 | // AmazingRecipes 4 | // 5 | // Created by Igor Silva on 27/07/23. 6 | // 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | @objc(Ingredient) 13 | public class Ingredient: NSManagedObject { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /AmazingRecipes/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "appstore.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /AmazingRecipes.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AmazingRecipes/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "platform" : "universal", 6 | "reference" : "systemOrangeColor" 7 | }, 8 | "idiom" : "universal" 9 | } 10 | ], 11 | "info" : { 12 | "author" : "xcode", 13 | "version" : 1 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /AmazingRecipes/Persistence/AmazingRecipes.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | AmazingRecipes.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /AmazingRecipes/Persistence/Ingredient+CoreDataProperties.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Ingredient+CoreDataProperties.swift 3 | // AmazingRecipes 4 | // 5 | // Created by Igor Silva on 27/07/23. 6 | // 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | 13 | extension Ingredient { 14 | 15 | @nonobjc public class func fetchRequest() -> NSFetchRequest { 16 | return NSFetchRequest(entityName: "Ingredient") 17 | } 18 | 19 | @NSManaged public var name: String? 20 | @NSManaged public var recipe: Recipe? 21 | 22 | } 23 | 24 | extension Ingredient : Identifiable { 25 | 26 | } 27 | -------------------------------------------------------------------------------- /AmazingRecipes.xcodeproj/xcuserdata/igorsilva.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | AmazingRecipes.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 232EEA9C2A24497B004B0636 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /AmazingRecipes/AppCore/AmazingRecipesApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AmazingRecipesApp.swift 3 | // AmazingRecipes 4 | // 5 | // Created by Igor Silva on 28/05/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct AmazingRecipesApp: App { 12 | // Ao utilizar o 'shared' os dados serão persistidos após encerramento do app. 13 | // Caso necessite apenas enquanto o app está executando troque por .preview 14 | let persistenceController = PersistenceController.shared 15 | 16 | var body: some Scene { 17 | WindowGroup { 18 | RecipesListView(dataSource: RecipeDataSourceManager(context: persistenceController.container.viewContext)) 19 | // Adicionamos o viewContext como um enviroment para que todas as views possam acessá-lo e realizar as operações de CRUD 20 | .environment(\.managedObjectContext, persistenceController.container.viewContext) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Igor Silva 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /AmazingRecipes/ImagePicker/ImagePickerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImagePickerView.swift 3 | // AmazingRecipes 4 | // 5 | // Created by Igor Silva on 01/06/23. 6 | // 7 | 8 | import SwiftUI 9 | import PhotosUI 10 | 11 | struct ImagePickerView: View { 12 | 13 | @State private var selectedItem: PhotosPickerItem? = nil 14 | @Binding var selectedImageData: Data? 15 | 16 | var body: some View { 17 | 18 | if let selectedImageData, 19 | let uiImage = UIImage(data: selectedImageData) { 20 | Image(uiImage: uiImage) 21 | .resizable() 22 | .scaledToFit() 23 | .frame(width: 250, height: 250) 24 | } 25 | PhotosPicker( 26 | selection: $selectedItem, 27 | matching: .images, 28 | photoLibrary: .shared()) { 29 | Text("Selecione uma imagem") 30 | } 31 | .onChange(of: selectedItem) { newItem in 32 | Task { 33 | // Retrieve selected asset in the form of Data 34 | if let data = try? await newItem?.loadTransferable(type: Data.self) { 35 | selectedImageData = data 36 | } 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /AmazingRecipes/Persistence/Recipe+CoreDataProperties.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Recipe+CoreDataProperties.swift 3 | // AmazingRecipes 4 | // 5 | // Created by Igor Silva on 27/07/23. 6 | // 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | 13 | extension Recipe { 14 | 15 | @nonobjc public class func fetchRequest() -> NSFetchRequest { 16 | return NSFetchRequest(entityName: "Recipe") 17 | } 18 | 19 | @NSManaged public var desc: String? 20 | @NSManaged public var image: Data? 21 | @NSManaged public var preparationTime: Int16 22 | @NSManaged public var price: Double 23 | @NSManaged public var timestamp: Date? 24 | @NSManaged public var title: String? 25 | @NSManaged public var ingredients: NSSet? 26 | 27 | var wrappedIngredients: [Ingredient] { 28 | self.ingredients?.allObjects as? [Ingredient] ?? [] 29 | } 30 | 31 | } 32 | 33 | // MARK: Generated accessors for ingredients 34 | extension Recipe { 35 | 36 | @objc(addIngredientsObject:) 37 | @NSManaged public func addToIngredients(_ value: Ingredient) 38 | 39 | @objc(removeIngredientsObject:) 40 | @NSManaged public func removeFromIngredients(_ value: Ingredient) 41 | 42 | @objc(addIngredients:) 43 | @NSManaged public func addToIngredients(_ values: NSSet) 44 | 45 | @objc(removeIngredients:) 46 | @NSManaged public func removeFromIngredients(_ values: NSSet) 47 | 48 | } 49 | 50 | extension Recipe : Identifiable { 51 | 52 | } 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Amazing Recipes 3 | 4 | ## Alguns desafios que você pode fazer para complementar o projeto 5 | 6 | 7 | ### Adicione uma barra de pesquisa à visualização da lista. 8 | - Implemente uma função de pesquisa para filtrar os itens com base no texto inserido. 9 | - Atualize a lista à medida que o usuário digita na barra de pesquisa. 10 | 11 | 12 | ### Refatore a tela de criar uma receita 13 | - Altere a tela de criar receita para que seja possível criar e editar uma receita utilizando a mesma **view** 14 | 15 | ### Adicionar classificação e filtragem. 16 | 17 | - Adicione opções de classificação à visualização da lista. 18 | 19 | - Permita que o usuário filtre os itens com base em critérios específicos. 20 | 21 | - Atualize a exibição da lista em tempo real conforme as opções de classificação e filtragem são alteradas. 22 | 23 | ### Separe a lógica de CRUD e as propriedades das views 24 | - Crie uma **view model** para realizar as operações de CRUD e armazenar as propriedades da view, reduzindo assim a quantidade de **@States** 25 | 26 | 27 | 28 | # DESAFIOS - PARTE 2 29 | 30 | ### Implemente o Data Source para criação de uma receita 31 | - Seguindo o pattern Repository + Data Source implemente a criação de uma receita 32 | 33 | ### Crie outras computed properties para as propriedades opcionais da entidade Recipe 34 | 35 | ### Crie uma nova versão da modelagem dos dados 36 | 37 | 38 | 39 | 40 | ## Conheça a IrmandadeSwift 41 | 42 | https://www.irmandadeswift.com/home 43 | -------------------------------------------------------------------------------- /AmazingRecipes/Persistence/AmazingRecipes.xcdatamodeld/AmazingRecipes.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /AmazingRecipes/Persistence/AmazingRecipes.xcdatamodeld/AmazingRecipes 2.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /AmazingRecipes/Persistence/RecipeDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecipeDataSource.swift 3 | // AmazingRecipes 4 | // 5 | // Created by Igor Silva on 27/07/23. 6 | // 7 | 8 | import Foundation 9 | import CoreData 10 | 11 | protocol RecipeDataSource { 12 | var context: NSManagedObjectContext { get set} 13 | func getAll() -> [Recipe] 14 | func getByName(name: String) -> Recipe? 15 | func delete(recipe: Recipe) 16 | func create(desc: String?, image: Data?, preparationTime: Int16, price: Double, title: String?) 17 | } 18 | 19 | 20 | class RecipeDataSourceManager: RecipeDataSource { 21 | var context: NSManagedObjectContext 22 | 23 | init(context: NSManagedObjectContext) { 24 | self.context = context 25 | } 26 | 27 | func getAll() -> [Recipe] { 28 | do { 29 | let recipes = try context.fetch(Recipe.fetchRequest()) 30 | return recipes 31 | } catch { 32 | return [] 33 | } 34 | } 35 | 36 | func getByName(name: String) -> Recipe? { 37 | let request = Recipe.fetchRequest() 38 | let predicate = NSPredicate(format: "title == %@", name) 39 | request.predicate = predicate 40 | do { 41 | let recipes = try context.fetch(request) 42 | return recipes.first 43 | } catch { 44 | return nil 45 | } 46 | } 47 | 48 | func delete(recipe: Recipe) { 49 | do { 50 | context.delete(recipe) 51 | try context.save() 52 | } catch { 53 | context.rollback() 54 | print("Erro ao deletar") 55 | } 56 | } 57 | 58 | func create(desc: String?, image: Data?, preparationTime: Int16, price: Double, title: String?) { 59 | let recipe = Recipe(context: context) 60 | recipe.desc = desc 61 | recipe.title = title 62 | recipe.price = price 63 | recipe.timestamp = Date() 64 | recipe.preparationTime = preparationTime 65 | recipe.image = image 66 | } 67 | 68 | 69 | } 70 | -------------------------------------------------------------------------------- /AmazingRecipes/RecipesList/RecipesListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecipesListView.swift 3 | // AmazingRecipes 4 | // 5 | // Created by Igor Silva on 28/05/23. 6 | // 7 | 8 | import SwiftUI 9 | import CoreData 10 | 11 | struct RecipesListView: View { 12 | 13 | var dataSource: RecipeDataSource 14 | @State private var showAddItem = false 15 | @State var recipes: [Recipe] = [] 16 | 17 | var body: some View { 18 | NavigationView { 19 | VStack { 20 | List { 21 | ForEach(recipes) { recipe in 22 | NavigationLink { 23 | RecipeInfoView(recipe: recipe) 24 | } label: { 25 | Text(recipe.title ?? "") 26 | } 27 | } 28 | .onDelete(perform: deleteRecipes) 29 | } 30 | .toolbar { 31 | ToolbarItem(placement: .bottomBar) { 32 | Button(action: addItem) { 33 | Label("Add Item", systemImage: "plus") 34 | } 35 | } 36 | ToolbarItem(placement: .bottomBar) { 37 | EditButton() 38 | } 39 | 40 | } 41 | } 42 | .sheet(isPresented: $showAddItem, onDismiss: { 43 | fetchRecipes() 44 | }) { 45 | CreateRecipeView() 46 | } 47 | .navigationTitle("Lista de Receitas") 48 | .onAppear { 49 | fetchRecipes() 50 | } 51 | } 52 | } 53 | 54 | private func fetchRecipes() { 55 | recipes = dataSource.getAll() 56 | } 57 | 58 | private func addItem() { 59 | showAddItem = true 60 | } 61 | 62 | private func deleteRecipes(offsets: IndexSet) { 63 | withAnimation { 64 | offsets.map { recipes[$0] }.forEach { recipe in 65 | dataSource.delete(recipe: recipe) 66 | } 67 | } 68 | fetchRecipes() 69 | } 70 | } 71 | 72 | struct ContentView_Previews: PreviewProvider { 73 | static var previews: some View { 74 | RecipesListView(dataSource: RecipeDataSourceManager(context: PersistenceController.preview.container.viewContext)) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /AmazingRecipes/RecipeInfos/RecipeInfoView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecipeInfoView.swift 3 | // AmazingRecipes 4 | // 5 | // Created by Igor Silva on 31/05/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct RecipeInfoView: View { 11 | // Experimente remover o StateObject do recipe. Você notará que ao voltar da tela de edição, mesmo salvando os valores não mudarão. 12 | @StateObject var recipe: Recipe 13 | @State private var showEditView = false 14 | var body: some View { 15 | ScrollView { 16 | VStack { 17 | // MARK: Image 18 | if let data = recipe.image, let uiImage = UIImage(data: data) { 19 | Image(uiImage: uiImage) 20 | .resizable() 21 | .scaledToFit() 22 | .frame(width: 250, height: 250) 23 | } 24 | VStack(alignment: .leading, spacing: 10) { 25 | 26 | Text(recipe.title ?? "") 27 | .font(.title) 28 | .padding(.leading) 29 | CustomTextInfo(title: "Custo de preparo:", text: "R$\(recipe.price.formatted())") 30 | CustomTextInfo(title: "Tempo de preparo:", text: "\(recipe.preparationTime) minutos") 31 | CustomTextInfo(title: "Modo de preparo:", text: recipe.desc ?? "") 32 | Text("Ingredientes:") 33 | .fontWeight(.bold) 34 | .padding(.leading) 35 | 36 | // MARK: Listagem dos ingredientes 37 | // Note que é feito um cast do tipo de dados. Essa conversão é necessária porque os dados são armazenados como Any, porém caso não seja informado o tipo correto ocorrerá um erro de casting. 38 | ForEach(recipe.wrappedIngredients) { ingredient in 39 | Text(ingredient.name ?? "") 40 | .padding(.leading) 41 | } 42 | 43 | } 44 | .padding() 45 | Spacer() 46 | } 47 | .sheet(isPresented: $showEditView, content: { 48 | EditRecipeView(recipe: recipe) 49 | }) 50 | .toolbar { 51 | ToolbarItem(placement: .navigationBarTrailing) { 52 | Button("Editar") { 53 | showEditView = true 54 | } 55 | } 56 | } 57 | } 58 | } 59 | } 60 | 61 | struct CustomTextInfo: View { 62 | var title: String 63 | var text: String 64 | 65 | var body: some View { 66 | VStack(alignment: .leading){ 67 | Text(title) 68 | .fontWeight(.bold) 69 | 70 | Text(text) 71 | } 72 | .frame(maxWidth: .infinity, alignment: .leading) 73 | .padding(.leading) 74 | } 75 | } 76 | 77 | struct RecipeInfoView_Previews: PreviewProvider { 78 | static var previews: some View { 79 | RecipeInfoView(recipe: try! PersistenceController.preview.container.viewContext.fetch(Recipe.fetchRequest()).first!) 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /AmazingRecipes/Persistence/Persistence.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Persistence.swift 3 | // AmazingRecipes 4 | // 5 | // Created by Igor Silva on 28/05/23. 6 | // 7 | 8 | import CoreData 9 | 10 | struct PersistenceController { 11 | static let shared = PersistenceController() 12 | 13 | 14 | // Este preview é uma **computed property** que cria uma sequencia de dados fictícios apenas para uso em memória. Ele é muito útil para os previews no canvas 15 | static var preview: PersistenceController = { 16 | let controller = PersistenceController(inMemory: true) 17 | 18 | for i in 0...10 { 19 | let recipe = Recipe(context: controller.container.viewContext) 20 | recipe.title = "Receita \(i)" 21 | recipe.desc = "Modo de preparo para receita \(i)" 22 | recipe.preparationTime = Int16(10) 23 | recipe.price = 10.0 24 | let ingredient = Ingredient(context: controller.container.viewContext) 25 | ingredient.name = "Ingrediente para receita \(i)" 26 | recipe.addToIngredients(ingredient) 27 | recipe.timestamp = Date() 28 | } 29 | 30 | return controller 31 | }() 32 | 33 | //MARK: Core Data Container 34 | // Esse container faz o gerenciamento do armazenamento do app. Nele vai exister o view context para as operações de CRUD 35 | let container: NSPersistentContainer 36 | 37 | init(inMemory: Bool = false) { 38 | // Aqui ocorre a inicialização do container informando o nome do arquivo que contém o modelo das entidades e relacionamentos 39 | container = NSPersistentContainer(name: "AmazingRecipes") 40 | 41 | // Essa condicional permite criar uma versão em memória dos dados, ou seja, assim que o app encerrar esses dados deixam de existir. Isso é útil para utilizar o preview do Canvas ou para não precisar apagar o app a cada compilação e execução. 42 | if inMemory { 43 | // O caminho /dev/null passa um local inválido para o armazenamento permanente, evitando assim que os dados sejam salvos após o encerramento do app. 44 | container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") 45 | } 46 | 47 | 48 | // Você deve chamar esse método para fazer o loading da store e permitir a criação da stack do core data com os dados 49 | container.loadPersistentStores(completionHandler: { nsDescription, loadError in 50 | // Aqui você pode executar alguma lógica necessária após o loading da store 51 | 52 | //⚠️ É importante conferir se o loadError contém nil, para garantir que não ocorreu nenhum problema no carregamento 53 | }) 54 | 55 | 56 | 57 | 58 | 59 | //⚠️ Se você está iniciando ainda não precisa se preocupar com esse trecho. 60 | 61 | /* Quando o automaticallyMergesChangesFromParent está definido como true para o viewContext, qualquer alteração feita nos contextos pais será automaticamente mesclada no viewContext. 62 | Isso significa que se um contexto pai fizer alterações em objetos gerenciados (managed objects), como criar, atualizar ou excluir objetos, essas alterações serão refletidas automaticamente no viewContext e, portanto, na interface do usuário. 63 | */ 64 | container.viewContext.automaticallyMergesChangesFromParent = true 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /AmazingRecipes.xcodeproj/xcshareddata/xcschemes/AmazingRecipes.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 42 | 44 | 50 | 51 | 52 | 53 | 56 | 57 | 58 | 59 | 65 | 67 | 73 | 74 | 75 | 76 | 78 | 79 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /AmazingRecipes/CreateRecipe/CreateRecipeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CreateRecipeView.swift 3 | // AmazingRecipes 4 | // 5 | // Created by Igor Silva on 30/05/23. 6 | // 7 | 8 | import SwiftUI 9 | import CoreData 10 | 11 | struct CreateRecipeView: View { 12 | 13 | @State private var title = "" 14 | @State private var price = 0.0 15 | @State private var preparationTime = 0 16 | @State private var image: Data? 17 | @State private var desc = "" 18 | 19 | @State private var presentAlert = false 20 | @State private var ingredientName = "" 21 | 22 | @State private var ingredients: [String] = [] 23 | @Environment(\.dismiss) var dismiss 24 | 25 | // Aqui ocorre o acesso ao context do core data 26 | @Environment(\.managedObjectContext) private var viewContext 27 | 28 | private var priceFormmater: NumberFormatter { 29 | let formatter = NumberFormatter() 30 | formatter.numberStyle = .currency 31 | return formatter 32 | } 33 | 34 | var body: some View { 35 | NavigationView { 36 | ScrollView { 37 | VStack(spacing: 20){ 38 | // MARK: Image Picker 39 | 40 | ImagePickerView(selectedImageData: $image) 41 | 42 | 43 | // MARK: Titulo 44 | VStack(alignment: .leading) { 45 | Text("Titulo da receita:") 46 | TextField("Titulo da receita", text: $title) 47 | .textFieldStyle(.roundedBorder) 48 | } 49 | 50 | // MARK: TEMPO DE PREPARO 51 | VStack(alignment: .leading) { 52 | Text("Tempo de preparo em minutos:") 53 | TextField("Tempo de preparo em minutos", value: $preparationTime, formatter: NumberFormatter()) 54 | .textFieldStyle(.roundedBorder) 55 | .keyboardType(.numberPad) 56 | } 57 | 58 | // MARK: CUSTO DE PREPARO 59 | VStack(alignment: .leading) { 60 | Text("Custo de preparo:") 61 | TextField("Custo de preparo", value: $price, formatter: priceFormmater) 62 | .textFieldStyle(.roundedBorder) 63 | .keyboardType(.numberPad) 64 | } 65 | // MARK: LISTAGEM DE INGREDIENTES 66 | if !ingredients.isEmpty { 67 | List { 68 | ForEach(ingredients, id: \.self) { ingredient in 69 | Text(ingredient) 70 | .listRowBackground(Color.gray.opacity(0.3)) 71 | } 72 | .onDelete(perform: deleteIngredients) 73 | } 74 | .background(.white) 75 | .scrollContentBackground(.hidden) 76 | .frame(height: 200) 77 | } 78 | 79 | // MARK: Adicionar ingrediente 80 | HStack { 81 | Button { 82 | presentAlert = true 83 | } label: { 84 | Label("Adicionar ingrediente", systemImage: "plus") 85 | } 86 | Spacer() 87 | } 88 | 89 | 90 | 91 | // MARK: DESCRIÇÃO DE PREPARO 92 | VStack(alignment: .leading) { 93 | Text("Modo de preparo:") 94 | TextEditor(text: $desc) 95 | .border(.gray.opacity(0.3)) 96 | .frame(height: 150) 97 | } 98 | 99 | 100 | } 101 | .padding() 102 | .alert("Ingrediente", isPresented: $presentAlert, actions: { 103 | TextField("Nome", text: $ingredientName) 104 | Button("Cancelar", role: .cancel) { 105 | 106 | } 107 | Button("Adicionar") { 108 | if !ingredientName.isEmpty { 109 | ingredients.append(ingredientName) 110 | } 111 | } 112 | 113 | }) 114 | .toolbar { 115 | ToolbarItem(placement: .navigationBarLeading) { 116 | Button("Cancelar") { 117 | dismiss() 118 | } 119 | } 120 | ToolbarItem(placement: .navigationBarTrailing) { 121 | Button("Salvar") { 122 | saveRecipe() 123 | } 124 | .disabled(!canSave()) 125 | } 126 | } 127 | } 128 | } 129 | } 130 | 131 | private func saveRecipe() { 132 | let recipe = Recipe(context: viewContext) 133 | recipe.title = title 134 | recipe.price = price 135 | recipe.desc = desc 136 | recipe.image = image 137 | recipe.preparationTime = Int16(preparationTime) 138 | recipe.timestamp = Date() 139 | 140 | ingredients.forEach { ingredientName in 141 | let ingredient = Ingredient(context: viewContext) 142 | ingredient.name = ingredientName 143 | recipe.addToIngredients(ingredient) 144 | } 145 | 146 | // Essa é uma segunda maneira de você salvar os dados sem precisar criar um block 'try catch' 147 | try? viewContext.save() 148 | dismiss() 149 | } 150 | 151 | private func canSave() -> Bool { 152 | return !title.isEmpty 153 | } 154 | 155 | private func deleteIngredients(offsets: IndexSet) { 156 | withAnimation { 157 | ingredients.remove(atOffsets: offsets) 158 | } 159 | } 160 | } 161 | 162 | struct CreateRecipeView_Previews: PreviewProvider { 163 | static var previews: some View { 164 | CreateRecipeView() 165 | .environment(\.managedObjectContext, PersistenceController(inMemory: true).container.viewContext) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /AmazingRecipes/EditRecipe/EditRecipeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EditRecipeView.swift 3 | // AmazingRecipes 4 | // 5 | // Created by Igor Silva on 01/06/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct EditRecipeView: View { 11 | 12 | @State private var title = "" 13 | @State private var price = 0.0 14 | @State private var preparationTime = 0 15 | @State private var image: Data? 16 | @State private var desc = "" 17 | 18 | @State private var presentAlert = false 19 | @State private var ingredientName = "" 20 | 21 | @State private var ingredients: [String] = [] 22 | @Environment(\.dismiss) var dismiss 23 | 24 | // Aqui ocorre o acesso ao context do core data 25 | @Environment(\.managedObjectContext) private var viewContext 26 | 27 | var recipe: Recipe 28 | 29 | init(recipe: Recipe) { 30 | self.recipe = recipe 31 | } 32 | 33 | private var priceFormmater: NumberFormatter { 34 | let formatter = NumberFormatter() 35 | formatter.numberStyle = .currency 36 | return formatter 37 | } 38 | 39 | var body: some View { 40 | NavigationView { 41 | ScrollView { 42 | VStack(spacing: 20){ 43 | 44 | // MARK: Image Picker 45 | ImagePickerView(selectedImageData: $image) 46 | 47 | // MARK: Titulo 48 | VStack(alignment: .leading) { 49 | Text("Titulo da receita:") 50 | TextField("Titulo da receita", text: $title) 51 | .textFieldStyle(.roundedBorder) 52 | } 53 | 54 | // MARK: TEMPO DE PREPARO 55 | VStack(alignment: .leading) { 56 | Text("Tempo de preparo em minutos:") 57 | TextField("Tempo de preparo em minutos", value: $preparationTime, formatter: NumberFormatter()) 58 | .textFieldStyle(.roundedBorder) 59 | .keyboardType(.numberPad) 60 | } 61 | 62 | // MARK: CUSTO DE PREPARO 63 | VStack(alignment: .leading) { 64 | Text("Custo de preparo:") 65 | TextField("Custo de preparo", value: $price, formatter: priceFormmater) 66 | .textFieldStyle(.roundedBorder) 67 | .keyboardType(.numberPad) 68 | } 69 | // MARK: LISTAGEM DE INGREDIENTES 70 | if !ingredients.isEmpty { 71 | List { 72 | ForEach(ingredients, id: \.self) { ingredient in 73 | Text(ingredient) 74 | .listRowBackground(Color.gray.opacity(0.3)) 75 | } 76 | .onDelete(perform: deleteIngredients) 77 | } 78 | .background(.white) 79 | .scrollContentBackground(.hidden) 80 | .frame(height: 200) 81 | } 82 | 83 | // MARK: Adicionar ingrediente 84 | HStack { 85 | Button { 86 | presentAlert = true 87 | } label: { 88 | Label("Adicionar ingrediente", systemImage: "plus") 89 | } 90 | Spacer() 91 | } 92 | 93 | 94 | 95 | // MARK: DESCRIÇÃO DE PREPARO 96 | VStack(alignment: .leading) { 97 | Text("Modo de preparo:") 98 | TextEditor(text: $desc) 99 | .border(.gray.opacity(0.3)) 100 | .frame(height: 150) 101 | } 102 | 103 | 104 | } 105 | .padding() 106 | .alert("Ingrediente", isPresented: $presentAlert, actions: { 107 | TextField("Nome", text: $ingredientName) 108 | Button("Cancelar", role: .cancel) { 109 | 110 | } 111 | Button("Adicionar") { 112 | if !ingredientName.isEmpty { 113 | ingredients.append(ingredientName) 114 | } 115 | } 116 | 117 | }) 118 | .toolbar { 119 | ToolbarItem(placement: .navigationBarLeading) { 120 | Button("Cancelar") { 121 | dismiss() 122 | } 123 | } 124 | ToolbarItem(placement: .navigationBarTrailing) { 125 | Button("Salvar") { 126 | saveRecipe() 127 | } 128 | .disabled(!canSave()) 129 | } 130 | } 131 | .onAppear { 132 | setRecipeInfos() 133 | } 134 | } 135 | } 136 | } 137 | 138 | private func setRecipeInfos() { 139 | self.title = recipe.title ?? "" 140 | self.price = recipe.price 141 | self.preparationTime = Int(recipe.preparationTime) 142 | self.image = recipe.image 143 | self.desc = recipe.desc ?? "" 144 | self.ingredients = recipe.wrappedIngredients.map{ $0.name ?? ""} 145 | } 146 | 147 | private func saveRecipe() { 148 | recipe.title = title 149 | recipe.price = price 150 | recipe.desc = desc 151 | recipe.image = image 152 | recipe.preparationTime = Int16(preparationTime) 153 | recipe.timestamp = Date() 154 | 155 | 156 | // Primeiro deletar todos os ingredientes da receita 157 | recipe.wrappedIngredients 158 | .forEach { ingredient in 159 | viewContext.delete(ingredient) 160 | } 161 | 162 | // Em seguida recriar a lista dos ingredientes que foram adicionados 163 | ingredients.forEach { ingredientName in 164 | let ingredient = Ingredient(context: viewContext) 165 | ingredient.name = ingredientName 166 | recipe.addToIngredients(ingredient) 167 | } 168 | 169 | 170 | // Essa é uma segunda maneira de você salvar os dados sem precisar criar um block 'try catch' 171 | try? viewContext.save() 172 | dismiss() 173 | } 174 | 175 | private func canSave() -> Bool { 176 | return !title.isEmpty 177 | } 178 | 179 | private func deleteIngredients(offsets: IndexSet) { 180 | withAnimation { 181 | ingredients.remove(atOffsets: offsets) 182 | } 183 | } 184 | } 185 | 186 | struct EditRecipeView_Previews: PreviewProvider { 187 | static var previews: some View { 188 | EditRecipeView(recipe: try! PersistenceController.preview.container.viewContext.fetch(Recipe.fetchRequest()).first!) 189 | 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /AmazingRecipes.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 231FD61D2A2603AE0081896E /* CreateRecipeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231FD61C2A2603AE0081896E /* CreateRecipeView.swift */; }; 11 | 232BCE6A2A7286060091A602 /* Recipe+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232BCE662A7286060091A602 /* Recipe+CoreDataClass.swift */; }; 12 | 232BCE6B2A7286060091A602 /* Recipe+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232BCE672A7286060091A602 /* Recipe+CoreDataProperties.swift */; }; 13 | 232BCE6C2A7286060091A602 /* Ingredient+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232BCE682A7286060091A602 /* Ingredient+CoreDataClass.swift */; }; 14 | 232BCE6D2A7286060091A602 /* Ingredient+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232BCE692A7286060091A602 /* Ingredient+CoreDataProperties.swift */; }; 15 | 232BCE6F2A731BC30091A602 /* RecipeDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232BCE6E2A731BC30091A602 /* RecipeDataSource.swift */; }; 16 | 232EEAA12A24497B004B0636 /* AmazingRecipesApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232EEAA02A24497B004B0636 /* AmazingRecipesApp.swift */; }; 17 | 232EEAA32A24497B004B0636 /* RecipesListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232EEAA22A24497B004B0636 /* RecipesListView.swift */; }; 18 | 232EEAA52A24497F004B0636 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 232EEAA42A24497F004B0636 /* Assets.xcassets */; }; 19 | 232EEAA82A24497F004B0636 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 232EEAA72A24497F004B0636 /* Preview Assets.xcassets */; }; 20 | 232EEAAA2A24497F004B0636 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232EEAA92A24497F004B0636 /* Persistence.swift */; }; 21 | 232EEAAD2A24497F004B0636 /* AmazingRecipes.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 232EEAAB2A24497F004B0636 /* AmazingRecipes.xcdatamodeld */; }; 22 | 234E98662A27560400BA91D7 /* RecipeInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 234E98652A27560400BA91D7 /* RecipeInfoView.swift */; }; 23 | 23B0D50A2A28687C001869B0 /* EditRecipeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23B0D5092A28687C001869B0 /* EditRecipeView.swift */; }; 24 | 23B0D50D2A287163001869B0 /* ImagePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23B0D50C2A287163001869B0 /* ImagePickerView.swift */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 231FD61C2A2603AE0081896E /* CreateRecipeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRecipeView.swift; sourceTree = ""; }; 29 | 232BCE662A7286060091A602 /* Recipe+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Recipe+CoreDataClass.swift"; sourceTree = ""; }; 30 | 232BCE672A7286060091A602 /* Recipe+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Recipe+CoreDataProperties.swift"; sourceTree = ""; }; 31 | 232BCE682A7286060091A602 /* Ingredient+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Ingredient+CoreDataClass.swift"; sourceTree = ""; }; 32 | 232BCE692A7286060091A602 /* Ingredient+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Ingredient+CoreDataProperties.swift"; sourceTree = ""; }; 33 | 232BCE6E2A731BC30091A602 /* RecipeDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeDataSource.swift; sourceTree = ""; }; 34 | 232EEA9D2A24497B004B0636 /* AmazingRecipes.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AmazingRecipes.app; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 232EEAA02A24497B004B0636 /* AmazingRecipesApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmazingRecipesApp.swift; sourceTree = ""; }; 36 | 232EEAA22A24497B004B0636 /* RecipesListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipesListView.swift; sourceTree = ""; }; 37 | 232EEAA42A24497F004B0636 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 38 | 232EEAA72A24497F004B0636 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 39 | 232EEAA92A24497F004B0636 /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; 40 | 232EEAAC2A24497F004B0636 /* AmazingRecipes.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = AmazingRecipes.xcdatamodel; sourceTree = ""; }; 41 | 234E98652A27560400BA91D7 /* RecipeInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeInfoView.swift; sourceTree = ""; }; 42 | 23B0D5092A28687C001869B0 /* EditRecipeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditRecipeView.swift; sourceTree = ""; }; 43 | 23B0D50C2A287163001869B0 /* ImagePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePickerView.swift; sourceTree = ""; }; 44 | /* End PBXFileReference section */ 45 | 46 | /* Begin PBXFrameworksBuildPhase section */ 47 | 232EEA9A2A24497B004B0636 /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | /* End PBXFrameworksBuildPhase section */ 55 | 56 | /* Begin PBXGroup section */ 57 | 231FD6182A25FEF40081896E /* AppCore */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | 232EEAA02A24497B004B0636 /* AmazingRecipesApp.swift */, 61 | ); 62 | path = AppCore; 63 | sourceTree = ""; 64 | }; 65 | 231FD6192A25FF000081896E /* RecipesList */ = { 66 | isa = PBXGroup; 67 | children = ( 68 | 232EEAA22A24497B004B0636 /* RecipesListView.swift */, 69 | ); 70 | path = RecipesList; 71 | sourceTree = ""; 72 | }; 73 | 231FD61A2A25FF130081896E /* Persistence */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | 232BCE662A7286060091A602 /* Recipe+CoreDataClass.swift */, 77 | 232BCE672A7286060091A602 /* Recipe+CoreDataProperties.swift */, 78 | 232BCE682A7286060091A602 /* Ingredient+CoreDataClass.swift */, 79 | 232BCE692A7286060091A602 /* Ingredient+CoreDataProperties.swift */, 80 | 232EEAA92A24497F004B0636 /* Persistence.swift */, 81 | 232EEAAB2A24497F004B0636 /* AmazingRecipes.xcdatamodeld */, 82 | 232BCE6E2A731BC30091A602 /* RecipeDataSource.swift */, 83 | ); 84 | path = Persistence; 85 | sourceTree = ""; 86 | }; 87 | 231FD61B2A2603810081896E /* CreateRecipe */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | 231FD61C2A2603AE0081896E /* CreateRecipeView.swift */, 91 | ); 92 | path = CreateRecipe; 93 | sourceTree = ""; 94 | }; 95 | 232EEA942A24497B004B0636 = { 96 | isa = PBXGroup; 97 | children = ( 98 | 232EEA9F2A24497B004B0636 /* AmazingRecipes */, 99 | 232EEA9E2A24497B004B0636 /* Products */, 100 | ); 101 | sourceTree = ""; 102 | }; 103 | 232EEA9E2A24497B004B0636 /* Products */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 232EEA9D2A24497B004B0636 /* AmazingRecipes.app */, 107 | ); 108 | name = Products; 109 | sourceTree = ""; 110 | }; 111 | 232EEA9F2A24497B004B0636 /* AmazingRecipes */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 23B0D50B2A287152001869B0 /* ImagePicker */, 115 | 23B0D5082A286865001869B0 /* EditRecipe */, 116 | 234E98642A27556A00BA91D7 /* RecipeInfos */, 117 | 231FD61B2A2603810081896E /* CreateRecipe */, 118 | 231FD61A2A25FF130081896E /* Persistence */, 119 | 231FD6192A25FF000081896E /* RecipesList */, 120 | 231FD6182A25FEF40081896E /* AppCore */, 121 | 232EEAA42A24497F004B0636 /* Assets.xcassets */, 122 | 232EEAA62A24497F004B0636 /* Preview Content */, 123 | ); 124 | path = AmazingRecipes; 125 | sourceTree = ""; 126 | }; 127 | 232EEAA62A24497F004B0636 /* Preview Content */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 232EEAA72A24497F004B0636 /* Preview Assets.xcassets */, 131 | ); 132 | path = "Preview Content"; 133 | sourceTree = ""; 134 | }; 135 | 234E98642A27556A00BA91D7 /* RecipeInfos */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | 234E98652A27560400BA91D7 /* RecipeInfoView.swift */, 139 | ); 140 | path = RecipeInfos; 141 | sourceTree = ""; 142 | }; 143 | 23B0D5082A286865001869B0 /* EditRecipe */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | 23B0D5092A28687C001869B0 /* EditRecipeView.swift */, 147 | ); 148 | path = EditRecipe; 149 | sourceTree = ""; 150 | }; 151 | 23B0D50B2A287152001869B0 /* ImagePicker */ = { 152 | isa = PBXGroup; 153 | children = ( 154 | 23B0D50C2A287163001869B0 /* ImagePickerView.swift */, 155 | ); 156 | path = ImagePicker; 157 | sourceTree = ""; 158 | }; 159 | /* End PBXGroup section */ 160 | 161 | /* Begin PBXNativeTarget section */ 162 | 232EEA9C2A24497B004B0636 /* AmazingRecipes */ = { 163 | isa = PBXNativeTarget; 164 | buildConfigurationList = 232EEAB02A24497F004B0636 /* Build configuration list for PBXNativeTarget "AmazingRecipes" */; 165 | buildPhases = ( 166 | 232EEA992A24497B004B0636 /* Sources */, 167 | 232EEA9A2A24497B004B0636 /* Frameworks */, 168 | 232EEA9B2A24497B004B0636 /* Resources */, 169 | ); 170 | buildRules = ( 171 | ); 172 | dependencies = ( 173 | ); 174 | name = AmazingRecipes; 175 | productName = AmazingRecipes; 176 | productReference = 232EEA9D2A24497B004B0636 /* AmazingRecipes.app */; 177 | productType = "com.apple.product-type.application"; 178 | }; 179 | /* End PBXNativeTarget section */ 180 | 181 | /* Begin PBXProject section */ 182 | 232EEA952A24497B004B0636 /* Project object */ = { 183 | isa = PBXProject; 184 | attributes = { 185 | BuildIndependentTargetsInParallel = 1; 186 | LastSwiftUpdateCheck = 1430; 187 | LastUpgradeCheck = 1430; 188 | TargetAttributes = { 189 | 232EEA9C2A24497B004B0636 = { 190 | CreatedOnToolsVersion = 14.3; 191 | }; 192 | }; 193 | }; 194 | buildConfigurationList = 232EEA982A24497B004B0636 /* Build configuration list for PBXProject "AmazingRecipes" */; 195 | compatibilityVersion = "Xcode 14.0"; 196 | developmentRegion = en; 197 | hasScannedForEncodings = 0; 198 | knownRegions = ( 199 | en, 200 | Base, 201 | ); 202 | mainGroup = 232EEA942A24497B004B0636; 203 | productRefGroup = 232EEA9E2A24497B004B0636 /* Products */; 204 | projectDirPath = ""; 205 | projectRoot = ""; 206 | targets = ( 207 | 232EEA9C2A24497B004B0636 /* AmazingRecipes */, 208 | ); 209 | }; 210 | /* End PBXProject section */ 211 | 212 | /* Begin PBXResourcesBuildPhase section */ 213 | 232EEA9B2A24497B004B0636 /* Resources */ = { 214 | isa = PBXResourcesBuildPhase; 215 | buildActionMask = 2147483647; 216 | files = ( 217 | 232EEAA82A24497F004B0636 /* Preview Assets.xcassets in Resources */, 218 | 232EEAA52A24497F004B0636 /* Assets.xcassets in Resources */, 219 | ); 220 | runOnlyForDeploymentPostprocessing = 0; 221 | }; 222 | /* End PBXResourcesBuildPhase section */ 223 | 224 | /* Begin PBXSourcesBuildPhase section */ 225 | 232EEA992A24497B004B0636 /* Sources */ = { 226 | isa = PBXSourcesBuildPhase; 227 | buildActionMask = 2147483647; 228 | files = ( 229 | 234E98662A27560400BA91D7 /* RecipeInfoView.swift in Sources */, 230 | 232BCE6F2A731BC30091A602 /* RecipeDataSource.swift in Sources */, 231 | 231FD61D2A2603AE0081896E /* CreateRecipeView.swift in Sources */, 232 | 232EEAAA2A24497F004B0636 /* Persistence.swift in Sources */, 233 | 232EEAAD2A24497F004B0636 /* AmazingRecipes.xcdatamodeld in Sources */, 234 | 232EEAA32A24497B004B0636 /* RecipesListView.swift in Sources */, 235 | 232BCE6B2A7286060091A602 /* Recipe+CoreDataProperties.swift in Sources */, 236 | 232BCE6D2A7286060091A602 /* Ingredient+CoreDataProperties.swift in Sources */, 237 | 23B0D50D2A287163001869B0 /* ImagePickerView.swift in Sources */, 238 | 232EEAA12A24497B004B0636 /* AmazingRecipesApp.swift in Sources */, 239 | 232BCE6C2A7286060091A602 /* Ingredient+CoreDataClass.swift in Sources */, 240 | 23B0D50A2A28687C001869B0 /* EditRecipeView.swift in Sources */, 241 | 232BCE6A2A7286060091A602 /* Recipe+CoreDataClass.swift in Sources */, 242 | ); 243 | runOnlyForDeploymentPostprocessing = 0; 244 | }; 245 | /* End PBXSourcesBuildPhase section */ 246 | 247 | /* Begin XCBuildConfiguration section */ 248 | 232EEAAE2A24497F004B0636 /* Debug */ = { 249 | isa = XCBuildConfiguration; 250 | buildSettings = { 251 | ALWAYS_SEARCH_USER_PATHS = NO; 252 | CLANG_ANALYZER_NONNULL = YES; 253 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 254 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 255 | CLANG_ENABLE_MODULES = YES; 256 | CLANG_ENABLE_OBJC_ARC = YES; 257 | CLANG_ENABLE_OBJC_WEAK = YES; 258 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 259 | CLANG_WARN_BOOL_CONVERSION = YES; 260 | CLANG_WARN_COMMA = YES; 261 | CLANG_WARN_CONSTANT_CONVERSION = YES; 262 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 263 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 264 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 265 | CLANG_WARN_EMPTY_BODY = YES; 266 | CLANG_WARN_ENUM_CONVERSION = YES; 267 | CLANG_WARN_INFINITE_RECURSION = YES; 268 | CLANG_WARN_INT_CONVERSION = YES; 269 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 270 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 271 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 272 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 273 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 274 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 275 | CLANG_WARN_STRICT_PROTOTYPES = YES; 276 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 277 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 278 | CLANG_WARN_UNREACHABLE_CODE = YES; 279 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 280 | COPY_PHASE_STRIP = NO; 281 | DEBUG_INFORMATION_FORMAT = dwarf; 282 | ENABLE_STRICT_OBJC_MSGSEND = YES; 283 | ENABLE_TESTABILITY = YES; 284 | GCC_C_LANGUAGE_STANDARD = gnu11; 285 | GCC_DYNAMIC_NO_PIC = NO; 286 | GCC_NO_COMMON_BLOCKS = YES; 287 | GCC_OPTIMIZATION_LEVEL = 0; 288 | GCC_PREPROCESSOR_DEFINITIONS = ( 289 | "DEBUG=1", 290 | "$(inherited)", 291 | ); 292 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 293 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 294 | GCC_WARN_UNDECLARED_SELECTOR = YES; 295 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 296 | GCC_WARN_UNUSED_FUNCTION = YES; 297 | GCC_WARN_UNUSED_VARIABLE = YES; 298 | IPHONEOS_DEPLOYMENT_TARGET = 16.4; 299 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 300 | MTL_FAST_MATH = YES; 301 | ONLY_ACTIVE_ARCH = YES; 302 | SDKROOT = iphoneos; 303 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 304 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 305 | }; 306 | name = Debug; 307 | }; 308 | 232EEAAF2A24497F004B0636 /* Release */ = { 309 | isa = XCBuildConfiguration; 310 | buildSettings = { 311 | ALWAYS_SEARCH_USER_PATHS = NO; 312 | CLANG_ANALYZER_NONNULL = YES; 313 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 314 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 315 | CLANG_ENABLE_MODULES = YES; 316 | CLANG_ENABLE_OBJC_ARC = YES; 317 | CLANG_ENABLE_OBJC_WEAK = YES; 318 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 319 | CLANG_WARN_BOOL_CONVERSION = YES; 320 | CLANG_WARN_COMMA = YES; 321 | CLANG_WARN_CONSTANT_CONVERSION = YES; 322 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 323 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 324 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 325 | CLANG_WARN_EMPTY_BODY = YES; 326 | CLANG_WARN_ENUM_CONVERSION = YES; 327 | CLANG_WARN_INFINITE_RECURSION = YES; 328 | CLANG_WARN_INT_CONVERSION = YES; 329 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 330 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 331 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 332 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 333 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 334 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 335 | CLANG_WARN_STRICT_PROTOTYPES = YES; 336 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 337 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 338 | CLANG_WARN_UNREACHABLE_CODE = YES; 339 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 340 | COPY_PHASE_STRIP = NO; 341 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 342 | ENABLE_NS_ASSERTIONS = NO; 343 | ENABLE_STRICT_OBJC_MSGSEND = YES; 344 | GCC_C_LANGUAGE_STANDARD = gnu11; 345 | GCC_NO_COMMON_BLOCKS = YES; 346 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 347 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 348 | GCC_WARN_UNDECLARED_SELECTOR = YES; 349 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 350 | GCC_WARN_UNUSED_FUNCTION = YES; 351 | GCC_WARN_UNUSED_VARIABLE = YES; 352 | IPHONEOS_DEPLOYMENT_TARGET = 16.4; 353 | MTL_ENABLE_DEBUG_INFO = NO; 354 | MTL_FAST_MATH = YES; 355 | SDKROOT = iphoneos; 356 | SWIFT_COMPILATION_MODE = wholemodule; 357 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 358 | VALIDATE_PRODUCT = YES; 359 | }; 360 | name = Release; 361 | }; 362 | 232EEAB12A24497F004B0636 /* Debug */ = { 363 | isa = XCBuildConfiguration; 364 | buildSettings = { 365 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 366 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 367 | CODE_SIGN_STYLE = Automatic; 368 | CURRENT_PROJECT_VERSION = 1; 369 | DEVELOPMENT_ASSET_PATHS = "\"AmazingRecipes/Preview Content\""; 370 | DEVELOPMENT_TEAM = 272983AP33; 371 | ENABLE_PREVIEWS = YES; 372 | GENERATE_INFOPLIST_FILE = YES; 373 | INFOPLIST_KEY_CFBundleDisplayName = "Amazing Recipes"; 374 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 375 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 376 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 377 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 378 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 379 | LD_RUNPATH_SEARCH_PATHS = ( 380 | "$(inherited)", 381 | "@executable_path/Frameworks", 382 | ); 383 | MARKETING_VERSION = 1.0; 384 | PRODUCT_BUNDLE_IDENTIFIER = com.igorsilva.AmazingRecipes; 385 | PRODUCT_NAME = "$(TARGET_NAME)"; 386 | SWIFT_EMIT_LOC_STRINGS = YES; 387 | SWIFT_VERSION = 5.0; 388 | TARGETED_DEVICE_FAMILY = "1,2"; 389 | }; 390 | name = Debug; 391 | }; 392 | 232EEAB22A24497F004B0636 /* Release */ = { 393 | isa = XCBuildConfiguration; 394 | buildSettings = { 395 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 396 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 397 | CODE_SIGN_STYLE = Automatic; 398 | CURRENT_PROJECT_VERSION = 1; 399 | DEVELOPMENT_ASSET_PATHS = "\"AmazingRecipes/Preview Content\""; 400 | DEVELOPMENT_TEAM = 272983AP33; 401 | ENABLE_PREVIEWS = YES; 402 | GENERATE_INFOPLIST_FILE = YES; 403 | INFOPLIST_KEY_CFBundleDisplayName = "Amazing Recipes"; 404 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 405 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 406 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 407 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 408 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 409 | LD_RUNPATH_SEARCH_PATHS = ( 410 | "$(inherited)", 411 | "@executable_path/Frameworks", 412 | ); 413 | MARKETING_VERSION = 1.0; 414 | PRODUCT_BUNDLE_IDENTIFIER = com.igorsilva.AmazingRecipes; 415 | PRODUCT_NAME = "$(TARGET_NAME)"; 416 | SWIFT_EMIT_LOC_STRINGS = YES; 417 | SWIFT_VERSION = 5.0; 418 | TARGETED_DEVICE_FAMILY = "1,2"; 419 | }; 420 | name = Release; 421 | }; 422 | /* End XCBuildConfiguration section */ 423 | 424 | /* Begin XCConfigurationList section */ 425 | 232EEA982A24497B004B0636 /* Build configuration list for PBXProject "AmazingRecipes" */ = { 426 | isa = XCConfigurationList; 427 | buildConfigurations = ( 428 | 232EEAAE2A24497F004B0636 /* Debug */, 429 | 232EEAAF2A24497F004B0636 /* Release */, 430 | ); 431 | defaultConfigurationIsVisible = 0; 432 | defaultConfigurationName = Release; 433 | }; 434 | 232EEAB02A24497F004B0636 /* Build configuration list for PBXNativeTarget "AmazingRecipes" */ = { 435 | isa = XCConfigurationList; 436 | buildConfigurations = ( 437 | 232EEAB12A24497F004B0636 /* Debug */, 438 | 232EEAB22A24497F004B0636 /* Release */, 439 | ); 440 | defaultConfigurationIsVisible = 0; 441 | defaultConfigurationName = Release; 442 | }; 443 | /* End XCConfigurationList section */ 444 | 445 | /* Begin XCVersionGroup section */ 446 | 232EEAAB2A24497F004B0636 /* AmazingRecipes.xcdatamodeld */ = { 447 | isa = XCVersionGroup; 448 | children = ( 449 | 232EEAAC2A24497F004B0636 /* AmazingRecipes.xcdatamodel */, 450 | ); 451 | currentVersion = 232EEAAC2A24497F004B0636 /* AmazingRecipes.xcdatamodel */; 452 | path = AmazingRecipes.xcdatamodeld; 453 | sourceTree = ""; 454 | versionGroupType = wrapper.xcdatamodel; 455 | }; 456 | /* End XCVersionGroup section */ 457 | }; 458 | rootObject = 232EEA952A24497B004B0636 /* Project object */; 459 | } 460 | --------------------------------------------------------------------------------