MVVM arxitekturasi


ULASHISH

SwiftUI MVVM

State va amallarni SwiftUI view’laringiz kuzatadigan ViewModel’ga joylashtirib, UI’ni biznes mantiqdan ajrating.


Model-View-ViewModel

MVVM UI’ni (View) biznes mantiq (ViewModel) va ma’lumotlardan (Model) ajratadi.

ViewModel — View kuzatadigan ObservableObject.

Sintaksis:

  • class VM: ObservableObject { @Published var state }
  • @StateObject private var vm = VM()
  • List(vm.items) { ... }
  • ObservableObject: State’i o‘zgarganda o‘z kuzatuvchilarini xabardor qiladigan sinf.
  • @Published: Qiymatdagi o‘zgarishlarni e’lon qilib (publish), kuzatuvchilarni xabardor qiladigan property wrapper.
  • @StateObject: Sinfning yangi nusxasini yaratib, uni view uchun haqiqat manbai (source of truth) qiladigan property wrapper.

Misol

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() }
  }
}
import Foundation
import Combine

struct Todo: Identifiable { let id: Int; let title: String }

final class TodoViewModel: ObservableObject {
  @Published var todos: [Todo] = []
  func load() { todos = [Todo(id: 1, title: "Learn MVVM")] }
}
import SwiftUI

struct ContentView: View {
  var body: some View { MVVMBasicView() }
}
import SwiftUI

@main
struct MyApp: App {
  var body: some Scene {
    WindowGroup { ContentView() }
  }
}

Ushbu misolda MVVM oqimi ko‘rsatilgan: ViewModel state’ni taqdim etadi, View uni kuzatadi va paydo bo‘lganda yuklashni ishga tushiradi.



Namunaviy ilova: Notes (MVVM)

Misol

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() }
  }
}
import Foundation
import Combine

struct Note: Identifiable { let id: UUID; var title: String; var body: String }

final class NotesViewModel: ObservableObject {
  @Published var notes: [Note] = []
  func loadMock() {
    notes = [
      Note(id: UUID(), title: "First", body: "Welcome to Notes"),
      Note(id: UUID(), title: "Second", body: "Try editing...")
    ]
  }
}
import SwiftUI

struct ContentView: View {
  var body: some View { NotesMVVMView() }
}
import SwiftUI

@main
struct MyApp: App {
  var body: some Scene {
    WindowGroup { ContentView() }
  }
}

Ushbu misol oddiy Notes modeli va ViewModel’ini yaratadi, so‘ng ro‘yxatni render qiladi va view paydo bo‘lganda soxta (mock) ma’lumotlarni yuklaydi.

Keyin Tarmoq bilan ishlash darsida ma’lumotlarni serverga ulang yoki Core Data darsida ularni lokal saqlang.


App Group

Kichik qiymatlarni (masalan, sonlar, bayroqlar, vaqt belgilari) ilovangiz va uning vidjeti o‘rtasida ulashish uchun App Group dan foydalaning.

Quyidagi hook ViewModel’dagi notes har safar o‘zgarganda notesCount ni yozadi.

Undan qanday foydalanish kerak:

  • Xuddi shu App Group’ni ilova target’ida ham, vidjet target’ida ham yoqing (masalan, group.com.example.notes).
  • updateSharedNotesCount o‘sha group ID’ga yozishda davom etsin; didSet kuzatuvchisi har bir o‘zgarishda yangi sonni yozadi.
  • Qiymatni vidjetda xuddi shu suiteName yordamida o‘qing (quyidagi kod parchasiga qarang).

App Group hook’i (vidjetdagi son)

Misol

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)
  }
}

Misol

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)")
  }
}

Maslahat: View’lar sodda va testlanadigan bo‘lib qolishi uchun bog‘liqliklarni (masalan, tarmoq klientlarini) ViewModel’ingizga uzating (inject).



W3Schools Pathfinder

Yutuqlaringizni kuzating – bu bepul!