├── 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 |
--------------------------------------------------------------------------------