SwiftUI Refactoring Strategy and ViewModel Use
This post shares my own experience and gives a roadmap for refactoring strategy, ViewModel structure, and managing multiple sheets.

Anyone who works with SwiftUI knows this, everything is easy and fast at first. But as the project grows, your ContentView.swift file can reach 1000, 2000, even 3000 lines. Even if the code works, a small update starts to feel like a headache. I also breathed easier when I made my SwiftUI projects modular at this point. In this post, I am sharing a roadmap that can guide you on refactoring strategy, ViewModel structure, and managing multiple sheets, based on my own experience.
What Did I Go Through?
I had written almost all the features in my project inside the ContentView.swift file:
- List view
- Receipt cards
- OCR operations
- PDF processing
- Notifications
- Page transitions
As a result:
- The code went past 2500 lines.
- Xcode Preview kept crashing.
- Dependencies between
@Statevalues got out of control. - Every new feature was written with the fear of “what if I break something”.
The Strategy I Developed
** My Prompt Template:**
While I was doing this process with help from artificial intelligence, Cursor, I developed this prompt template:
“Apply a refactoring strategy. Prevent state mess. Manage multiple sheets through the ViewModel with an active page type.”
This sentence became my guide.
Step by Step Recovery Plan
1. Split Each View Into Its Own File
I moved all my screens into separate .swift files under the Views/ folder:
- ReceiptCardView.swift
- ReceiptDetailView.swift
- ReceiptEditView.swift
- SplashView.swift
- DashboardView.swift
This made it clear what each component does.
2. Move Business Logic to the ViewModel
I moved all @State variables and functions that do not belong to the view into an ObservableObject class:
class ReceiptViewModel: ObservableObject {
@Published var receipts: [Receipt] = []
func add(_ receipt: Receipt) { ... }
func delete(_ receipt: Receipt) { ... }
}
This greatly improved code readability and testability.
3. Manage Sheet Usage with an Enum
There were multiple .sheet views in the project. I managed all of them through a single enum in the ViewModel:
enum ActiveSheet: Identifiable {
case detail(Receipt), edit(Receipt)
var id: String { ... }
}
In the view, it was enough to write this:
.sheet(item: $viewModel.activeSheet) { sheet in
switch sheet {
case .detail(let receipt): ReceiptDetailView(receipt: receipt)
case .edit(let receipt): ReceiptEditView(receipt: receipt)
}
}
4. Separate Services Into the Services/ Folder
I moved functions like OCR processing and PDF data creation into their own classes:
- PDFService.swift
- TextRecognitionService.swift
This made it easier to move the code into other projects later.
5. Standardize the File Structure
I changed the app folder structure to this:
HikmetApp/
├── Views/
├── ViewModels/
├── Models/
├── Services/
├── Utils/
This structure makes finding your way around much easier in big projects.
Result: Comfortable Code, Faster Development
After these changes, this happened:
- ContentView.swift is now only 100 to 150 lines
- I write new features with less fear
- Xcode Preview works smoothly
- I catch bugs much more easily



Comments