Model-View-ViewModel
MVVM separates UI (View) from business logic (ViewModel) and data (Model).
The ViewModel is an ObservableObject the View observes.
Syntax:
class VM: ObservableObject { @Published var state }@StateObject private var vm = VM()List(vm.items) { ... }ObservableObject:A class that notifies its observers when its state changes.@Published:A property wrapper that publishes changes to a value, notifying observers.@StateObject:A property wrapper that creates a new instance of a class, making it a source of truth for a view.
Example
Demo.swift
ViewModel.swift
ContentView.swift
App.swift
import SwiftUI
struct MVVMBasicView: View {
@StateObject private var vm = TodoViewModel()
var body: some View {
List(vm.todos) { t in Text(t.title) }
.onAppear { vm.load() }
}
}
This example shows the MVVM flow: a ViewModel exposes state, the View observes it, and triggers loading on appear.
REMOVE ADS
Sample App: Notes (MVVM)
Example
Demo.swift
ViewModel.swift
ContentView.swift
App.swift
import SwiftUI
struct NotesMVVMView: View {
@StateObject private var vm = NotesViewModel()
var body: some View {
List(vm.notes) { n in
VStack(alignment: .leading) {
Text(n.title).font(.headline)
Text(n.body).font(.subheadline)
}
}
.onAppear { vm.loadMock() }
}
}
This example creates a simple Notes model and ViewModel, then renders a list and loads mock data when the view appears.
Next, connect data to a server in Networking or persist locally in Core Data.
App Group
Use an App Group to share small values (like counts, flags, timestamps) between your app and its Widget.
The hook below writes notesCount whenever the ViewModel’s notes change.
How to use it:
- Enable the same App Group on both the app target and the widget target (e.g.,
group.com.example.notes). - Keep
updateSharedNotesCountwriting to that group ID; thedidSetobserver writes the new count on every change. - Read the value from the widget using the same
suiteName(see snippet below).
App Group hook (Widget Count)
Example
NotesViewModel.swift
import Combine
func updateSharedNotesCount(_ count: Int) {
let defaults = UserDefaults(suiteName: "group.com.example.notes")
defaults?.set(count, forKey: "notesCount")
}
class NotesViewModel: ObservableObject {
@Published var notes: [Note] = [] {
didSet { updateSharedNotesCount(notes.count) }
}
func add(title: String, body: String) {
notes.append(.init(id: UUID(), title: title, body: body))
}
func delete(at offsets: IndexSet) {
notes.remove(atOffsets: offsets)
}
}
Example
WidgetRead.swift
import SwiftUI
// Option A: Direct read via UserDefaults (e.g., from a TimelineProvider)
let defaults = UserDefaults(suiteName: "group.com.example.notes")
let count = defaults?.integer(forKey: "notesCount") ?? 0
// Option B: @AppStorage with a custom store (usable in a Widget view)
struct WidgetCountView: View {
@AppStorage("notesCount", store: UserDefaults(suiteName: "group.com.example.notes"))
private var notesCount: Int = 0
var body: some View {
Text("Notes: \(notesCount)")
}
}
Leave a Reply