From 9088301b15ccb864a6a2f69686c4cbcb40e7036a Mon Sep 17 00:00:00 2001 From: Vicnet <35202538+VincentMeilinger@users.noreply.github.com> Date: Sun, 17 Sep 2023 16:37:08 +0200 Subject: [PATCH] Better image caching --- .../ViewModels/MainViewModel.swift | 16 +++++-- .../Views/CategoryDetailView.swift | 2 +- .../Views/RecipeDetailView.swift | 47 ++++++++++--------- 3 files changed, 37 insertions(+), 28 deletions(-) diff --git a/Nextcloud Cookbook iOS Client/ViewModels/MainViewModel.swift b/Nextcloud Cookbook iOS Client/ViewModels/MainViewModel.swift index 7da989b..ce579f3 100644 --- a/Nextcloud Cookbook iOS Client/ViewModels/MainViewModel.swift +++ b/Nextcloud Cookbook iOS Client/ViewModels/MainViewModel.swift @@ -77,7 +77,7 @@ import UIKit /// - needsUpdate: Determines wether the image should be loaded directly from the server, or if it should be loaded from cache/store first. /// - Returns: The image if found locally or on the server, otherwise nil. func loadImage(recipeId: Int, full: Bool, needsUpdate: Bool = false) async -> UIImage? { - print("loadImage(recipeId: \(recipeId), full: \(full))") + print("loadImage(recipeId: \(recipeId), full: \(full), needsUpdate: \(needsUpdate)") // If the image needs an update, request it from the server and overwrite the stored image if needsUpdate { if let data = await imageDataFromServer(recipeId: recipeId, full: full) { @@ -88,17 +88,23 @@ import UIKit } } // Try to load image from cache - print("Attempting to load image from local storage ...") - if let image = imageFromCache(recipeId: recipeId, full: full) { - return image + print("Attempting to load image from cache ...") + if imageCache[recipeId] != nil { + print("Image found in cache.") + return imageFromCache(recipeId: recipeId, full: full) } // Try to load from store - print("Attempting to load image from server ...") + print("Attempting to load image from local storage ...") if let image = await imageFromStore(recipeId: recipeId, full: full) { + print("Image found in local storage.") + imageToCache(image: image, recipeId: recipeId, full: full) return image } // Try to load from the server. Store if successfull. + print("Attempting to load image from server ...") if let data = await imageDataFromServer(recipeId: recipeId, full: full) { + print("Image data received.") + imageCache[recipeId] = RecipeImage() // Create empty RecipeImage for each recipe even if no image found, so that further server requests are only sent if explicitly requested. guard let image = UIImage(data: data) else { return nil } await dataStore.save(data: data.base64EncodedString(), toPath: localImagePath(recipeId, full)) imageToCache(image: image, recipeId: recipeId, full: full) diff --git a/Nextcloud Cookbook iOS Client/Views/CategoryDetailView.swift b/Nextcloud Cookbook iOS Client/Views/CategoryDetailView.swift index 7321fb0..8ae2be7 100644 --- a/Nextcloud Cookbook iOS Client/Views/CategoryDetailView.swift +++ b/Nextcloud Cookbook iOS Client/Views/CategoryDetailView.swift @@ -14,7 +14,7 @@ struct RecipeBookView: View { @State var categoryName: String @ObservedObject var viewModel: MainViewModel var body: some View { - ScrollView { + ScrollView(showsIndicators: false) { LazyVStack { if let recipes = viewModel.recipes[categoryName] { ForEach(recipes, id: \.recipe_id) { recipe in diff --git a/Nextcloud Cookbook iOS Client/Views/RecipeDetailView.swift b/Nextcloud Cookbook iOS Client/Views/RecipeDetailView.swift index 0043c9e..6c40f91 100644 --- a/Nextcloud Cookbook iOS Client/Views/RecipeDetailView.swift +++ b/Nextcloud Cookbook iOS Client/Views/RecipeDetailView.swift @@ -45,14 +45,16 @@ struct RecipeDetailView: View { Divider() RecipeYieldSection(recipeDetail: recipeDetail) RecipeDurationSection(recipeDetail: recipeDetail) - if(!recipeDetail.recipeIngredient.isEmpty) { - RecipeIngredientSection(recipeDetail: recipeDetail) - } - if(!recipeDetail.tool.isEmpty) { - RecipeToolSection(recipeDetail: recipeDetail) - } - if(!recipeDetail.recipeInstructions.isEmpty) { - RecipeInstructionSection(recipeDetail: recipeDetail) + LazyVGrid(columns: [GridItem(.adaptive(minimum: 400), alignment: .top)]) { + if(!recipeDetail.recipeIngredient.isEmpty) { + RecipeIngredientSection(recipeDetail: recipeDetail) + } + if(!recipeDetail.tool.isEmpty) { + RecipeToolSection(recipeDetail: recipeDetail) + } + if(!recipeDetail.recipeInstructions.isEmpty) { + RecipeInstructionSection(recipeDetail: recipeDetail) + } } }.padding(.horizontal, 5) @@ -130,19 +132,20 @@ struct RecipeIngredientSection: View { @State var recipeDetail: RecipeDetail var body: some View { VStack(alignment: .leading) { + Divider() HStack { SecondaryLabel(text: "Ingredients") Spacer() } ForEach(recipeDetail.recipeIngredient, id: \.self) { ingredient in - Text("\u{2022} \(ingredient)") - .multilineTextAlignment(.leading) - .padding(4) + HStack(alignment: .top) { + Text("\u{2022}") + Text("\(ingredient)") + .multilineTextAlignment(.leading) + } + .padding(4) } }.padding() - .frame(maxWidth: .infinity) - .background(Color("accent")) - .clipShape(RoundedRectangle(cornerRadius: 10)) } } @@ -150,19 +153,20 @@ struct RecipeToolSection: View { @State var recipeDetail: RecipeDetail var body: some View { VStack(alignment: .leading) { + Divider() HStack { SecondaryLabel(text: "Tools") Spacer() } ForEach(recipeDetail.tool, id: \.self) { tool in - Text("\u{2022} \(tool)") - .multilineTextAlignment(.leading) - .padding(4) + HStack(alignment: .top) { + Text("\u{2022}") + Text("\(tool)") + .multilineTextAlignment(.leading) + } + .padding(4) } }.padding() - .frame(maxWidth: .infinity) - .background(Color("accent")) - .clipShape(RoundedRectangle(cornerRadius: 10)) } } @@ -170,6 +174,7 @@ struct RecipeInstructionSection: View { @State var recipeDetail: RecipeDetail var body: some View { VStack(alignment: .leading) { + Divider() HStack { SecondaryLabel(text: "Instructions") Spacer() @@ -181,8 +186,6 @@ struct RecipeInstructionSection: View { }.padding(4) } }.padding() - .background(Color("accent")) - .clipShape(RoundedRectangle(cornerRadius: 10)) } }