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.

viewmodel

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 @State values 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
Hikmet Tüfekçi
Builds iOS apps and writes about the places in between. About

Comments

Leave a comment

Comments appear after approval. No email needed.

You might also like

Software posts