iOS 16 ile gelen Layout protocol, SwiftUI'da tamamen custom container layout'lar olusturmayi mumkun kildi. HStack ve VStack yetmedigi noktalarda FlowLayout, MasonryLayout veya circular layout gibi ozel yerlesimler yazabilirsiniz. Bu rehberde Layout protocol'u derinlemesine inceleyecegiz.
Icindekiler
- Layout Protocol Nedir?
- Temel Yapisi
- FlowLayout Ornegi
- Masonry Grid Layout
- Circular Layout
- Animated Layout Transitions
- ViewThatFits ile Adaptive Layout
- AnyLayout ile Dynamic Switching
- Performans Ipuclari
- Best Practices
- Sonuc
1. Layout Protocol Nedir?
Layout protocol, iki temel metod gerektirir:
Metod | Amac | Ne Zaman Cagrilir |
|---|---|---|
`sizeThatFits` | Container'in boyutunu hesapla | Parent boyut sordugunda |
`placeSubviews` | Child view'leri konumla | Layout uygulanirken |
2. Temel Yapisi
swift
1import SwiftUI2 3struct SimpleHStack: Layout {4 var spacing: CGFloat = 85 6 func sizeThatFits(7 proposal: ProposedViewSize,8 subviews: Subviews,9 cache: inout ()10 ) -> CGSize {11 let sizes = subviews.map { $0.sizeThatFits(.unspecified) }12 let totalWidth = sizes.reduce(0) { $0 + $1.width }13 + CGFloat(max(0, subviews.count - 1)) * spacing14 let maxHeight = sizes.map(\.height).max() ?? 015 return CGSize(width: totalWidth, height: maxHeight)16 }17 18 func placeSubviews(19 in bounds: CGRect,20 proposal: ProposedViewSize,21 subviews: Subviews,22 cache: inout ()23 ) {24 var x = bounds.minX25 for subview in subviews {26 let size = subview.sizeThatFits(.unspecified)27 subview.place(28 at: CGPoint(x: x, y: bounds.midY),29 anchor: .leading,30 proposal: ProposedViewSize(size)31 )32 x += size.width + spacing33 }34 }35}36 37// Kullanim38struct ContentView: View {39 var body: some View {40 SimpleHStack(spacing: 12) {41 ForEach(0..<5) { i in42 Text("Item \(i)")43 .padding(8)44 .background(.blue.opacity(0.2))45 .cornerRadius(8)46 }47 }48 }49}3. FlowLayout Ornegi
Tag cloud veya chip listesi icin ideal:
swift
1struct FlowLayout: Layout {2 var spacing: CGFloat = 83 var lineSpacing: CGFloat = 84 5 func sizeThatFits(6 proposal: ProposedViewSize,7 subviews: Subviews,8 cache: inout ()9 ) -> CGSize {10 let containerWidth = proposal.width ?? .infinity11 var currentX: CGFloat = 012 var currentY: CGFloat = 013 var lineHeight: CGFloat = 014 var maxWidth: CGFloat = 015 16 for subview in subviews {17 let size = subview.sizeThatFits(.unspecified)18 19 if currentX + size.width > containerWidth && currentX > 0 {20 currentX = 021 currentY += lineHeight + lineSpacing22 lineHeight = 023 }24 25 currentX += size.width + spacing26 lineHeight = max(lineHeight, size.height)27 maxWidth = max(maxWidth, currentX - spacing)28 }29 30 return CGSize(width: maxWidth, height: currentY + lineHeight)31 }32 33 func placeSubviews(34 in bounds: CGRect,35 proposal: ProposedViewSize,36 subviews: Subviews,37 cache: inout ()38 ) {39 var currentX = bounds.minX40 var currentY = bounds.minY41 var lineHeight: CGFloat = 042 43 for subview in subviews {44 let size = subview.sizeThatFits(.unspecified)45 46 if currentX + size.width > bounds.maxX && currentX > bounds.minX {47 currentX = bounds.minX48 currentY += lineHeight + lineSpacing49 lineHeight = 050 }51 52 subview.place(53 at: CGPoint(x: currentX, y: currentY),54 anchor: .topLeading,55 proposal: ProposedViewSize(size)56 )57 58 currentX += size.width + spacing59 lineHeight = max(lineHeight, size.height)60 }61 }62}63 64// Tag Cloud kullanimi65struct TagCloudView: View {66 let tags = ["Swift", "SwiftUI", "iOS", "Xcode", "UIKit",67 "Combine", "Async/Await", "Core Data", "Metal",68 "ARKit", "HealthKit", "CloudKit"]69 70 var body: some View {71 FlowLayout(spacing: 8, lineSpacing: 8) {72 ForEach(tags, id: \.self) { tag in73 Text(tag)74 .font(.caption)75 .padding(.horizontal, 12)76 .padding(.vertical, 6)77 .background(.blue.opacity(0.1))78 .foregroundColor(.blue)79 .cornerRadius(16)80 }81 }82 .padding()83 }84}4. Circular Layout
swift
1struct CircularLayout: Layout {2 var radius: CGFloat = 1003 var startAngle: Angle = .degrees(0)4 5 func sizeThatFits(6 proposal: ProposedViewSize,7 subviews: Subviews,8 cache: inout ()9 ) -> CGSize {10 let diameter = radius * 2 + 5011 return CGSize(width: diameter, height: diameter)12 }13 14 func placeSubviews(15 in bounds: CGRect,16 proposal: ProposedViewSize,17 subviews: Subviews,18 cache: inout ()19 ) {20 let center = CGPoint(x: bounds.midX, y: bounds.midY)21 let angleStep = 2 * .pi / Double(subviews.count)22 23 for (index, subview) in subviews.enumerated() {24 let angle = startAngle.radians + angleStep * Double(index)25 let x = center.x + radius * cos(angle)26 let y = center.y + radius * sin(angle)27 28 subview.place(29 at: CGPoint(x: x, y: y),30 anchor: .center,31 proposal: .unspecified32 )33 }34 }35}5. Animated Layout Transitions
swift
1struct LayoutTransitionDemo: View {2 @State private var isCircular = false3 4 var body: some View {5 let layout = isCircular ?6 AnyLayout(CircularLayout(radius: 120)) :7 AnyLayout(FlowLayout())8 9 layout {10 ForEach(0..<8) { index in11 Circle()12 .fill(Color(hue: Double(index) / 8, saturation: 0.8, brightness: 0.9))13 .frame(width: 44, height: 44)14 .overlay(Text("\(index + 1)").foregroundColor(.white))15 }16 }17 .animation(.spring(duration: 0.6), value: isCircular)18 19 Button(isCircular ? "Flow Layout" : "Circular Layout") {20 isCircular.toggle()21 }22 }23}6. ViewThatFits ile Adaptive Layout
swift
1struct AdaptiveStack<Content: View>: View {2 @ViewBuilder var content: Content3 4 var body: some View {5 ViewThatFits {6 HStack { content }7 VStack { content }8 }9 }10}11 12// Kullanim — yatay sigmazsa dikeye gecer13struct ButtonBar: View {14 var body: some View {15 AdaptiveStack {16 Button("Kaydet") { }17 .buttonStyle(.borderedProminent)18 Button("Iptal") { }19 .buttonStyle(.bordered)20 Button("Sil") { }21 .buttonStyle(.bordered)22 .tint(.red)23 }24 }25}7. AnyLayout ile Dynamic Switching
swift
1struct DynamicLayoutDemo: View {2 @Environment(\.horizontalSizeClass) var sizeClass3 4 var layout: AnyLayout {5 sizeClass == .compact ?6 AnyLayout(VStackLayout(spacing: 12)) :7 AnyLayout(HStackLayout(spacing: 20))8 }9 10 var body: some View {11 layout {12 InfoCard(title: "Kullanicilar", value: "1.2M")13 InfoCard(title: "Indirme", value: "5.6M")14 InfoCard(title: "Gelir", value: "$2.3M")15 }16 .animation(.easeInOut, value: sizeClass)17 }18}8. Performans Ipuclari
Teknik | Aciklama |
|---|---|
**Cache kullanin** | makeCache/updateCache ile hesaplamalari onbellekleyin |
**Layout priority** | Onemli view'lere oncelik verin |
**Spacing** | subviews.spacing ile sistem spacing'ini kullanin |
**ProposedViewSize** | .zero, .unspecified, .infinity farklarini bilin |
swift
1struct CachedFlowLayout: Layout {2 struct CacheData {3 var sizes: [CGSize] = []4 }5 6 func makeCache(subviews: Subviews) -> CacheData {7 CacheData(sizes: subviews.map { $0.sizeThatFits(.unspecified) })8 }9 10 func sizeThatFits(11 proposal: ProposedViewSize,12 subviews: Subviews,13 cache: inout CacheData14 ) -> CGSize {15 // cache.sizes kullanarak hesapla — her seferinde yeniden olcme yapma16 let totalHeight = cache.sizes.reduce(0) { $0 + $1.height }17 let maxWidth = cache.sizes.map(\.width).max() ?? 018 return CGSize(width: maxWidth, height: totalHeight)19 }20 21 func placeSubviews(22 in bounds: CGRect,23 proposal: ProposedViewSize,24 subviews: Subviews,25 cache: inout CacheData26 ) {27 // cache.sizes kullan28 }29}9. Best Practices
- Once ViewThatFits deneyin — basit adaptive durumlar icin yeterli
- AnyLayout ile gecis yapin — animasyonlu layout degisimi icin
- Cache kullanin — tekrarlanan hesaplamalari onbelleege alin
- Spacing API'sini kullanin — subviews.spacing ile tutarli aralıklar
- Accessibility'i unutmayin — dynamic type ile test edin
ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.
Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
Okuyucu Ödülü
Tebrikler! Bu yaziyi sonuna kadar okudugun icin sana ozel bir hediyem var:
10. Sonuc
Layout protocol, SwiftUI'da custom yerlesim mantigi yazmanin en guclu yoludur. FlowLayout, masonry grid ve circular layout gibi yapilar ile standart HStack/VStack'in otesine gecebilirsiniz. ViewThatFits ve AnyLayout ile adaptive tasarimlar olusturarak her ekran boyutuna uyum saglayabilirsiniz.
Sonuc ve Oneriler
Custom Layout protocol, SwiftUI'nin yerlesim sistemini tamamen ozellestirmenize olanak tanir. Apple'in kendi HStack ve VStack implementasyonlarinin da ayni protocol uzerine insa edildigi dusunuldugunde, bu API'nin ne kadar guclu oldugu anlasilir. FlowLayout ile etiket sistemleri, masonry grid ile Pinterest tarzinda gorunum ve circular layout ile dashboard benzeri arayuzler olusturabilirsiniz.
Layout gelistirirken sizePlaceChildren dongu mantigini iyi kavramak kritik oneme sahiptir. Her child view'in ideal, minimum ve maximum boyutlarini sorgulayarak esnek ve responsive yerlesimler olusturun. LayoutValueKey ile child view'lardan ozel degerler alarak daha sofistike yerlesim algoritmalari implement edebilirsiniz.
Performans acsindan Layout protocol'u SwiftUI'nin render pipeline'ina dogal olarak entegre oldugu icin ek maliyet minimamaldir. Ancak cok sayida child view iceren layout'larda cache mekanizmasi kullanmayi ve gereksiz yeniden hesaplamalari onlemeyi ihmal etmeyin. ViewThatFits ve AnyLayout ile farkli ekran boyutlari icin otomatik layout gecisleri yaparak kullanici deneyimini en ust seviyeye tasiyabilirsiniz.

