- Published on
SwiftUI-da EnvironmentObject bilan ViewModel qo'shish
- Authors
- Name
- ShoxruxC
- @iOSdasturchi
ViewModel nima va nima uchun kerak
MVVMdagi VM — bu ViewModel, va u arxitekturaning asosiy qismi hisoblanadi. Bir tomonda View (barcha UI komponentlar), ikkinchi tomonda Model (ma'lumot nuqtalari), va o'rtada esa ViewModel turadi.
ViewModel view-ga bog'langan bo'ladi va view uchun zarur barcha ma'lumotlarni saqlaydi — bu ma'lumotlar, albatta, oldingi videoda yaratgan ItemModellarimiz. Shuningdek, ViewModel ma'lumotlarni yaratish, o'qish, yangilash va o'chirish bo'yicha barcha mantiqni (logic) o'z ichiga oladi.
ListView-ga funksiyalar qo'shish (ViewModel oldidan)
Oldin, ViewModel yaratishdan avval, funksiyalarni to'g'ridan-to'g'ri ListViewga qanday qo'shish mumkinligini ko'rsatib o'tamiz — keyinroq ularni ViewModel-ga ko'chiramiz.
Swiping to'g'risida o'chirish (delete)
ForEach loop-ining pastiga .onDelete qo'shamiz:
.onDelete(perform: deleteItem)
Pastda esa shu funksiyani yaratamiz:
func deleteItem(indexSet: IndexSet) {
items.remove(atOffsets: indexSet)
}
Tortib ko'chirish (move)
Xuddi shunday, .onMove qo'shamiz:
.onMove(perform: moveItem)
Va funksiya:
func moveItem(from: IndexSet, to: Int) {
items.move(fromOffsets: from, toOffset: to)
}
Resume va Play bosib, jonli oldindan ko'rishda tekshiramiz: swipe qilib item o'chirish va edit rejimida tartibini o'zgartirish ishlaydi.
ViewModels papkasi va ListViewModel class
Endi barcha bu mantiqni ViewModelga ko'chiramiz. Sababini tushuntiraylik: ListViewdagi hamma narsa faqat UI uchun bo'lishi kerak — items massivi, deleteItem va moveItem funksiyalari esa UI bilan emas, ma'lumot bilan bog'liq. Shuning uchun bularni ajratib chiqaramiz.
Navigator-da o'ng tugmani bosib, yangi Group yaratamiz va unga ViewModels deb nom beramiz (Models va Views papkalari orasida joylashtiramiz — ViewModel ularni bog'lagani uchun o'rtada turishi mantiqli).
ViewModels papkasida yangi Swift File yaratamiz — ListViewModel deb nomlaymiz.
ListViewModel — asosiy tuzilma
import Foundation
class ListViewModel: ObservableObject {
@Published var items: [ItemModel] = []
init() {
getItems()
}
// CRUD funksiyalari
func getItems() {
let newItems: [ItemModel] = [
ItemModel(title: "This is the first title!", isCompleted: false),
ItemModel(title: "This is the second title!", isCompleted: true),
ItemModel(title: "This is the third title!", isCompleted: false)
]
items.append(contentsOf: newItems)
}
func deleteItem(indexSet: IndexSet) {
items.remove(atOffsets: indexSet)
}
func moveItem(from: IndexSet, to: Int) {
items.move(fromOffsets: from, toOffset: to)
}
func addItem(title: String) {
let newItem = ItemModel(title: title, isCompleted: false)
items.append(newItem)
}
func updateItem(item: ItemModel) {
if let index = items.firstIndex(where: { $0.id == item.id }) {
items[index] = item.updateCompletion()
}
}
}
Bir nechta muhim jihatlarni tushuntiraylik:
ObservableObject— bu class-ni kuzatib bo'ladigan qiladi, ya'ni undagi o'zgarishlar view-larni yangilaydi.@Published—itemso'zgarganda, bu o'zgaruvchini kuzatayotgan barcha view-lar qayta chiziladi.@Stateclass-larda ishlatilmaydi, faqatView-larda ishlatiladi. Class-larda buning o'rniga@Publishedishlatiladi.init()—ListViewModelyaratilgan zahotigetItems()chaqiriladi va boshlang'ich ma'lumotlar yuklanadi.
EnvironmentObject sifatida sozlash
ListViewModelni ilovaning bosh faylida — TodoListApp.swiftda — yaratamiz:
@main
struct TodoListApp: App {
@StateObject var listViewModel: ListViewModel = ListViewModel()
var body: some Scene {
WindowGroup {
NavigationView {
ListView()
}
.environmentObject(listViewModel)
}
}
}
Muhim jihatlar:
@StateObject— class-ni kuzatish uchun ishlatiladi (class yaratilganda shu property wrapper tanlanadi)..environmentObject(...)— buNavigationViewichidagi barcha view-lar shulistViewModelga kirishini ta'minlaydi.ListViewModelObservableObjectga mos kelishi kerak edi — va biz buni yuqoridagi class tuzilmasida allaqachon qildik.
ListView-ni yangilash
Endi ListView-dan keraksiz narsalarni olib tashlaymiz:
struct ListView: View {
@EnvironmentObject var listViewModel: ListViewModel
var body: some View {
List {
ForEach(listViewModel.items) { item in
ListRowView(item: item)
.onTapGesture {
withAnimation(.linear) {
listViewModel.updateItem(item: item)
}
}
}
.onDelete(perform: listViewModel.deleteItem)
.onMove(perform: listViewModel.moveItem)
}
.navigationTitle("Todo List 📝")
.navigationBarItems(
leading: EditButton(),
trailing: NavigationLink("Add", destination: AddView())
)
}
}
itemsmassivi vadeleteItem,moveItemfunksiyalari view-dan o'chirildi — ular endi ViewModel-da.@EnvironmentObjectorqalilistViewModelga kirish imkoniga ega bo'lamiz — uni qo'lda uzatishga hojat yo'q.
Preview-ni tuzatish
Preview-da EnvironmentObject mavjud emas (u faqat App.swift orqali qo'shiladi), shuning uchun preview-ni alohida ta'minlashimiz kerak:
#Preview {
NavigationView {
ListView()
}
.environmentObject(ListViewModel())
}
AddView-ni yangilash
AddView-da ham listViewModelga kirish kerak bo'ladi:
struct AddView: View {
@Environment(\.presentationMode) var presentationMode
@EnvironmentObject var listViewModel: ListViewModel
@State var textFieldText: String = ""
@State var alertTitle: String = ""
@State var showAlert: Bool = false
var body: some View {
ScrollView {
VStack {
TextField("Type something here...", text: $textFieldText)
.padding(.horizontal)
.frame(height: 55)
.background(Color.gray.brightness(0.3))
.cornerRadius(10)
Button {
saveButtonPressed()
} label: {
Text("Save".uppercased())
.foregroundColor(.white)
.font(.headline)
.frame(height: 55)
.frame(maxWidth: .infinity)
.background(Color.accentColor)
.cornerRadius(10)
}
}
.padding(14)
}
.navigationTitle("Add an Item 🖊️")
.alert(isPresented: $showAlert, content: getAlert)
}
func saveButtonPressed() {
if textIsAppropriate() {
listViewModel.addItem(title: textFieldText)
presentationMode.wrappedValue.dismiss()
}
}
func textIsAppropriate() -> Bool {
if textFieldText.count < 3 {
alertTitle = "Your new todo item must be at least 3 characters long! 😱"
showAlert.toggle()
return false
}
return true
}
func getAlert() -> Alert {
return Alert(title: Text(alertTitle))
}
}
presentationMode.wrappedValue.dismiss()— item saqlangandan so'ng, oldingi ekranga (ListView-ga) qaytadi.textIsAppropriate()— matn kamida 3 ta belgidan iborat bo'lishligini tekshiradi; aks holda alert ko'rsatiladi.
AddView preview-si
#Preview {
NavigationView {
AddView()
}
.environmentObject(ListViewModel())
}
ItemModel-ni yangilash — immutable struct va updateCompletion()
Hozirgi holda, updateItem funksiyasida biz:
items[index] = ItemModel(title: item.title, isCompleted: !item.isCompleted)
deb yozsak, yangi ID yaratiladi — bu noto'g'ri, chunki biz aslida xuddi shu itemni yangilayapmiz.
Muammoni hal qilish — idni initializer orqali uzatish
ItemModel-ni quyidagicha yangilaymiz:
struct ItemModel: Identifiable {
let id: String
let title: String
let isCompleted: Bool
// id optional — berilmasa, avtomatik UUID yaratiladi
init(id: String = UUID().uuidString, title: String, isCompleted: Bool) {
self.id = id
self.title = title
self.isCompleted = isCompleted
}
// Completion holatini teskari qilib, xuddi shu ID bilan yangi model qaytaradi
func updateCompletion() -> ItemModel {
return ItemModel(id: id, title: title, isCompleted: !isCompleted)
}
}
Muhim jihatlar:
id: String = UUID().uuidString— yangi item yaratilganda,idberilmasa, avtomatik UUID yaratiladi. Mavjud itemni yangilashda esa xuddi shuiduzatiladi.- Immutable struct — barcha o'zgaruvchilar
letbilan belgilangan, ya'ni o'zgarmas. Bu yaxshi amaliyot: modelni tasodifiy joydan o'zgartirib yuborish xavfini kamaytiradi. Modelni faqatupdateCompletion()funksiyasi orqali yangilash mumkin. updateCompletion()— mavjudidvatitleni saqlab, faqatisCompletedni teskari qiladi va yangiItemModelqaytaradi.
ViewModel-dagi updateItemni tozalash
func updateItem(item: ItemModel) {
if let index = items.firstIndex(where: { $0.id == item.id }) {
items[index] = item.updateCompletion()
}
}
Bu yerda .firstIndex(where:) — massiv ichidagi har bir elementni ko'rib chiqib, ID-si mos keladigan birinchi indexni qaytaradi. Bu index optional (Int?), shuning uchun if let bilan xavfsiz unwrap qilinadi.
CRUD funksiyalari
ViewModel-dagi to'rtta funksiyamiz — ma'lumotlar bilan ishlashning to'rtta asosiy amali deb ataladi:
// CRUD functions
// Create
func addItem(title: String) { ... }
// Read
func getItems() { ... }
// Update
func updateItem(item: ItemModel) { ... }
// Delete
func deleteItem(indexSet: IndexSet) { ... }
CRUD — Create, Read, Update, Delete. Ilovangizda qaysi ma'lumot bilan ishlasangiz (itemlar, foydalanuvchilar, xabarlar va boshqalar), ular uchun doim shu to'rtta turdagi funksiya bo'ladi. Shuning uchun bu to'rttasini yaxshi tushunib olish — har qanday ilova uchun mustahkam zamin yaratadi.
Natija
Endi ilovamiz to'liq ishlaydi:
- Item-larni swipe qilib o'chirish
- Edit rejimida tartibini o'zgartirish
- Yangi item qo'shish (kamida 3 belgi tekshiruvi bilan)
- Item-ga bosib, tugallangan/tugallanmagan holatini animatsiya bilan almashtirish
Barcha mantiq ListViewModel-da, barcha UI ListView va AddView-da, ma'lumot tuzilmasi esa ItemModel-da — bu MVVM arxitekturasi.