LazyVStack və LazyHStack nədir öyrənin — SwiftUI-də səmərəli siyahılar, şəbəkələr və karusellər yaratmaq üçün tənbəl yığınlar. Adi VStack və HStack-dən fərqli olaraq, tənbəl yığınlar elementləri yalnız görünmə sahəsinə daxil olduqda yaradır ki, bu da böyük verilənlər dəstləri ilə işləyərkən yaddaş istehlakını kritik dərəcədə azaldır. Tənbəl yığınların arxitekturası Layout protokolu əsasında qurulmuşdur və ForEach və ScrollView vasitəsilə identifikasiya ilə inteqrasiya olunmuşdur.
Əsas məqamlar
LazyVStack və LazyHStack — SwiftUI-də yerləşdirmə konteynerləridir ki, alt görünüşləri yalnız sürüşdürülən sahədə görünən olduqda yaradır və göstərir. LazyVStack elementləri şaquli (yuxarıdan aşağıya), LazyHStack isə üfüqi (soldan sağa) yerləşdirir.
Hər iki yığın Apple tərəfindən SwiftUI 2.0 (iOS 14, macOS 11, watchOS 7, tvOS 14) ilə LazyVGrid və LazyHGrid ilə birlikdə təqdim edilmişdir. Tənbəl yığınlar meydana çıxmamışdan əvvəl proqramçılar böyük siyahılarla səmərəli işləmək üçün UITableView və UICollectionView-dən UIViewRepresentable vasitəsilə istifadə etməli idilər. LazyVStack bu ehtiyacı aradan qaldıraraq avtomatik tənbəl yükləmə ilə doğma SwiftUI interfeysi təqdim etdi.
Apple WWDC Session 10031 (2020) məlumatlarına görə, tənbəl yığınlar deferred view creation mexanizmindən istifadə edir: SwiftUI mənbə məlumatlarını (məsələn, modellər massivi) saxlayır və görünüş nümunələrini birbaşa ekranda göstərilmədən əvvəl yaradır. Sürüşdürmə zamanı yığınlar artıq yaradılmış görünüşləri təkrar istifadə edir, yeni allocasiyalardan qaçınır — bu yaddaş alokatoru və Swift təmizləyicisinə yükü azaldır.
Tənbəl yığınlarla işləmək üçün onları ScrollView-in içərisinə yerləşdirmək lazımdır — sürüşdürmə olmadan ekran hüdudlarından kənara çıxan elementlər sadəcə kəsiləcək, tənbəl yaradılmayacaq.
LazyVStack-də tənbəl yükləmə mexanizmi həndəsəyə əsaslanır: SwiftUI hər bir alt görünüşün ScrollView konteynerinə nisbətən mövqeyini izləyir. Element görünən sahənin həddini keçdikdə (bir neçə punktluq kiçik buferlə), sistem onun inizializatorunu çağırır və məzmunu göstərir. Element ekranı tərk etdikdə, SwiftUI görünüşü məhv edir, lakin @State vasitəsilə saxlanıla bilən kimi qeyd olunubsa, vəziyyəti qoruyur.
Bu yanaşma VStack-dən fərqlənir, burada bütün alt görünüşlər konteynerin inizializasiyası zamanı dərhal, görünməsindən asılı olmayaraq yaradılır. 10 000 elementlik siyahı üçün VStack yaddaşda 10 000 görünüş nümunəsi yaradacaq, LazyVStack isə yalnız ekrana sığanları (adətən 8–15).
LazyVStack üç konfiqurasiya parametri qəbul edir: alignment (HorizontalAlignment — leading, center, trailing), spacing (CGFloat — elementlər arasında boşluq) və pinnedViews (PinnedScrollableViews — bölmə başlıqlarının bərkidilməsi). LazyHStack eyni parametrlərdən istifadə edir, lakin alignment VerticalAlignment (top, center, bottom) qəbul edir.
LazyVStack və VStack arasında əsas fərq — alt elementlərin yaradılması strategiyasıdır. VStack (eager stack) göstərilmə anında bütün alt görünüşlərin ölçüsünü və mövqeyini hesablayır ki, bu da onu böyük dinamik siyahılar üçün yararsız edir. LazyVStack (lazy stack) yaratmanı element görünən olana qədər təxirə salır.
1000 mətn sətirindən ibarət siyahı nümunəsində davranışı müqayisə edək. VStack bütün 1000 sətiri dərhal yaddaşa yükləyərək hər sətrin inizializatorunu çağıracaq və ona yaddaş ayıracaq. Bu, zəif cihazlarda (iPhone SE, iPad mini) performansın düşməsinə və ekranın işə düşmə vaxtının artmasına səbəb olur. LazyVStack yalnız görünən 10–12 sətiri yükləyəcək, qalanlarını sürüşdürmə zamanı yaradacaq.
Praktiki test (Xcode Instruments, Allocations profili) göstərir: iPhone 12 mini-də LazyVStack ilə 5000 elementlik siyahı 3–5 MB yaddaş sərf edir, eyni məzmunlu VStack isə 150–250 MB — 50 dəfə çox. LazyVStack üçün ilkin göstərmə müddəti ~50 ms, eyni cihazda VStack üçün ~800 ms təşkil edir.
Statik və ya qısa siyahılar (10–15 elementədək) üçün VStack, dinamik və ya potensial olaraq uzun siyahılar üçün LazyVStack seçin. Apple siyahının maksimum ölçüsünə əmin deyilsinizsə, standart olaraq LazyVStack istifadə etməyi tövsiyə edir.
VStack statik interfeyslər üçün ən yaxşı seçim olaraq qalır: profil ekranı, giriş forması, məhsul kartı — elementlərin sayı məlum olan və 10–15-i keçməyən yerlər. VStack belə sayda elementlərin ilkin göstərilməsində daha sürətli işləyir, çünki həndəsə izləmə və tənbəl yükləməyə resurs sərf etmir. Bundan əlavə, VStack ScrollView xaricində də düzgün işləyir (məsələn, ZStack və ya Group daxilində), LazyVStack isə ScrollView olmadan mənasını itirir.
Tənbəl yığınlar optimaldır çox sayda və ya gözlənilməz sayda element ssenariləri üçün: sosial media lentləri, məhsul kataloqları, çat siyahıları, media fayl kitabxanaları, hadisə jurnalları, minlərlə qeydi olan idarəetmə panelləri.
Konkret istifadə halları: mesajlaşma proqramında mesaj siyahısı (on minlərlə mesaj), qalereya tətbiqində şəkil karuseli, sonsuz yükləmə ilə xəbər lenti, onlayn mağazada sifariş siyahısı. LazyHStack xüsusilə üfüqi karusellər üçün faydalıdır — Instagram Stories və ya reklam bannerləri kimi.
Əks göstərişlər: elementlərin görünmə animasiyası olan interfeyslər (tənbəl yığınlar əlavə məntiq olmadan element silinmə vəziyyətləri arasında keçidləri dəstəkləmir), bütün elementlərin eyni anda görünməli olduğu hallar (qısa seçim siyahısı) və hüceyrələrin təkrar istifadəsinə dəqiq nəzarətə ehtiyac olduqda (bu halda List və ya Table üstün ola bilər).
Əsas nümunə minimal yaddaş sərfiyyatı ilə 1000 elementi göstərir. Əsas elementlər: sürüşdürmə konteyneri kimi ScrollView, tənbəl yükləmə üçün LazyVStack, verilənlər üzərində iterasiya üçün identifikatorlu ForEach.
import SwiftUI
struct LazyListExample: View {
let items = Array(0..<1000)
var body: some View {
ScrollView {
LazyVStack(spacing: 8) {
ForEach(items, id: \.self) { index in
Text("Element #\(index)")
.font(.body)
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(8)
}
}
.padding()
}
}
}
Kod ScrollView yaradır, içərisində elementlər arasında 8pt boşluq olan LazyVStack yerləşdirilib. ForEach items massivi üzərində keçir və hər indeks üçün Text yaradır. Tənbəl yükləmə sayəsində 1000 elementdən eyni anda yaddaşda yalnız görünən 10–12-si olur.
Nümunə elementlərin bərkidilmiş başlıqlarla bölmələrə qruplaşdırılmasını göstərir, iOS kontaktlarındakı kimi. Section başlıq və məzmunu müəyyən edir, pinnedViews: .sectionHeaders sürüşdürmə zamanı başlığı ekranın yuxarısında sabitləyir.
import SwiftUI
struct SectionedList: View {
let cities = ["Moskva", "London", "Tokio", "Nyu-York", "Paris"]
let countries = ["Rusiya", "Böyük Britaniya", "Yaponiya", "ABŞ", "Fransa"]
var body: some View {
ScrollView {
LazyVStack(pinnedViews: .sectionHeaders) {
Section(header: Text("Şəhərlər").font(.title).bold()) {
ForEach(cities, id: \.self) { city in
Text(city).padding(8)
}
}
Section(header: Text("Ölkələr").font(.title).bold()) {
ForEach(countries, id: \.self) { country in
Text(country).padding(8)
}
}
}
}
}
}
Bərkidilmiş başlıqlar (.sectionHeaders) UITableView-də section headers kimi davranır: bölmə sürüşdürüldükdə başlıq bütün bölmə yox olana qədər ekranın yuxarı kənarına „yapışır", sonra növbəti bölmənin başlığı onu əvəz edir. pinnedViews birləşdirilə bilər: .sectionHeaders və .sectionFooters eyni anda.
LazyHStack üfüqi sürüşdürmə üçün istifadə olunur — şəkil karuselləri, üfüqi kateqoriya siyahıları. alignment: .top parametri elementləri yuxarı kənara düzləndirir.
import SwiftUI
struct HorizontalCarousel: View {
let colors: [Color] = [.red, .blue, .green, .orange, .purple, .pink]
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: 16, alignment: .top) {
ForEach(0..<100, id: \.self) { index in
RoundedRectangle(cornerRadius: 12)
.fill(colors[index % colors.count])
.frame(width: 150, height: 200)
.overlay(Text("\(index + 1)").foregroundColor(.white).bold())
}
}
.padding(.horizontal)
}
.frame(height: 220)
}
}
Kod LazyHStack ilə üfüqi ScrollView yaradır. 100 düzbucaqlıdan eyni anda 2–3-ü göstərilir (ekran eni və element ölçüsündən asılı olaraq). Sola sürüşdürdükdə yeni elementlər tənbəl yüklənir. Konteynerin hündürlüyü sabitdir (220pt), üfüqi sürüşdürmədə sonsuz hündürlüyün qarşısını almaq üçün.
PinnedScrollableViews — LazyVStack və LazyHStack-in sürüşdürmə zamanı bölmə başlıqlarının və altlıqlarının bərkidilməsini idarə edən konfiqurasiya seçimi. İki dəyər dəstəklənir: sectionHeaders (başlıqlar konteynerin əvvəlinə yapışır) və sectionFooters (altlıqlar sona yapışır).
Pinned views mexanizmi yalnız LazyVStack-ə daxil edilmiş Section konteyneri daxilində işləyir. Hər Section-ın başlığı və/və ya altlığı avtomatik olaraq yapışma davranışı əldə edir. SwiftUI hər bölmənin ScrollView hüdudlarına nisbətən mövqeyini izləyir və bölmələr arasında keçid zamanı bərkidilmiş elementin görünməsini dəyişir.
Vacib: pinnedViews yerləşdirmə hesablamasının mürəkkəbliyini artırır, çünki SwiftUI hansı başlığın hazırda bərkidildiyini daim yenidən hesablamalıdır. pinnedViews-dən yalnız funksionallıq həqiqətən lazım olduqda istifadə edin — bölməsiz sadə siyahılar üçün bu parametri buraxmaq daha yaxşıdır. Apple sənədləşməsində (Human Interface Guidelines, 2024) əlifba göstəriciləri və tarixlərə görə qruplaşdırma üçün bərkidilmiş başlıqlardan istifadə etməyi tövsiyə edir.
İdentifikatorlardan düzgün istifadə — LazyVStack performansının ən vacib amilidir. ForEach-dəki hər bir element sabit unikal id-yə malik olmalıdır. Primitiv tiplərlə (Int, String) \.self istifadəsi məqbuldur, lakin məlumat modelləri üçün həmişə Identifiable protokolunu tətbiq edin. Qeyri-sabit id-lər (məsələn, hər dəfə yaradılan UUID) SwiftUI-ni hər yeniləmədə bütün görünüşləri yenidən yaratmağa məcbur edir.
Yığının hər bir elementinin body-si daxilində ağır hesablamalardan qaçının. Element mürəkkəb düzülüş və ya məlumat emalı ehtiva edirsə — məntiqi öz tənbəl yükləməsi olan ayrıca görünüş strukturuna çıxarın. Elementin məlumatları dəyişmədikdə lazımsız yenidən çəkmələrin qarşısını almaq üçün EquatableView istifadə edin.
LazyVStack daxilində şəkillər üçün mütləq asinxron yükləmə (AsyncImage) və ya Kingfisher/Nuke vasitəsilə keşləmə tətbiq edin. Ekranda görünən hər bir element sinxron şəkildə şəkil yükləməməlidir — bu sürüşdürmədə ləngimələrə (jank) səbəb olacaq. WWDC Session 10031 məlumatlarına görə, prefetching üçün optimal bufer ölçüsü cari mövqedən 3–5 ekran irəli və geridir.
Performansı Xcode Instruments ilə SwiftUI profili vasitəsilə ölçün. Metriklərə diqqət yetirin: body evaluations, allocations və FPS. Hədəf göstəriciləri: sürüşdürmə zamanı FPS > 55, bir elementin göstərilmə müddəti < 1 ms.
Tez-tez verilən suallar
List daxili imkanlar təmin edir: sürüşdürmə ilə redaktə (swipeActions), .onDelete ilə silmə, .onMove ilə yerdəyişmə, .insetGrouped qruplaşdırma stili. LazyVStack daha aşağı səviyyəli alətdir, redaktə jestləri üçün daxili dəstək yoxdur. List daxildə LazyVStack-dən istifadə edir, lakin doğma iOS cədvəl stilini əlavə edir. Əgər xüsusi hüceyrə dizaynına ehtiyacınız varsa və daxili redaktə lazım deyilsə — LazyVStack seçin. swipeActions, .onDelete və @FetchRequest ilə iş lazımdırsa — List istifadə edin.
Tənbəl yığınlar prefetching istifadə edir — SwiftUI sürüşdürmənin hamar olması üçün elementləri kiçik qabaqcadan yükləmə buferi (prefetch buffer) ilə yaradır. Bufer ölçüsü avtomatik olaraq sürüşdürmə sürətinə və cihazın performansına uyğunlaşdırılır. Apple profilləmə məlumatlarına görə, prefetch buffer adətən sürüşdürmə istiqamətində 1–3 ekran təşkil edir. Əgər çoxlu görünməz element yaradıldığını görürsünüzsə, hər dəfə yaradılan identifikatorlarınızın və ya görünüş inizializatorunda ağır hesablamalarınızın olub olmadığını yoxlayın.
Bəli, lakin məhdudiyyətlərlə. LazyVStack-i VStack-ə yerləşdirmək mənasızdır — xarici VStack daxili LazyVStack-in bütün elementlərini dərhal yaradaraq tənbəl yükləməni ləğv edir. VStack-i LazyVStack-ə yerləşdirmək mümkündür, bu tənbəl mexanizmi pozmur. LazyVStack-i başqa LazyVStack-in içərisinə yerləşdirmək iç-içə bölmələr üçün məqbuldur, lakin performansa nəzarət edin: hər səviyyə həndəsə izləməyə əlavə yük gətirir.
SwiftUI LazyVStack üçün daxili ayırıcılar təmin etmir. Onları əl ilə əlavə edin: ForEach-də hər elementdən sonra Divider() yerləşdirin və ya hər elementdə .overlay(Divider(), alignment: .bottom) modifierindən istifadə edin. Xüsusi ayırıcılar üçün Rectangle().frame(height: 1).foregroundColor(.gray.opacity(0.3)) çəkin.
Xülasə
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun