SwiftUI'da LazyVStack ve LazyHStack'in ne olduğunu öğrenin — iOS, macOS, watchOS ve tvOS'da kaydırılabilir listeleri, ızgaraları ve karuselleri verimli şekilde oluşturan tembel yığınlar. Normal VStack ve HStack'in aksine, tembel yığınlar öğeleri yalnızca görünür alanda göründüklerinde oluşturur ve büyük veri kümeleriyle çalışırken bellek tüketimini kritik şekilde azaltır. Tembel yığınların mimarisi Layout protokolüne dayanır ve ForEach ile ScrollView aracılığıyla tanımlamayla entegre edilmiştir.
Önemli Noktalar
LazyVStack ve LazyHStack, SwiftUI'da alt görünümleri yalnızca ihtiyaç duyulduğunda, kaydırılabilir alanda görünür hale geldiklerinde oluşturan ve gösteren düzen kaplarıdır. LazyVStack öğeleri dikey olarak (yukarıdan aşağıya) düzenlerken, LazyHStack yatay olarak (soldan sağa) düzenler.
Her iki yığın da Apple tarafından SwiftUI 2.0 (iOS 14, macOS 11, watchOS 7, tvOS 14) ile LazyVGrid ve LazyHGrid ile birlikte tanıtıldı. Tembel yığınlar ortaya çıkmadan önce, geliştiriciler büyük listelerle verimli çalışmak için UIViewRepresentable aracılığıyla UITableView ve UICollectionView kullanmak zorundaydı. LazyVStack, otomatik tembel yüklemeli yerel bir SwiftUI arayüzü sağlayarak bu ihtiyacı ortadan kaldırdı.
Apple WWDC Session 10031'e (2020) göre, tembel yığınlar ertelenmiş görünüm oluşturma mekanizması kullanır: SwiftUI kaynak verileri (örneğin, bir model dizisi) saklar ve ekranda oluşturmadan hemen önce görünüm örnekleri oluşturur. Kaydırma sırasında yığınlar önceden oluşturulmuş görünümleri yeniden kullanarak yeni tahsislerden kaçınır — bu, bellek ayırıcı ve Swift'in çöp toplayıcısı üzerindeki yükü azaltır.
Tembel yığınlarla çalışmak için onları her zaman bir ScrollView içine yerleştirin — kaydırma olmadan, ekran sınırlarının ötesine uzanan öğeler tembelce oluşturulmak yerine basitçe kırpılır.
LazyVStack'te tembel yükleme mekanizması geometriye dayanır: SwiftUI, her alt görünümün ScrollView kapsayıcısına göre konumunu izler. Bir öğe görünür alan sınırını geçtiğinde (birkaç puanlık küçük bir arabellekle), sistem onun başlatıcısını çağırır ve içeriği oluşturur. Bir öğe ekrandan ayrıldığında, SwiftUI görünümü yok eder ancak @State aracılığıyla durumu korur (korunabilir olarak işaretlenmişse).
Bu yaklaşım, görünürlükten bağımsız olarak tüm alt görünümlerin kapsayıcı başlatma sırasında hemen oluşturulduğu VStack'ten farklıdır. 10.000 öğelik bir liste için VStack, bellekte 10.000 görünüm örneği oluştururken, LazyVStack yalnızca ekrana sığanları oluşturur (genellikle 8–15).
LazyVStack üç yapılandırma parametresi kabul eder: alignment (HorizontalAlignment — leading, center, trailing), spacing (CGFloat — öğeler arası boşluk) ve pinnedViews (PinnedScrollableViews — bölüm başlıklarını sabitleme). LazyHStack aynı parametreleri kullanır ancak alignment, VerticalAlignment (top, center, bottom) kabul eder.
LazyVStack ve VStack arasındaki temel fark, alt öğeleri oluşturma stratejisidir. VStack (istekli yığın), oluşturma anında tüm alt görünümlerin boyutunu ve konumunu hesaplar, bu da onu büyük dinamik listeler için uygunsuz hale getirir. LazyVStack (tembel yığın), öğe görünür olana kadar oluşturmayı erteler.
1000 metin satırlık bir liste kullanarak davranışı karşılaştıralım. VStack, tüm 1000 satırı hemen belleğe yükleyecek, her satırın başlatıcısını çağıracak ve onun için bellek ayıracaktır. Bu, zayıf cihazlarda (iPhone SE, iPad mini) performans düşüşüne ve ekran başlatma süresinin artmasına neden olur. LazyVStack yalnızca görünür 10–12 satırı yükleyecek, geri kalanını kaydırma sırasında oluşturacaktır.
Pratik bir test (Xcode Instruments, Allocations profili kullanarak) gösteriyor ki: iPhone 12 mini'de, LazyVStack ile 5000 öğelik bir liste 3–5 MB bellek tüketirken, aynı içeriğe sahip VStack 150–250 MB tüketiyor — 50 kat daha fazla. Bu arada, LazyVStack için ilk oluşturma süresi ~50 ms iken, aynı cihazda VStack için ~800 ms'dir.
Statik veya kısa listeler (10–15 öğeye kadar) için VStack'i, dinamik veya potansiyel olarak uzun listeler için LazyVStack'i seçin. Apple, listenin maksimum boyutundan emin değilseniz varsayılan olarak LazyVStack kullanmanızı önerir.
VStack statik arayüzler için en iyi seçim olmaya devam ediyor: profil ekranı, giriş formu, ürün kartı — öğe sayısının bilindiği ve 10–15'i aşmadığı durumlarda. VStack, bu tür miktarlar için ilk oluşturmada daha hızlı çalışır çünkü geometri izleme ve tembel yüklemeye kaynak harcamaz. Ayrıca, VStack ScrollView dışında (örneğin ZStack veya Group içinde) doğru çalışırken, LazyVStack ScrollView olmadan amacını kaybeder.
Tembel yığınlar, büyük veya tahmin edilemez sayıda öğe içeren senaryolar için idealdir: sosyal medya akışları, ürün katalogları, sohbet listeleri, medya dosyası kütüphaneleri, etkinlik günlükleri, binlerce kayıtlı yönetim panelleri.
Belirli kullanım durumları: bir mesajlaşma uygulamasındaki mesaj listesi (on binlerce mesaj), galeri uygulamasındaki resim karuseli, sonsuz yüklemeli haber akışı, çevrimiçi mağazadaki sipariş listesi. LazyHStack özellikle yatay karuseller için kullanışlıdır — örneğin Instagram Hikayeleri veya promosyon banner'ları.
Kontrendikasyonlar: öğe görünme animasyonları olan arayüzler (tembel yığınlar ek mantık olmadan öğe silme durumları arasında geçişi desteklemez), tüm öğelerin aynı anda görünür olması gereken durumlar (kısa bir onay kutusu listesi) ve hücre yeniden kullanımı üzerinde hassas kontrol ihtiyacı (bu durumda List veya Table daha uygun olabilir).
Temel bir örnek, minimum bellek tüketimiyle 1000 öğe görüntüler. Anahtar öğeler: ScrollView kaydırma kabı olarak, LazyVStack tembel yükleme için, ForEach veri tekrarı için bir tanımlayıcıyla.
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("Öğe #\(index)")
.font(.body)
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(8)
}
}
.padding()
}
}
}
Kod, öğeler arasında 8pt boşluk bulunan bir LazyVStack içeren ScrollView oluşturur. ForEach, items dizisinde döngü yapar ve her dizin için bir Text oluşturur. Tembel yükleme sayesinde, 1000 öğeden yalnızca görünür 10–12'si aynı anda bellekte bulunur.
Bu örnek, iOS kişilerine benzer şekilde, sabitlenmiş başlıklarla öğeleri bölümlere göre gruplandırmayı gösterir. Section başlığı ve içeriği tanımlar, pinnedViews: .sectionHeaders kaydırma sırasında başlığı ekranın üstüne sabitler.
import SwiftUI
struct SectionedList: View {
let cities = ["Moskova", "Londra", "Tokyo", "New York", "Paris"]
let countries = ["Rusya", "Birleşik Krallık", "Japonya", "ABD", "Fransa"]
var body: some View {
ScrollView {
LazyVStack(pinnedViews: .sectionHeaders) {
Section(header: Text("Şehirler").font(.title).bold()) {
ForEach(cities, id: \.self) { city in
Text(city).padding(8)
}
}
Section(header: Text("Ülkeler").font(.title).bold()) {
ForEach(countries, id: \.self) { country in
Text(country).padding(8)
}
}
}
}
}
}
Sabitlenmiş başlıklar (.sectionHeaders), UITableView bölüm başlıkları gibi davranır: bir bölüm kaydırıldığında, başlık tüm bölüm kaybolana kadar ekranın üst kenarına "yapışır" ve ardından bir sonraki bölümün başlığıyla değiştirilir. pinnedViews birleştirilebilir: .sectionHeaders ve .sectionFooters aynı anda.
LazyHStack, yatay kaydırma — resim karuselleri, yatay kategori listeleri için kullanılır. alignment: .top parametresi öğeleri üst kenara hizalar.
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 ile yatay bir ScrollView oluşturur. 100 dikdörtgenden yalnızca 2–3'ü aynı anda görüntülenir (ekran genişliğine ve öğe boyutuna bağlı olarak). Sola kaydırıldığında, yeni öğeler tembelce yüklenir. Kapsayıcı yüksekliği sabittir (220pt) ve yatay kaydırmada sonsuz yüksekliği önler.
PinnedScrollableViews, LazyVStack ve LazyHStack için kaydırma sırasında bölüm başlıklarını ve altbilgilerini sabitlemeyi kontrol eden bir yapılandırma seçeneğidir. İki değer desteklenir: sectionHeaders (başlıklar kapsayıcının başına yapışır) ve sectionFooters (altbilgiler sona yapışır).
Sabitlenmiş görünüm mekanizması yalnızca LazyVStack içinde iç içe geçmiş Section kapsayıcısı içinde çalışır. Her Section, otomatik olarak yapışma davranışı elde eden bir başlık ve/veya altbilgiye sahiptir. SwiftUI, her bölümün konumunu ScrollView sınırlarına göre izler ve bölümler arasında geçiş yaparken sabitlenmiş öğenin görünürlüğünü değiştirir.
Önemli: pinnedViews, düzen hesaplama karmaşıklığını artırır çünkü SwiftUI hangi başlığın o anda sabitlendiğini sürekli yeniden hesaplamalıdır. pinnedViews'ı yalnızca işlevsellik gerçekten gerekli olduğunda kullanın — bölümleri olmayan basit listeler için bu parametreyi atlamak daha iyidir. Apple, belgelerinde (Human Interface Guidelines, 2024) alfabetik indeksler ve tarih tabanlı gruplandırma için sabitlenmiş başlıkların kullanılmasını önerir.
Tanımlayıcıların doğru kullanımı, LazyVStack için en önemli performans faktörüdür. ForEach'teki her öğe kararlı ve benzersiz bir id'ye sahip olmalıdır. İlkel türlerle (Int, String) \.self kullanımı kabul edilebilir, ancak veri modelleri için her zaman Identifiable protokolünü uygulayın. Kararsız id'ler (örneğin, her seferinde oluşturulan UUID), SwiftUI'nin her güncellemede tüm görünümleri yeniden oluşturmasına neden olur.
Her yığın öğesinin body'si içinde ağır hesaplamalardan kaçının. Bir öğe karmaşık düzen veya veri işleme içeriyorsa — mantığı kendi tembel yüklemesi olan ayrı bir görünüm yapısına çıkarın. Öğenin verileri değişmediğinde gereksiz yeniden çizimleri önlemek için EquatableView kullanın.
LazyVStack içindeki resimler için her zaman asenkron yükleme (AsyncImage) veya Kingfisher/Nuke ile önbellekleme kullanın. Her öğe ekranda göründüğünde senkron olarak resim yüklememelidir — bu kaydırma takılmalarına neden olur. WWDC Session 10031'e göre, önceden getirme arabelleğinin optimum boyutu mevcut konumdan 3–5 ekran ileri ve geridir.
Performansı Xcode Instruments ile SwiftUI profili kullanarak ölçün. Metriklere dikkat edin: body evaluations, allocations ve kare hızı (FPS). Hedef değerler: kaydırma sırasında FPS > 55, öğe başına oluşturma süresi < 1 ms.
Sıkça Sorulan Sorular
List yerleşik yetenekler sağlar: kaydırarak düzenleme (swipeActions), .onDelete ile silme, .onMove ile yeniden sıralama, gruplandırılmış stil .insetGrouped. LazyVStack, düzenleme jestleri için yerleşik destek olmayan daha düşük seviyeli bir araçtır. List dahili olarak LazyVStack kullanır ancak yerel iOS tablo stilini ekler. Özel hücre tasarımına ihtiyacınız varsa ve yerleşik düzenlemeye ihtiyacınız yoksa — LazyVStack'i seçin. SwipeActions, .onDelete ve @FetchRequest ile çalışmaya ihtiyacınız varsa — List kullanın.
Tembel yığınlar önceden getirme kullanır — SwiftUI, pürüzsüz kaydırma sağlamak için küçük bir önceden alma arabelleği (prefetch buffer) ile öğeler oluşturur. Arabellek boyutu, kaydırma hızına ve cihaz performansına göre otomatik olarak ayarlanır. Apple profil oluşturma verilerine göre, önceden getirme arabelleği genellikle kaydırma yönünde 1–3 ekrandır. Çok fazla görünmez öğe oluşturulduğunu görürseniz, her seferinde oluşturulan tanımlayıcılarınızın veya görünüm başlatıcıda ağır hesaplamalarınızın olup olmadığını kontrol edin.
Evet, ancak sınırlamalarla. LazyVStack'i VStack içine yerleştirmek anlamsızdır — dış VStack, iç LazyVStack'in tüm öğelerini hemen oluşturarak tembel yüklemeyi iptal eder. VStack'i LazyVStack içine yerleştirmek kabul edilebilir ve tembel mekanizmayı bozmaz. LazyVStack'i başka bir LazyVStack içine yerleştirmek iç içe bölümler için kabul edilebilir, ancak performansa dikkat edin: her seviye geometri izleme için ek yük ekler.
SwiftUI, LazyVStack için yerleşik ayırıcılar sağlamaz. Bunları manuel olarak ekleyin: ForEach'teki her öğeden sonra Divider() yerleştirin veya her öğede .overlay(Divider(), alignment: .bottom) değiştiricisini kullanın. Özel ayırıcılar için Rectangle().frame(height: 1).foregroundColor(.gray.opacity(0.3)) çizin.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.