- Published on
MVVM arxitekturasi va boshqa xususiyatlarni yakuniy ko'rib chiqish
- Authors
- Name
- ShoxruxC
- @iOSdasturchi
Ilovaga umumiy nazar
Ushbu video β kodlash emas, balki biz qurib yakunlagan ilovaning yakuniy sharhi.
Map layer
LocationsView-dagi xarita nisbatan murakkab tuzilmaga ega:
Map(coordinateRegion: $vm.mapRegion,
annotationItems: vm.locations) { location in
MapAnnotation(coordinate: location.coordinates) {
LocationMapAnnotationView()
.scaleEffect(vm.mapLocation == location ? 1 : 0.7)
.shadow(radius: 10)
.onTapGesture {
vm.showNextLocation(location: location)
}
}
}
$vm.mapRegionβ binding orqali ulangan: ViewModel-damapRegiono'zgarganda, xarita avtomatik yangilanadi- Custom annotation β standart pin o'rniga
LocationMapAnnotationViewishlatiladi β ilovaning o'ziga xos stil pin-i .scaleEffectβ tanlangan location-ning pin-i kattaroq ko'rinadi, qolganlarniki kichikroq.onTapGestureβ xaritadagi boshqa pin-larga bosib, pastki preview va xaritani o'sha location-ga o'tkazish imkoni
Location almashganda bir vaqtda bir nechta narsa sodir bo'ladi: pastki preview animatsiya bilan o'zgaradi, sarlavha yangilanadi, xarita siljiydi, pin kattayyadi β bu birgalikda juda yaxshi UI beradi.
Joylashuvlar ro'yxati
Header-dagi ro'yxat oddiy if ifodasi orqali ko'rsatiladi:
if vm.showLocationsList {
LocationsListView()
.transition(.move(edge: .top).animation(.easeInOut))
}
Listkomponenti ishlatilgani uchun, itemlar orasida ajratuvchi chiziq (divider) avtomatik paydo bo'ladiListβ scrollable: 5 ta location ham, 300 ta location ham ishlaydi- Yangi location qo'shish uchun faqat
LocationsDataService-dagi massivga element qo'shish kifoya
Yangi location qo'shish
LocationsDataService.swift-dagi locations massiviga yangi element qo'shasiz:
static let locations: [Location] = [
Location(
name: "Colosseum",
cityName: "Rome",
coordinates: CLLocationCoordinate2D(latitude: 41.8902, longitude: 12.4922),
description: "...",
imageNames: ["rome-colosseum-1", "rome-colosseum-2"],
link: "https://en.wikipedia.org/wiki/Colosseum"
),
// ... yangi location
]
Muhim:
imageNamesqiymatidagi rasm nomlari Assets papkasidagi rasm nomlari bilan aynan mos kelishi kerak.
LocationPreviewStack β ZStack hiylasi
ZStack {
ForEach(vm.locations) { location in
if vm.mapLocation == location {
LocationPreviewView(location: location)
.transition(
.asymmetric(
insertion: .move(edge: .trailing),
removal: .move(edge: .leading)
)
)
}
}
}
Bu kod β ko'rinishidan oddiy, lekin aslida aqlli:
ZStackbarcha locationlar uchunLocationPreviewViewyaratishga tayyor, lekinifsharti tufayli faqat joriy location-niki ko'rsatiladi- Asymmetric transition β kirish o'ngdan, chiqish chapdan: "keyingi" tuyg'usini beradi
- Bitta qator transition kodi β katta vizual ta'sir
Transition kuchini ko'rsatish uchun (tajriba sifatida):
// Scale transition
.transition(.scale.animation(.easeInOut))
// Opacity transition
.transition(.opacity.animation(.easeInOut))
Bu misollar transition-lar qanchalik kuchli ekanligini ko'rsatadi β bitta qator kodni o'zgartirish butun animatsiya xulqini o'zgartiradi.
View body-ni o'qilishi oson saqlash
LocationsView-ning body-si:
var body: some View {
ZStack {
mapLayer
contentLayer
}
.sheet(item: $vm.sheetLocation) { location in
LocationDetailView(location: location)
}
}
private var contentLayer: some View {
VStack(spacing: 0) {
header
Spacer()
locationsPreviewStack
}
}
Bu muhim amaliyot: murakkab view-ni alohida computed variable-larga bo'lish β mapLayer, contentLayer, header, locationsPreviewStack va boshqalar. Natijada:
bodyqisqa va tushunarli- Boshqa dasturchi (yoki bir hafta o'tib o'zingiz) kodni tezda tushunadi
- Xato topish osonlashadi
MVVM yakuniy sharhi
Model β Location
struct Location: Identifiable, Equatable {
let id: String
let name: String
let cityName: String
let coordinates: CLLocationCoordinate2D
let description: String
let imageNames: [String]
let link: String
// Equatable β id orqali tenglikni belgilaydi
static func == (lhs: Location, rhs: Location) -> Bool {
lhs.id == rhs.id
}
}
IdentifiableβForEach-da ishlatish uchunEquatableβif vm.mapLocation == locationkabi tekshirishlar uchun,idasosida solishtirish
ViewModel β LocationsViewModel
class LocationsViewModel: ObservableObject {
@Published var locations: [Location]
@Published var mapLocation: Location
@Published var mapRegion: MKCoordinateRegion
@Published var showLocationsList: Bool = false
@Published var sheetLocation: Location? = nil
let mapSpan = MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
init() {
let locations = LocationsDataService.locations
self.locations = locations
self.mapLocation = locations.first!
self.mapRegion = MKCoordinateRegion(
center: locations.first!.coordinates,
span: mapSpan
)
}
func showNextLocation(_ location: Location) { ... }
func nextButtonPressed() { ... }
}
Ushbu ViewModel β ilovadagi barcha ekranlarning yagona manba:
- locations massivi
- joriy map location
- joriy map region
- ro'yxat ko'rinishi holati
- sheet holati
Views β EnvironmentObject orqali ulash
// App.swift
@main
struct MapApp: App {
@StateObject private var vm = LocationsViewModel()
var body: some Scene {
WindowGroup {
LocationsView()
.environmentObject(vm)
}
}
}
// Har bir view-da
@EnvironmentObject private var vm: LocationsViewModel
Ilovaning bosh nuqtasida LocationsViewModel yaratiladi va .environmentObject(vm) orqali muhitga joylashtiriladi. Bu yerdan, LocationsView-ning barcha avlod view-lari (LocationPreviewView, LocationsListView, LocationDetailView) β hech qaysi birida ViewModel-ni qo'lda uzatmasdan ham, @EnvironmentObject orqali shu ViewModel-ga murojaat qiladi.
Bu β SwiftUI-ning eng kuchli mexanizmlaridan biri: bir ob'ektni yaratib, butun ilova bo'ylab muammosiz ulashish imkoniyati.
Backend bilan ishlashga tayyorgarlik
Hozir locations LocationsDataService-dan yuklanadi. Agar haqiqiy backend bo'lsa, faqat init() qismini o'zgartirish kifoya:
init() {
// Hozirgi holat:
self.locations = LocationsDataService.locations
// Backend bilan:
self.locations = []
downloadLocations()
}
func downloadLocations() {
// API so'rov...
// self.locations.append(contentsOf: downloadedLocations)
}
Ilovaning qolgan hech qanday kodi o'zgarmaydi β chunki barcha view-lar faqat vm.locations massiviga qaraydi. Massiv qayerdan to'ldirilishi β view-lar uchun farq qilmaydi.
Accent color β bir qadamda ilovaning ko'rinishini o'zgartirish
Assets.xcassets β AccentColor-ni o'zgartirish β ilovaning barcha tugmalari, havolalar, pin-lar va boshqa elementlar bir vaqtda yangi rangga o'tadi. Hech qanday kod o'zgarmaydi.
Bu β AccentColor-ni ilovaning barcha joylarida aniq rang yozish o'rniga Color.accentColor yoki standart holatda qoldirish sababidir.
Ilova qo'llab-quvvatlanishi
| Qurilma | Portrait | Landscape |
|---|---|---|
| iPhone | β | β (o'chirilgan) |
| iPad | β | β |
| Rejim | Qo'llab-quvvatlanadi |
|---|---|
| Light mode | β |
| Dark mode | β (avtomatik) |
Dark mode avtomatik ishlagan sabab β barcha matnlarda .primary/.secondary, barcha fonlarda material ishlatilgan. Hech qanday aniq rang (Color.black, Color.white) ishlatilmagan.