Document-Based App with MVVM

Dear all,

I made an app for computing Finite Element Analysis of electric motors. I (think I) managed to follow the MVVM principle by not exposing the model to the views. Now, my goal is to be able to use documents, each representing a different motor. I want to have my files saved on iCloud, and I want to be able to read plain text from it, so some other code (i.e. python) can create new configurations, even though this app is made for building, graphically.

Before trying to work with FileDocument, my class ViewModel: ObservableObject had properties with Published, like @Published var staOD = 80.0, and I would have views with TextFields to change these values.

Now, I’m trying to blend in FileDocument, and I’m lost. I don’t know how I should work with my data. Under the “Separation of Concerns”, I guessed that:

  • ViewModel: Should handle computations, updates, and application logic.
  • Document: Should focus on data persistence and encapsulate data to be saved/loaded as a document.

My ViewModel looks a bit strange to me, and I’m not sure I’m updating it the right way. I have around 100 parameters, I’m afraid I’m updating these parameters too often and unnecessarily every parameter at the same time, even when only one value is changed in the document.

What I’m asking:

  • Clarifications on how to work with FileDocument in my case of MVVM (I’m open to change the entire workflow, my main knowledge is on the Model built in Swift, but not SwiftUI)
  • Why isn’t the computed area on the DocumentView at the right value when I open a document? I would like to open documents and have it in the “right” state. In reality, I’m computing an image of the electric motor, and it would be nice to open the document and see the “real” image, and not a dummy image before I can validate the geometry.
  • I have these warnings popping every time I open a document and that scares me, especially because I want ideally to use swift 6 in the coming future, with concurrency the right way.
    • Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.

Thanks a lot for your help,

Guillaume


I made an example/simplified code that has all the logic. I can't show the entire code in this prompt due to space limitation. Therefore, I put everything (184 lines) in a single Swift file for you to download. You can just create a Multiplatform Document App. Remove all files except the ...App file, in which you can paste the content of the shared swift file. Run on iPhone simulator.

Development environment: Xcode 16, macOS 15

Run-time configuration: iOS 18, macOS 15

  • Open the app, click on "New motor"
  • You will see "Computed area: 3'063..."
  • Click on "Geometry", change "Stator OD" to 60 instead of 80.
  • Click on "Save" button, now Computed area is 863...
  • Click on "Cancel" button, and reopen the same document
  • Problem: area is again 3'063 while when you open Geometry, you see that "Stator OD" is rightfully 60.
Answered by DTS Engineer in 808285022

I won't comment the MVVM part because it is an architecture level choice, which people may have different opinions depending on their experience and their angle to address the problem.

I can explain why the computed area isn't updated though – It is because onAppear is not triggered when you open the document again. As a result, viewModel.updateArea() is not triggered either, and so computedArea is not updated. You can confirm this behavior by putting a print in the closure of onAppear and observing that it only prints the first time you open a document.

The behavior is as designed. Conceptually, a view's onAppear method is only triggered when the view is newly created (and inserted to the view hierarchy). SwiftUI doesn't create a new document view every time you open a document; instead, it uses the new document to refresh the existing document view. This may be a bit mind-bending – If you feel so, I'd suggest that you go through Demystify SwiftUI to see how SwiftUI manages the life cycle of a view.

Regarding FileDocument (or ReferenceFileDocument), I see a document that conforms the protocol a data controller, which wraps the data and also manages the data access (reading and writing). The protocol doesn't update SwiftUI directly in any way though, and so you need to manage the UI update with your own code.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Accepted Answer

I won't comment the MVVM part because it is an architecture level choice, which people may have different opinions depending on their experience and their angle to address the problem.

I can explain why the computed area isn't updated though – It is because onAppear is not triggered when you open the document again. As a result, viewModel.updateArea() is not triggered either, and so computedArea is not updated. You can confirm this behavior by putting a print in the closure of onAppear and observing that it only prints the first time you open a document.

The behavior is as designed. Conceptually, a view's onAppear method is only triggered when the view is newly created (and inserted to the view hierarchy). SwiftUI doesn't create a new document view every time you open a document; instead, it uses the new document to refresh the existing document view. This may be a bit mind-bending – If you feel so, I'd suggest that you go through Demystify SwiftUI to see how SwiftUI manages the life cycle of a view.

Regarding FileDocument (or ReferenceFileDocument), I see a document that conforms the protocol a data controller, which wraps the data and also manages the data access (reading and writing). The protocol doesn't update SwiftUI directly in any way though, and so you need to manage the UI update with your own code.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Document-Based App with MVVM
 
 
Q