Post

Replies

Boosts

Views

Activity

Computed properties and performance issues
Hello everyone, I need your help with a performance issue I’m encountering in my app. I’m learning app development in SwiftUI, and I built a simple budgeting app based on the 50/30/20 rule, which consists in dividing your expenses in “Needs”’, “Wants”, and “Savings and debts”. The main objects of my app are months, and transactions, each month containing an array of associated transactions. My app shows graphs for the current month. For example, it can show a pie chart representing the expenses of different categories. Now, in order to create these graphs, I have to compute some numbers from the month. For example, I have to retrieve the percent of the budget that has been spent. For now, I did this by adding various computed variables to my MonthBudget model, which is the model containing the transactions. This looks something like this: @Model final class MonthBudget { @Relationship(deleteRule: .cascade) var transactions: [Transaction]? = [] let identifier: UUID = UUID() var monthlyBudget: Double = 0 var needsBudgetRepartition: Double = 50 var wantsBudgetRepartition: Double = 30 var savingsDebtsBudgetRepartition: Double = 20 // Definition of other variables... } extension MonthBudget { @Transient var totalSpent: Double { spentNeedsBudget + spentWantsBudget + spentSavingsDebtsBudget } @Transient var remaining: Double { totalAvailableFunds + totalSpent } // Negative amount spent for a specific category (ex: -250) @Transient var spentNeedsBudget: Double { transactions!.filter { $0.category == .needs && $0.amount < 0 }.reduce(0, { $0 + $1.amount }) } @Transient var spentWantsBudget: Double { transactions!.filter { $0.category == .wants && $0.amount < 0 }.reduce(0, { $0 + $1.amount }) } @Transient var spentSavingsDebtsBudget: Double { transactions!.filter { $0.category == .savingsDebts && $0.amount < 0 }.reduce(0, { $0 + $1.amount }) } // Definition of multiple other computed properties... } Now, this approach worked fine when I only had one or two months with a few transactions in memory. But now that I’m actually using the app, I see serious performance issues (most notably hangs/freezes) whenever I am trying to display a graph. I used “Instruments” to inspect what was going wrong with my app, and I saw that the hangs happened mostly when trying to get the value of these variables, meaning that the actual computing was taking too long. I’m therefore trying to find a more efficient way of getting these informations (totalSpent, spentNeedsBudget, etc.). Is there a common practice that would help with these performance issues? I thought about caching the last computed property (and persist it using SwiftData), and using a function that would re-compute and persist all of these properties whenever a transaction is added or removed. But this has multiple cons: I’d have to call the function that re-computes the properties and stores them in memory each time I delete/add a transaction, losing the very benefit of using computed properties This would potentially be a bad idea: if there’s some sort of bug with SwiftData and one or multiple transactions are added or deleted without the user actually doing anything, there could be a mismatch between the persisted amount/value and the actual value. Did any of you face the same issue as me, and if so, how did you solve it? Any idea is appreciated! Thanks for reading so far, Louis.
1
0
784
Jan ’24
Trouble with Persisting One-to-Many Relationship Data in SwiftData, What am I Missing?
Hi everyone, I'm new to programming and I've been experimenting with Apple's SwiftData. I've run into an issue I can't seem to resolve. I'm creating a personal relationship manager app where I have a Relation model and an Interaction model. Relation has a one-to-many relationship with Interaction. I'm using SwiftData's @Model and @Relationship property wrappers to define these models and their relationship. I've taken inspiration from Apple's sample code, that can be found here: Adopting SwiftData for a Core Data app (WWDC23 Session: "Migrate to SwiftData") The relevant parts of the models look something like this: @Model final class Relation { ... @Relationship(.cascade, inverse: \Interaction.relation) var interactions: [Interaction] = [] ... } @Model final class Interaction { ... var relation: Relation? ... } In my SwiftUI view, I'm adding a new Interaction to a Relation like this: private func AddItem() { withAnimation { let newInteraction = Interaction(...) modelContext.insert(newInteraction) newInteraction.relation = relation relation.interactions.append(newInteraction) } } When I add a new Interaction like this, everything seems to work fine during that app session. I can see the new Interaction in my app's UI. But when I quit the app and relaunch it, the new Interaction is gone. It's as if it was never saved. I've double-checked my code and as far as I can tell, I'm using SwiftData correctly. My usage aligns with the sample code provided by Apple, and I'm not getting any errors or warnings. I think that this issue is not related to SwiftData being in Beta, because Apple's sample code works perfectly fine. I have a few questions: Is there something I'm missing about how to properly save models using SwiftData? Is there a specific step or method I need to call to persist the changes to the Relation and Interaction objects? Is there a way to debug what's going wrong when SwiftData attempts to save these changes? Any help would be greatly appreciated. Thank you in advance! Louis
2
0
2.1k
Jul ’23
Border-to-Border Horizontal Scrollview inside a List
Hi everyone! I'm facing a small problem with SwiftUI. In my app, I'd like to use a Horizontal ScrollView inside a List. The following screenshot pictures what I have now: As you can see, "Louis Martin" is cropped because of the padding that is automatically applied to the list. I thought about using a Vertical Scrollview that would contain the horizontal one and a ForEach to replace the list, but I'm using List features such as swipe actions. So I can't really get rid of the list. I've also tried to use listRowInsets and negative padding modifiers, without any success. Does someone have a solution? Thanks in advance, Louis.
1
2
698
Jul ’23
Strange behavior with .append()
Hello everyone, I'm new to SwiftUI, and to coding in general, and I'm currently trying to build an app using SwiftUI. I have a problem that I was not able to solve, even after a careful review of my code, and with the help of GPT-4. So, the goal of my app is to remind the user of interacting with the persons that they're closed to. Therefore, my app mainly uses two structs, called Relation (representing a person), and Interaction (representing one interaction with a person). I've built a sheet called "NewInteractionSheet", whose goal is to add a new Interaction to one of the relations' array of interactions. This sheet works perfectly well when it comes to adding an interaction. However, for some reason, it only works once. Why does it do that? That's what I'm trying to figure out. Here's a part of the code of "NewInteractionSheet.swift": import PhotosUI import CoreLocation import MapKit struct NewInteractionSheet: View { @Binding var isPresentingNewInteractionView: Bool @Binding var relations: [Relation] @State private var newInteraction = Interaction.emptyInteraction @State private var relation: Relation = Relation.emptyRelation @State private var isPresentingLocationPicker: Bool = false var body: some View { NavigationView { Form { Section("You interacted with...") { RelationPicker(relations: $relations, relation: $relation) } Section("Interaction details") { InteractionDatePicker(dateToSet: $newInteraction.date) TypePicker(typeToSet: $newInteraction.type) DurationPicker(shouldShow: newInteraction.type.hasDuration, hoursToSet: $newInteraction.durationHours, minutesToSet: $newInteraction.durationMinutes) SummaryTextField(summaryToSet: $newInteraction.summary) LocationPicker(shouldShow: newInteraction.type.hasLocation, coordinatesToSet: $newInteraction.location.coordinates, locationNameToSet: $newInteraction.location.name, isPresentingLocationPicker: $isPresentingLocationPicker) InteractionPhotosPicker(images: $newInteraction.pictures) } } .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Dismiss") { isPresentingNewInteractionView = false } } ToolbarItem(placement: .confirmationAction) { Button("Add") { if let index = relations.firstIndex(where: { $0.id == relation.id }) { print("\nBefore appending to relations") print(relations[index]) print(newInteraction) relations[index].interactions.append(newInteraction) print("\nAfter having appended to relations") print(relations[index]) print(newInteraction) } isPresentingNewInteractionView = false //AJOUTER LA PLANIFICATION D'UNE NOTIFICATION } } } .navigationTitle("New interaction") } } } As you can see in the code, I've included three "print" instructions to help me debug this. And when I'm trying to add two interactions, here's what's printing in the console: Before appending to relations Relation(id: EA18AAD4-E576-49A9-90BF-CC58C5000ECE, firstName: "Johanna", lastName: "Duby", photo: nil, interactions: [], contactFrequency: 1814400.0, birthday: Optional(2023-06-15 14:34:40 +0000), notes: "", theme: Relations.Theme.blue, reminders: nil) Interaction(id: 106CD832-1949-4800-AC75-E21B8890E580, date: 2023-06-15 14:34:43 +0000, type: Relations.InteractionType.audioCall, durationHours: 0, durationMinutes: 0, summary: "", location: Relations.Location(name: "", coordinates: nil), pictures: []) After having appended to relations Relation(id: EA18AAD4-E576-49A9-90BF-CC58C5000ECE, firstName: "Johanna", lastName: "Duby", photo: nil, interactions: [Relations.Interaction(id: 106CD832-1949-4800-AC75-E21B8890E580, date: 2023-06-15 14:34:43 +0000, type: Relations.InteractionType.audioCall, durationHours: 0, durationMinutes: 0, summary: "", location: Relations.Location(name: "", coordinates: nil), pictures: [])], contactFrequency: 1814400.0, birthday: Optional(2023-06-15 14:34:40 +0000), notes: "", theme: Relations.Theme.blue, reminders: nil) Interaction(id: 106CD832-1949-4800-AC75-E21B8890E580, date: 2023-06-15 14:34:43 +0000, type: Relations.InteractionType.audioCall, durationHours: 0, durationMinutes: 0, summary: "", location: Relations.Location(name: "", coordinates: nil), pictures: []) Before appending to relations Relation(id: 8D3D2012-D8A2-4092-B1A9-D476F7E05B9A, firstName: "Nastassja", lastName: "Ferrari", photo: nil, interactions: [], contactFrequency: 1209600.0, birthday: nil, notes: "", theme: Relations.Theme.green, reminders: nil) Interaction(id: 5C4EE2E1-7D2D-4E32-BC00-FCA781EC8C20, date: 2023-06-15 14:34:49 +0000, type: Relations.InteractionType.audioCall, durationHours: 0, durationMinutes: 0, summary: "", location: Relations.Location(name: "", coordinates: nil), pictures: []) After having appended to relations Relation(id: 8D3D2012-D8A2-4092-B1A9-D476F7E05B9A, firstName: "Nastassja", lastName: "Ferrari", photo: nil, interactions: [], contactFrequency: 1209600.0, birthday: nil, notes: "", theme: Relations.Theme.green, reminders: nil) Interaction(id: 5C4EE2E1-7D2D-4E32-BC00-FCA781EC8C20, date: 2023-06-15 14:34:49 +0000, type: Relations.InteractionType.audioCall, durationHours: 0, durationMinutes: 0, summary: "", location: Relations.Location(name: "", coordinates: nil), pictures: []) You don't need to read this in detail, but just see that: the first time, the interaction is correctly appended to the relations array of the interaction the second time though, for some reason, it's not appended. I'd be really grateful for any guidance that could shed some light on this perplexing issue! I'm willing to share more code if necessary, such as the parent view from which this sheet is called for example. Any insight would be extremely valuable. Thank you so much for your time and help! PS: The iPhone I'm using to run this is an iPhone 13 mini, running iOS 16.5
2
0
569
Jun ’23