SwiftUI Performans: LazyVStack ve Karmaşık Listeleri Optimize Etme
LazyVStack ve SwiftUI listeleri için optimizasyon teknikleri. Bellek tüketimini azaltın, kaydırma performansını iyileştirin ve sık karşılaşılan tuzakları önleyin.

Listeler, iOS uygulamalarında en sık kullanılan bileşenlerden birini temsil eder. SwiftUI'nin LazyVStack ve List bileşenleri veri koleksiyonlarını göstermek için performanslı çözümler sunar, ancak yanlış kullanım kullanıcı deneyimini hızla bozabilir. Bu bileşenlerin iç mekanizmalarını anlamak, sık karşılaşılan tuzakları önlemeye ve akıcı arayüzler oluşturmaya yardımcı olur.
Bu makale SwiftUI listeleri için temel optimizasyon tekniklerini sunar: lazy loading, view geri dönüşümü, tanımlayıcı yönetimi ve büyük veri kümeleri için gelişmiş desenler. Yeni yeniden sıralama ve swipe action API'leri ile iOS 27 için güncellendi.
SwiftUI'de Lazy Loading'i Anlamak
Lazy loading, view'ları yalnızca ekranda görünür hale geldiklerinde örneklendirir. Tüm alt view'ları hemen oluşturan VStack'in aksine, LazyVStack bu oluşturma işlemini erteler ve bellek tüketimini ile başlangıç render süresini önemli ölçüde azaltır.
import SwiftUI
// ❌ Problem: VStack instantiates all 10,000 views immediately
struct NonLazyListView: View {
let items = (1...10000).map { "Item (/bin/bash)" }
var body: some View {
ScrollView {
VStack {
ForEach(items, id: .self) { item in
// Each view is created at launch
ExpensiveRowView(title: item)
}
}
}
}
}
// ✅ Solution: LazyVStack creates views on demand
struct LazyListView: View {
let items = (1...10000).map { "Item (/bin/bash)" }
var body: some View {
ScrollView {
LazyVStack {
ForEach(items, id: .self) { item in
// Only visible views are created
ExpensiveRowView(title: item)
}
}
}
}
}Performans farkı yalnızca birkaç yüz öğeyle bile belirgin hale gelir. 10.000 öğeyle VStack başlatılması birkaç saniye sürebilirken LazyVStack anında kalır.
Lazy Loading Etkisini Ölçmek
Instruments, bellek ve CPU kullanımını hassas bir şekilde ölçmeye olanak tanır. İşte farkı gösteren bir test view'i:
struct ExpensiveRowView: View {
let title: String
// Simulating expensive initialization
init(title: String) {
self.title = title
// Log to visualize when the view is created
print("Creating row: (title)")
}
var body: some View {
HStack {
// Image with processing
Circle()
.fill(
LinearGradient(
colors: [.blue, .purple],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.frame(width: 50, height: 50)
VStack(alignment: .leading) {
Text(title)
.font(.headline)
Text("Subtitle with computation")
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
}
.padding()
}
}VStack ile çalıştırıldığında, 10.000 logun tamamı hemen görünür. LazyVStack ile, yalnızca görünür öğeler (ekran boyutuna bağlı olarak yaklaşık 15-20) başlangıçta loglanır ve kaydırma sırasında daha fazlası ortaya çıkar.
LazyVStack, oluşturulan view'ları göründükten sonra bellekte tutar. Hücreleri aktif olarak geri dönüştüren List'in aksine, LazyVStack içindeki view'lar üst bileşen yok edilene kadar kalır.
Kararlı Tanımlayıcıların Önemi
Tanımlayıcılar SwiftUI liste güncellemelerinin merkezi mekanizmasını oluşturur. Kararsız bir tanımlayıcı gereksiz view yeniden oluşturmalarına neden olur ve yanlış animasyonlar veya kaydırma konumu kaybı gibi görsel hatalara yol açabilir.
// ❌ Problem: using index as identifier
struct UnstableIdentifierView: View {
@State private var items = ["A", "B", "C", "D"]
var body: some View {
List {
// Index changes if an element is deleted
ForEach(items.indices, id: .self) { index in
Text(items[index])
}
}
}
}
// ❌ Problem: using UUID() in ForEach
struct RegeneratedIdentifierView: View {
let items = ["A", "B", "C", "D"]
var body: some View {
List {
// UUID() generates a new ID on each render
ForEach(items, id: .self) { item in
// Subtle issue if items contain duplicates
Text(item)
}
}
}
}
// ✅ Solution: model with stable identifier
struct Item: Identifiable {
let id: UUID // Created once
var name: String
init(name: String) {
self.id = UUID()
self.name = name
}
}
struct StableIdentifierView: View {
@State private var items = [
Item(name: "A"),
Item(name: "B"),
Item(name: "C"),
Item(name: "D")
]
var body: some View {
List {
// id is stable for the item's lifetime
ForEach(items) { item in
Text(item.name)
}
}
}
}Benzersiz ve kalıcı bir tanımlayıcı kullanmak, SwiftUI'nin güncellemeler, animasyonlar ve karşılaştırmalar sırasında öğeleri doğru şekilde ayırt edebilmesini garantiler.
Equatable ile Hücre Optimizasyonu
SwiftUI yeniden render gerekip gerekmediğini belirlemek için view'ları karşılaştırır. Varsayılan olarak bu karşılaştırma reflection kullanır ki bu da maliyetli olabilir. Equatable uygulamak optimize edilmiş ve açık karşılaştırma sağlar.
// Data model
struct Contact: Identifiable, Equatable {
let id: UUID
var name: String
var email: String
var avatarURL: URL?
var lastActivity: Date
// Custom comparison: ignore lastActivity
// if other properties are identical
static func == (lhs: Contact, rhs: Contact) -> Bool {
lhs.id == rhs.id &&
lhs.name == rhs.name &&
lhs.email == rhs.email &&
lhs.avatarURL == rhs.avatarURL
// lastActivity intentionally excluded
}
}
// Optimized cell view
struct ContactRow: View, Equatable {
let contact: Contact
// Explicit comparison to avoid unnecessary re-renders
static func == (lhs: ContactRow, rhs: ContactRow) -> Bool {
lhs.contact == rhs.contact
}
var body: some View {
HStack(spacing: 12) {
// Async avatar
AsyncImage(url: contact.avatarURL) { phase in
switch phase {
case .success(let image):
image
.resizable()
.aspectRatio(contentMode: .fill)
case .failure:
Image(systemName: "person.circle.fill")
.foregroundStyle(.gray)
default:
ProgressView()
}
}
.frame(width: 44, height: 44)
.clipShape(Circle())
// Contact information
VStack(alignment: .leading, spacing: 2) {
Text(contact.name)
.font(.body.weight(.medium))
Text(contact.email)
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
}
.padding(.vertical, 4)
}
}
// List using EquatableView
struct ContactListView: View {
let contacts: [Contact]
var body: some View {
List {
ForEach(contacts) { contact in
// EquatableView prevents re-renders if contact unchanged
EquatableView(content: ContactRow(contact: contact))
}
}
}
}Bu optimizasyon, özellikle hesaplama veya görsel içeren hücrelerde hızlı kaydırma sırasında CPU yükünü önemli ölçüde azaltır.
iOS mülakatlarında başarılı olmaya hazır mısın?
İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.
Asenkron Görsel Yüklemenin Yönetimi
Görseller listelerde sıklıkla performans darboğazı haline gelir. iOS 27, birçok senaryoda manuel önbelleğe alma ihtiyacını ortadan kaldırarak varsayılan olarak AsyncImage için standart HTTP önbelleğe alma özelliğini tanıttı. iOS 26 ve önceki sürümleri hedefleyen veya önbelleğe alma davranışı üzerinde hassas kontrol gerektiren uygulamalar için özel bir uygulama değerli olmaya devam eder.
import SwiftUI
// Singleton image cache (for iOS 26 or custom caching needs)
actor ImageCache {
static let shared = ImageCache()
private var cache = NSCache<NSString, UIImage>()
private init() {
// Memory limit: 50 MB
cache.totalCostLimit = 50 * 1024 * 1024
}
func image(for url: URL) -> UIImage? {
cache.object(forKey: url.absoluteString as NSString)
}
func setImage(_ image: UIImage, for url: URL) {
// Cost estimation: image bytes
let cost = Int(image.size.width * image.size.height * 4)
cache.setObject(image, forKey: url.absoluteString as NSString, cost: cost)
}
}
// Optimized image view with caching
struct CachedAsyncImage: View {
let url: URL?
let size: CGSize
@State private var image: UIImage?
@State private var isLoading = false
var body: some View {
Group {
if let image {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fill)
} else if isLoading {
Rectangle()
.fill(Color.gray.opacity(0.2))
.overlay(ProgressView())
} else {
Rectangle()
.fill(Color.gray.opacity(0.2))
}
}
.frame(width: size.width, height: size.height)
.clipped()
.task(id: url) {
await loadImage()
}
}
private func loadImage() async {
guard let url else { return }
// Check cache
if let cached = await ImageCache.shared.image(for: url) {
self.image = cached
return
}
isLoading = true
defer { isLoading = false }
// Download and resize
do {
let (data, _) = try await URLSession.shared.data(from: url)
// Resize to save memory
if let original = UIImage(data: data),
let resized = await resizeImage(original, to: size) {
await ImageCache.shared.setImage(resized, for: url)
self.image = resized
}
} catch {
// Handle error silently
}
}
private func resizeImage(_ image: UIImage, to size: CGSize) async -> UIImage? {
// Use screen scale
let scale = await UIScreen.main.scale
let targetSize = CGSize(
width: size.width * scale,
height: size.height * scale
)
return await withCheckedContinuation { continuation in
DispatchQueue.global(qos: .userInitiated).async {
let renderer = UIGraphicsImageRenderer(size: targetSize)
let resized = renderer.image { _ in
image.draw(in: CGRect(origin: .zero, size: targetSize))
}
continuation.resume(returning: resized)
}
}
}
}iOS 27 ve sonrası için standart AsyncImage HTTP cache header'larını otomatik olarak dikkate alır. Özel gereksinimler için asyncImageURLSession(_:) modifier'ı aracılığıyla özel bir URLCache uygulanabilir.
Öngörü için Akıllı Prefetching
Çok uzun listeler için prefetching görselleri görünür hale gelmeden önce yükler:
// Prefetching coordinator
@Observable
final class ImagePrefetcher {
private var prefetchTasks: [URL: Task<Void, Never>] = [:]
private let prefetchDistance = 10 // Number of items ahead
func prefetchImages(for items: [Contact], visibleRange: Range<Int>) {
// Calculate prefetch range
let prefetchStart = max(0, visibleRange.lowerBound - prefetchDistance)
let prefetchEnd = min(items.count, visibleRange.upperBound + prefetchDistance)
// Launch prefetch for items in range
for index in prefetchStart..<prefetchEnd {
guard let url = items[index].avatarURL else { continue }
// Avoid duplicates
guard prefetchTasks[url] == nil else { continue }
prefetchTasks[url] = Task {
// Check if already cached
if await ImageCache.shared.image(for: url) != nil {
return
}
// Prefetch
do {
let (data, _) = try await URLSession.shared.data(from: url)
if let image = UIImage(data: data) {
await ImageCache.shared.setImage(image, for: url)
}
} catch {
// Ignore prefetch errors
}
}
}
// Cancel out-of-range prefetches
cancelOutOfRangePrefetches(validRange: prefetchStart..<prefetchEnd, items: items)
}
private func cancelOutOfRangePrefetches(validRange: Range<Int>, items: [Contact]) {
let validURLs = Set(
items[validRange].compactMap { /bin/bash.avatarURL }
)
for (url, task) in prefetchTasks {
if !validURLs.contains(url) {
task.cancel()
prefetchTasks.removeValue(forKey: url)
}
}
}
}List ve LazyVStack Arasında Seçim
iOS 27, List ile LazyVStack arasındaki farkı önemli ölçüde azalttı. Daha önce, List yerel swipe eylemleri ve sürükleyerek yeniden sıralama için tek seçenekti. Yeni .swipeActionsContainer() ve .reorderable() API'leri bu yetenekleri herhangi bir konteynere taşıyor.
// ✅ List: automatic cell recycling, built-in styles
// Best for: standard contacts, settings, data tables
struct ContactsWithSwipeActions: View {
@State private var contacts: [Contact] = []
var body: some View {
List {
ForEach(contacts) { contact in
ContactRow(contact: contact)
.swipeActions(edge: .trailing) {
Button(role: .destructive) {
deleteContact(contact)
} label: {
Label("Delete", systemImage: "trash")
}
}
.swipeActions(edge: .leading) {
Button {
favoriteContact(contact)
} label: {
Label("Favorite", systemImage: "star")
}
.tint(.yellow)
}
}
}
.listStyle(.plain)
}
private func deleteContact(_ contact: Contact) {
contacts.removeAll { /bin/bash.id == contact.id }
}
private func favoriteContact(_ contact: Contact) {
// Favorite logic
}
}
// ✅ LazyVStack with iOS 27 swipe actions
// Best for: custom layouts, cards, feeds
struct CustomFeedWithSwipeActions: View {
@State private var posts: [Post] = []
var body: some View {
ScrollView {
LazyVStack(spacing: 16) {
ForEach(posts) { post in
PostCard(post: post)
.swipeActions(edge: .trailing) {
Button(role: .destructive) {
deletePost(post)
} label: {
Label("Delete", systemImage: "trash")
}
}
}
}
.padding(.horizontal)
.swipeActionsContainer()
}
}
private func deletePost(_ post: Post) {
posts.removeAll { /bin/bash.id == post.id }
}
}Seçim artık özellik kullanılabilirliğine değil görsel gereksinimlere bağlıdır:
| Gereksinim | Önerilen Konteyner |
|---|---|
| Bellek verimliliği için hücre geri dönüşümü | List |
| Özel boşluk ve padding | LazyVStack |
| Yerleşik ayırıcılar ve girintiler | List |
| Kart tabanlı veya standart dışı düzenler | LazyVStack |
| Yapışkan davranışlı bölüm başlıkları | Her ikisi (pinnedViews kullanın) |
| Swipe eylemleri | Her ikisi (iOS 27+) |
| Sürükleyerek yeniden sıralama | Her ikisi (iOS 27+) |
List hücreleri aktif olarak geri dönüştürür ve bu, yerel state (@State) ile sorunlara neden olabilir. List hücrelerindeki @State değerleri beklenmedik şekilde yeniden kullanılabilir. State'i veri modelinde veya bir ViewModel'de saklamak tercih edilmelidir.
LazyVStack'te Öğeleri Yeniden Sıralama (iOS 27)
iOS 27'den önce, sürükleyerek yeniden sıralama onMove(perform:) ile List gerektiriyordu. Yeni reorderable API'leri LazyVStack ve LazyVGrid dahil herhangi bir konteynerle çalışır.
import SwiftUI
struct ReorderableCardList: View {
@State private var cards: [Card] = Card.sampleData
var body: some View {
ScrollView {
LazyVStack(spacing: 12) {
ForEach(cards) { card in
CardView(card: card)
}
.reorderable() // Mark content as draggable
}
.padding()
.reorderContainer(for: Card.self) { difference in
// Apply the reorder to the model
cards.apply(difference: difference)
}
}
}
}
struct Card: Identifiable {
let id: UUID
var title: String
var color: Color
static var sampleData: [Card] {
[
Card(id: UUID(), title: "Design Review", color: .blue),
Card(id: UUID(), title: "Code Sprint", color: .green),
Card(id: UUID(), title: "Testing Phase", color: .orange)
]
}
}
struct CardView: View {
let card: Card
var body: some View {
Text(card.title)
.font(.headline)
.frame(maxWidth: .infinity)
.padding()
.background(card.color.opacity(0.2))
.cornerRadius(12)
}
}
// Extension to apply ReorderDifference
extension Array where Element: Identifiable {
mutating func apply(difference: ReorderDifference<Element.ID>) {
// Move items from sources to destination
let movedItems = difference.sources.compactMap { id in
self.first { /bin/bash.id == id }
}
// Remove from original positions
self.removeAll { item in
difference.sources.contains(item.id)
}
// Insert at destination
if let destinationIndex = self.firstIndex(where: { /bin/bash.id == difference.destination }) {
self.insert(contentsOf: movedItems, at: destinationIndex)
} else {
self.append(contentsOf: movedItems)
}
}
}SwiftUI sürükleme önizlemesini, ekleme placeholder'ını ve bırakma animasyonunu yönetir. ReorderDifference nesnesi kaynak ID'lerini ve hedefi sağlar, model güncellemelerini uygulama koduna bırakır.
Optimize Edilmiş Bölümler ve Başlıklar
İçeriği bölümler halinde organize etmek okunabilirliği artırır ancak kötü uygulanırsa performansı etkileyebilir. Sabitlenmiş başlıklar ve bölüm yönetimi özel dikkat gerektirir.
// Grouped data model
struct GroupedContacts {
let letter: String
let contacts: [Contact]
}
// View with optimized sections
struct SectionedContactList: View {
let groupedContacts: [GroupedContacts]
var body: some View {
ScrollView {
LazyVStack(spacing: 0, pinnedViews: [.sectionHeaders]) {
ForEach(groupedContacts, id: .letter) { group in
Section {
// Section content
ForEach(group.contacts) { contact in
ContactRow(contact: contact)
.padding(.horizontal)
.padding(.vertical, 8)
// Custom separator
if contact.id != group.contacts.last?.id {
Divider()
.padding(.leading, 68)
}
}
} header: {
// Optimized pinned header
SectionHeader(title: group.letter)
}
}
}
}
}
}
// Lightweight header for performance
struct SectionHeader: View {
let title: String
var body: some View {
Text(title)
.font(.headline)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal)
.padding(.vertical, 8)
.background(.ultraThinMaterial)
}
}
// Optimized grouping function
extension Array where Element == Contact {
func groupedByFirstLetter() -> [GroupedContacts] {
// Dictionary for O(n) grouping
var groups: [String: [Contact]] = [:]
for contact in self {
let letter = String(contact.name.prefix(1)).uppercased()
groups[letter, default: []].append(contact)
}
// Sort groups alphabetically
return groups
.map { GroupedContacts(letter: /bin/bash.key, contacts: /bin/bash.value) }
.sorted { /bin/bash.letter < .letter }
}
}Sabitlenmiş başlıklar (pinnedViews: [.sectionHeaders]) kaydırma sırasında görünür kalır ve uzun listelerde gezinmeyi iyileştirir.
Sayfalama ve Sonsuz Kaydırma
Büyük veri kümeleri için sayfalama tüm verileri belleğe yüklemekten kaçınır. Uygulama kullanıcı için şeffaf olmalıdır.
// ViewModel handling pagination
@Observable
final class PaginatedListViewModel {
private(set) var items: [Contact] = []
private(set) var isLoading = false
private(set) var hasMorePages = true
private var currentPage = 0
private let pageSize = 20
private let dataService: ContactDataService
init(dataService: ContactDataService) {
self.dataService = dataService
}
func loadInitialData() async {
guard items.isEmpty else { return }
await loadNextPage()
}
func loadMoreIfNeeded(currentItem: Contact) async {
// Trigger loading when approaching the end
guard let index = items.firstIndex(where: { /bin/bash.id == currentItem.id }) else {
return
}
// Load 5 items before the end
let thresholdIndex = items.count - 5
if index >= thresholdIndex {
await loadNextPage()
}
}
private func loadNextPage() async {
guard !isLoading, hasMorePages else { return }
isLoading = true
defer { isLoading = false }
do {
let newItems = try await dataService.fetchContacts(
page: currentPage,
limit: pageSize
)
items.append(contentsOf: newItems)
currentPage += 1
hasMorePages = newItems.count == pageSize
} catch {
// Handle error
}
}
}
// View with infinite scroll
struct InfiniteContactList: View {
@State private var viewModel: PaginatedListViewModel
init(dataService: ContactDataService) {
_viewModel = State(initialValue: PaginatedListViewModel(dataService: dataService))
}
var body: some View {
List {
ForEach(viewModel.items) { contact in
ContactRow(contact: contact)
.task {
// Check if more loading needed
await viewModel.loadMoreIfNeeded(currentItem: contact)
}
}
// Loading indicator at end of list
if viewModel.isLoading {
HStack {
Spacer()
ProgressView()
Spacer()
}
.padding()
}
}
.task {
await viewModel.loadInitialData()
}
}
}
// Protocol for data service
protocol ContactDataService {
func fetchContacts(page: Int, limit: Int) async throws -> [Contact]
}Bu desen, arayüzü engellemeden akıcı yükleme sağlar ve verimli bellek yönetimine olanak tanır.
iOS mülakatlarında başarılı olmaya hazır mısın?
İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.
Instruments ile Profilleme
Performans sorunlarını tanımlamak hassas ölçüm araçları gerektirir. Instruments, SwiftUI'ye uygun çeşitli şablonlar sunar.
// Measurement points for debugging
struct PerformanceMonitor {
// Measure view creation time
static func measureViewCreation<T: View>(
_ name: String,
@ViewBuilder content: () -> T
) -> T {
let start = CFAbsoluteTimeGetCurrent()
let view = content()
let elapsed = CFAbsoluteTimeGetCurrent() - start
#if DEBUG
if elapsed > 0.016 { // More than 16ms = frame drop
print("[(name)] View creation took (elapsed * 1000)ms")
}
#endif
return view
}
}
// Extension to trace renders
extension View {
func debugRender(_ label: String) -> some View {
#if DEBUG
let _ = Self._printChanges()
print("Rendering: (label)")
#endif
return self
}
func measureRender(_ label: String) -> some View {
modifier(RenderMeasureModifier(label: label))
}
}
struct RenderMeasureModifier: ViewModifier {
let label: String
@State private var renderCount = 0
func body(content: Content) -> some View {
content
.onAppear {
renderCount += 1
#if DEBUG
print("[(label)] Render count: (renderCount)")
#endif
}
}
}Instruments Optimizasyon Kontrol Listesi
Etkili SwiftUI liste profillemesi için:
- Time Profiler: en çok CPU tüketen fonksiyonları tanımlayın
- Allocations: kaydırma sırasında bellek artışını doğrulayın
- SwiftUI Instrument: body değerlendirmelerini görselleştirin
- Core Animation: kare düşüşlerini tespit edin
// Instrumented view for profiling
struct ProfiledContactList: View {
let contacts: [Contact]
var body: some View {
let _ = Self._printChanges() // Shows changes triggering re-render
List {
ForEach(contacts) { contact in
ContactRow(contact: contact)
.measureRender("ContactRow-(contact.id)")
}
}
}
}Bu SwiftUI hata ayıklama API'si, hangi özelliklerin değiştiğini ve body yeniden değerlendirmesini tetiklediğini konsola yazdırır. Gereksiz yeniden render'ları tanımlamak için vazgeçilmezdir.
drawingGroup ile İleri Düzey Optimizasyonlar
Birçok görsel efekt içeren karmaşık view'lar için drawingGroup(), view'ı bir Metal katmanına rasterize ederek performansı önemli ölçüde artırabilir.
// Cell with complex visual effects
struct ComplexVisualRow: View {
let item: Item
var body: some View {
HStack(spacing: 16) {
// Circle with gradient and shadow
Circle()
.fill(
RadialGradient(
colors: [.blue, .purple, .pink],
center: .center,
startRadius: 0,
endRadius: 25
)
)
.frame(width: 50, height: 50)
.shadow(color: .purple.opacity(0.5), radius: 8, y: 4)
VStack(alignment: .leading, spacing: 4) {
Text(item.name)
.font(.headline)
// Progress bar with gradient
GeometryReader { geometry in
Capsule()
.fill(Color.gray.opacity(0.2))
.overlay(alignment: .leading) {
Capsule()
.fill(
LinearGradient(
colors: [.green, .yellow, .orange],
startPoint: .leading,
endPoint: .trailing
)
)
.frame(width: geometry.size.width * item.progress)
}
}
.frame(height: 8)
}
}
.padding()
// Rasterization for performance
.drawingGroup()
}
}
// List using optimized cells
struct OptimizedComplexList: View {
let items: [Item]
var body: some View {
ScrollView {
LazyVStack(spacing: 8) {
ForEach(items) { item in
ComplexVisualRow(item: item)
}
}
.padding()
}
}
}
struct Item: Identifiable {
let id: UUID
let name: String
let progress: Double
}drawingGroup(), gradyanlar, gölgeler ve bulanıklık efektlerini birleştiren view'larda özellikle etkilidir.
Kaynaklar
- WWDC26 SwiftUI Guide - AsyncImage önbelleğe alma ve reorderable API'leri dahil iOS 27 SwiftUI güncellemeleri hakkında resmi Apple belgeleri
- New SwiftUI APIs for Reordering and Drag and Drop on iOS 27 -
.reorderable()ve.reorderContainer(for:)modifier'larının ayrıntılı açıklaması - What's New in SwiftUI for iOS 27 - WWDC26 SwiftUI değişikliklerinin kapsamlı özeti
Sonuç
SwiftUI liste optimizasyonu, lazy loading, geri dönüşüm ve view karşılaştırma mekanizmalarının derin bir anlayışına dayanır. iOS 27, yerel AsyncImage önbelleğe alma ve konteynerden bağımsız swipe eylemleri ve yeniden sıralama API'leri ile önemli iyileştirmeler getirdi.
SwiftUI Performans Kontrol Listesi
- Koleksiyonlar için
VStackyerineLazyVStackveyaListkullanın - Kararlı, benzersiz ID'lerle
Identifiableuygulayın - Karmaşık hücreler için
Equatablebenimseyin - iOS 27'nin yerel AsyncImage önbelleğe almasını kullanın veya önceki sürümler için özel önbelleğe alma uygulayın
- Akıllı prefetching ile verileri önceden yükleyin
- Hücre geri dönüşümü için
Listveya özel düzenler içinLazyVStackseçin (her ikisi artık swipe eylemleri ve yeniden sıralamayı destekliyor) - Bölüm başlıkları için
pinnedViewskullanın - Büyük veri kümeleri için sayfalama uygulayın
- Düzenli olarak Instruments ile profilleyin
- Karmaşık görsel efektlere sahip view'lara
drawingGroup()uygulayın
Pratik yapmaya başla!
Mülakat simülatörleri ve teknik testlerle bilgini test et.
iOS kodundaki hatayı bulabilir misin?
Gerçek bir kod parçası, gizli bir hata, günde bir deneme. Denemek için hesap gerekmez.

Yazan:
Anthony Fillion-MailletSharpSkill kurucusu
10 yılı aşkın süredir fullstack geliştirici. SharpSkill’i yönetiyor ve burada yayımlanan her şeyden sorumlu.
19 Ağustos 2026 tarihinde güncellendi
Etiketler
Paylaş
İlgili makaleler

Özel SwiftUI ViewModifier'lar: Design System için yeniden kullanılabilir desenler
Tutarlı bir design system için SwiftUI'da özel ViewModifier'lar oluşturun. iOS view'lerini verimli şekilde stillendirmek için desenler, en iyi uygulamalar ve pratik örnekler.

SwiftUI @Observable vs @State: 2026'da Hangisini Ne Zaman Kullanmalı
iOS uygulamaları için doğru durum yönetimi aracını seçmek üzere SwiftUI'daki @Observable ve @State arasındaki farkları öğrenin.

2026'da SwiftUI ile CloudKit: cihazlar arası veri senkronizasyon desenleri
SwiftUI ile CloudKit senkronizasyonunu uygulamak için kapsamlı kılavuz: CKSyncEngine, SwiftData entegrasyonu, çakışma çözümü ve iOS 2026 için en iyi uygulamalar.