Ekranlar arası veri akışı: UIKit'te injection'dan SwiftUI'da @Observable'a
UIKit view controller'ları arasında veriyi singleton kullanmadan ileri ve geri taşımak, SwiftUI'da @Observable, @State, @Environment ve @Bindable ile state paylaşmak, değer tabanlı navigasyon ve Xcode 27'de @State başlangıç değerlerinde neyin değiştiği.
Bir uygulamaya ikinci ekran eklendiği anda verinin bir ekrandan diğerine geçmesi gerekiyor: bir liste detay ekranına bir sipariş veriyor, bir editör sonucu geri gönderiyor, birkaç ekran aynı sepeti gösteriyor. Verinin sahibi belli değilse belirtiler de tahmin edilebilir: kimse set etmediği için crash eden implicitly unwrapped property'ler, üst ve alt ekran arasında retain cycle'lar, güncellenmeyen ya da modelini yanlış zamanda yeniden oluşturan SwiftUI view'ları.
Özünde bu bir sahiplik ve dependency injection meselesi. Bu yazıda önce UIKit için önerdiğim kalıpları, sonra iOS 17'den beri SwiftUI tarafını ObservableObject dönemine göre çok daha sade hale getiren Observation framework'ünü, en sonda da @State'in başlangıç değerini değerlendirme biçimindeki 2026 değişikliği dahil tuzakları anlatıyorum.
Üç tür veri akışı
Üç ayrı durumu birbirinden ayırmak işe yarıyor:
- İleri: A ekranında bir
Ordervar, B ekranının onu göstermesi gerekiyor. - Geri: B ekranı bir şeyi düzenliyor ve A'nın sonucu alması gerekiyor.
- Paylaşılan: Birkaç ekran aynı state'i gösteriyor ve değiştiriyor, örneğin bir alışveriş sepeti.
Üçünü de aynı araçla, genelde bir singleton ya da NotificationCenter ile çözmek cazip geliyor. İkisi de çalışır, ikisi de altı ay sonra kodun takibini zorlaştırır. Her durum için daha uygun bir kalıp var.
UIKit: initializer'lar, closure'lar ve delegate'ler
İleri: initializer üzerinden injection
En temiz seçenek, derleyicinin kontrol edebildiği seçenek: bir let property ve bir initializer.
final class OrderDetailViewController: UIViewController {
private let order: Order
init(order: Order) {
self.order = order
super.init(nibName: nil, bundle: nil)
}
// Storyboard versiyonu, instantiateViewController(identifier:creator:) ile kullanılır
init?(coder: NSCoder, order: Order) {
self.order = order
super.init(coder: coder)
}
required init?(coder: NSCoder) {
fatalError("init(order:) ya da init(coder:order:) kullanın")
}
}
Ekran bir storyboard'daysa, sonradan doldurulan implicitly unwrapped bir var order: Order!'a artık ihtiyacınız yok. iOS 13'ten beri UIStoryboard sizin initializer'ınızı çağırabiliyor:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let detail = storyboard.instantiateViewController(identifier: "OrderDetail") { coder in
OrderDetailViewController(coder: coder, order: order)
}
navigationController?.pushViewController(detail, animated: true)
prepare(for:sender:) hâlâ çalışıyor ve segue ağırlıklı eski kodlarda gayet yeterli. Sorunu şu: property'yi nesne oluştuktan sonra dolduruyor, bu yüzden hedef ekranın aslında hiç olmaması gereken "boş" bir durumu ele alması gerekiyor. Yeni kodda kullanmamanızı öneririm.
Geri: closure mı, delegate mi
"Kullanıcı yeni bir başlık kaydetti" gibi tek bir sonuç için en az törenli yol closure:
let editor = EditTitleViewController(initialTitle: order.title)
editor.onSave = { [weak self] newTitle in
self?.rename(order, to: newTitle)
}
present(UINavigationController(rootViewController: editor), animated: true)
Alt ekran birbiriyle ilişkili birkaç olay bildiriyorsa (kaydedildi, iptal edildi, silindi, doğrulama başarısız oldu) delegate protokolü daha uygun. Sözleşmeyi tek bir yerde belgeliyor, weak var delegate de sahipliği açıkça gösteriyor:
protocol EditTitleViewControllerDelegate: AnyObject {
func editTitleViewController(_ controller: EditTitleViewController, didSave title: String)
func editTitleViewControllerDidCancel(_ controller: EditTitleViewController)
}
İki yaklaşımın da tek bir kuralı var: alt ekran üst ekranı hayatta tutmamalı. Delegate'i weak yapın, saklanan closure'larda [weak self] kullanın.
SwiftUI'da Observation (iOS 17+)
Observation framework'ü ve @Observable macro'su iOS 17 ile geldi ve bu sahiplik sorularının çoğunu çok daha basit hale getirdi. Geçiş yaparken şu eşleştirme iyi bir başlangıç noktası:
| Eskiden (iOS 13–16) | Şimdi (iOS 17+) |
|---|---|
class Model: ObservableObject + @Published var |
@Observable class Model, düz var ile |
@StateObject var model = Model() |
@State var model = Model() |
@ObservedObject var model: Model |
let model: Model ya da @Bindable var model: Model |
.environmentObject(model) + @EnvironmentObject |
.environment(model) + @Environment(Model.self) |
Küçük ama eksiksiz bir örnek: en üstte sahiplenilen ve environment üzerinden paylaşılan bir sepet.
import SwiftUI
import Observation
@Observable
final class CartItem: Identifiable {
let id = UUID()
var name: String
var quantity: Int
var note = ""
init(name: String, quantity: Int = 1) {
self.name = name
self.quantity = quantity
}
}
@Observable
final class Cart {
var items: [CartItem] = []
var totalQuantity: Int { items.reduce(0) { $0 + $1.quantity } }
}
@main
struct ShopApp: App {
@State private var cart = Cart() // sahibi burası
var body: some Scene {
WindowGroup {
CartScreen()
.environment(cart) // iOS 17+
}
}
}
struct CartScreen: View {
@Environment(Cart.self) private var cart // iOS 17+
var body: some View {
NavigationStack {
List(cart.items) { item in
NavigationLink(value: item.id) {
HStack {
Text(item.name)
Spacer()
Text("x\(item.quantity)")
}
}
}
.navigationDestination(for: UUID.self) { id in // iOS 16+
if let item = cart.items.first(where: { $0.id == id }) {
CartItemEditor(item: item)
}
}
.toolbar { Text("\(cart.totalQuantity) ürün") }
}
}
}
struct CartItemEditor: View {
@Bindable var item: CartItem // iOS 17+, $item.quantity'yi sağlar
var body: some View {
Form {
Stepper("Adet: \(item.quantity)", value: $item.quantity, in: 1...99)
TextField("Not", text: $item.note)
}
}
}
Altını çizmek istediğim birkaç nokta:
- Sadece okuyan alt view'lar wrapper istemez. Bir view
@Observablebir nesneyi yalnızca okuyorsa düz birletyeterli. SwiftUI,body'nin gerçekten okuduğu property'leri takip ediyor. @Bindablesadece binding içindir.Stepperya daTextFieldiçin$item.quantitygerektiğinde kullanın. Environment'tan gelen bir nesne içinbodyiçinde yerel olarak da oluşturabilirsiniz:@Bindable var cart = cart.- Değerlerle navigasyon yapın.
NavigationLink(value:)ilenavigationDestination(for:)birlikte kullanıldığında hedef ekran,Hashablebir değerden ihtiyaç anında oluşturuluyor. Bir ID geçirip paylaşılan modelde aramak, tek bir doğruluk kaynağı (single source of truth) bırakıyor.
Ayrıntılı takip ve iç içe nesneler
ObservableObject ile bir üst nesne yalnızca kendi @Published property'sine yeni bir değer atandığında değişiklik yayınlar. items nesnelerden oluşan bir array ise ve siz items[3].quantity'yi değiştirirseniz, array hâlâ aynı referansları tuttuğu için üst nesne hiçbir şey yayınlamaz ve liste güncellenmez. Klasik "array güncellenmiyor" problemi tam olarak bu.
Observation değişiklikleri her nesnede property bazında takip ediyor. item.quantity'yi okuyan satır o item'ın o property'sine bağımlı hale geliyor, değişince de satır güncelleniyor. İç içe değişiklikleri elle iletmenize artık gerek yok.
Tuzaklar
@State başlangıç değeri ve Xcode 27
Bu konu dikkat istiyor, çünkü davranış 2026'da değişti.
Xcode 26'ya kadar Apple'ın State dokümantasyonu, property'nin SwiftUI view'u her oluşturduğunda varsayılan değerini de oluşturduğunu söylüyor. SwiftUI storage olarak yalnızca ilk örneği tutuyor, ama üst view struct'ı yeniden oluşturduğu her seferde Model() yeniden değerlendiriliyor ve bu sık olabiliyor. @StateObject'te bu sorun hiç yoktu, çünkü başlangıç değerini autoclosure olarak alıyor. Bu yüzden @State private var model = ExpensiveModel() sinsi bir performans ve yan etki tuzağıydı.
Xcode 27 ile @State artık property wrapper değil, bir Swift macro'su. Apple'ın dokümantasyonu ve TN3211 teknik notu, başlangıç değerinin artık lazy olarak, view ilk oluşturulduğunda yalnızca bir kez değerlendirildiğini söylüyor. Davranış, derlediğiniz Xcode sürümüne bağlı. Yani hâlâ Xcode 26 kullanan bir ekip eski davranışla çalışıyor. TN3211 ayrıca kaynak uyumsuzluklarını da listeliyor. Bunlardan biri şu: bir @State property'nin inline varsayılan değeri varken init içinde de atama yaparsanız inline değer kazanıyor ve sizin atamanız yok sayılıyor.
İki toolchain'de de işe yarayan kurallarım:
@Statebaşlangıç değerlerini ucuz ve yan etkisiz tutun.- Ağ çağrısı, dosya erişimi ya da veritabanı kurulumu gibi pahalı işleri initializer'larda değil,
.task(iOS 15+) içinde yapın. - Model bir parametreye bağlıysa onu
.task(id:)içinde oluşturun. Böylece parametre değiştiğinde gerçekten yeni bir model oluşur:
@MainActor
@Observable
final class ProfileModel {
let userID: Int
var name = ""
init(userID: Int) { self.userID = userID }
func load() async { /* profili getir */ }
}
struct ProfileScreen: View {
let userID: Int
@State private var model: ProfileModel?
var body: some View {
Group {
if let model { ProfileContent(model: model) } else { ProgressView() }
}
.task(id: userID) {
let model = ProfileModel(userID: userID)
self.model = model
await model.load()
}
}
}
Parametreye bağlı modellerde kilit nokta bu: state, view kimliği (identity) başına bir kez oluşturulur. Sonradan farklı bir parametre geçirmek onu değiştirmez. Kimliği değiştirmeniz (.id(userID) ile) ya da yukarıdaki gibi modeli kendiniz yeniden oluşturmanız gerekir.
Diğer tuzaklar
- Sahibi olmadığınız nesne için
@State. Üst view bir model geçiriyorsa, alt view onu@Stateile sarmamalı. Bu, eski@StateObjectve@ObservedObjectkarışıklığının yeni isimlerle tekrarı. - Sahibi olmayan environment. Optional olmayan
@Environment(Cart.self), bir üst view'un.environment(cart)çağırdığını varsayar. Çağıran yoksa bunu derleme zamanında değil, çalışma zamanında öğrenirsiniz. Bu genelde preview'larda başınıza gelir. Nesnenin olmaması meşru bir durumsa property'yiCart?olarak tanımlayın. Apple, optional versiyonunnildöndürdüğünü belgeliyor. - İki dünyayı karıştırmak.
@Stateiçinde saklanan birObservableObject, view'u yalnızca referans değiştiğinde günceller,@Publishedproperty'leri değiştiğinde değil. Bir modeli taşıyacaksanız tamamen taşıyın.
Özet
- UIKit ileri yön: initializer injection. Storyboard için
instantiateViewController(identifier:creator:)(iOS 13+). - UIKit geri yön: tek sonuç için closure, çok olay için
weakdelegate. - SwiftUI, iOS 17+: sahibinde
@Observable+@State, paylaşmak için.environment/@Environment(Type.self), sadece binding gereken yerde@Bindable. NavigationStackvenavigationDestination(for:)(iOS 16+) ile değerlerle navigasyon yapın.@Statebaşlangıç değerlerini ucuz tutun, ağır kurulumu.taskya da.task(id:)içine taşıyın ve Xcode 27'nin bu değerin ne zaman değerlendirildiğini değiştirdiğini unutmayın.
Veriyi ekranlar arasında taşımak büyük ölçüde verinin sahibinin kim olduğu meselesi. O netleştiğinde doğru API genelde kendiliğinden belli oluyor.
- ---
- baslik: "Ekranlar arası veri akışı: UIKit'te injection'dan SwiftUI'da @Observable'a"
- ozet: "UIKit view controller'ları arasında veriyi singleton kullanmadan ileri ve geri taşımak, SwiftUI'da @Observable, @State, @Environment ve @Bindable ile state paylaşmak, değer tabanlı navigasyon ve Xcode 27'de @State başlangıç değerlerinde neyin değiştiği."
- tarih: 2026-09-03
- etiketler:
- - ios
- - swift
- - uikit
- - swiftui
- kategoriler:
- - ios
- ---
- Bir uygulamaya ikinci ekran eklendiği anda verinin bir ekrandan diğerine geçmesi gerekiyor: bir liste detay ekranına bir sipariş veriyor, bir editör sonucu geri gönderiyor, birkaç ekran aynı sepeti gösteriyor. Verinin sahibi belli değilse belirtiler de tahmin edilebilir: kimse set etmediği için crash eden implicitly unwrapped property'ler, üst ve alt ekran arasında retain cycle'lar, güncellenmeyen ya da modelini yanlış zamanda yeniden oluşturan SwiftUI view'ları.
- Özünde bu bir sahiplik ve dependency injection meselesi. Bu yazıda önce UIKit için önerdiğim kalıpları, sonra iOS 17'den beri SwiftUI tarafını `ObservableObject` dönemine göre çok daha sade hale getiren Observation framework'ünü, en sonda da `@State`'in başlangıç değerini değerlendirme biçimindeki 2026 değişikliği dahil tuzakları anlatıyorum.
- ## Üç tür veri akışı
- Üç ayrı durumu birbirinden ayırmak işe yarıyor:
- 1. **İleri:** A ekranında bir `Order` var, B ekranının onu göstermesi gerekiyor.
- 2. **Geri:** B ekranı bir şeyi düzenliyor ve A'nın sonucu alması gerekiyor.
- 3. **Paylaşılan:** Birkaç ekran aynı state'i gösteriyor ve değiştiriyor, örneğin bir alışveriş sepeti.
- Üçünü de aynı araçla, genelde bir singleton ya da `NotificationCenter` ile çözmek cazip geliyor. İkisi de çalışır, ikisi de altı ay sonra kodun takibini zorlaştırır. Her durum için daha uygun bir kalıp var.
- ## UIKit: initializer'lar, closure'lar ve delegate'ler
- ### İleri: initializer üzerinden injection
- En temiz seçenek, derleyicinin kontrol edebildiği seçenek: bir `let` property ve bir initializer.
- ```swift
- final class OrderDetailViewController: UIViewController {
- private let order: Order
- init(order: Order) {
- self.order = order
- super.init(nibName: nil, bundle: nil)
- }
- // Storyboard versiyonu, instantiateViewController(identifier:creator:) ile kullanılır
- init?(coder: NSCoder, order: Order) {
- self.order = order
- super.init(coder: coder)
- }
- required init?(coder: NSCoder) {
- fatalError("init(order:) ya da init(coder:order:) kullanın")
- }
- }
- ```
- Ekran bir storyboard'daysa, sonradan doldurulan implicitly unwrapped bir `var order: Order!`'a artık ihtiyacınız yok. iOS 13'ten beri `UIStoryboard` sizin initializer'ınızı çağırabiliyor:
- ```swift
- let storyboard = UIStoryboard(name: "Main", bundle: nil)
- let detail = storyboard.instantiateViewController(identifier: "OrderDetail") { coder in
- OrderDetailViewController(coder: coder, order: order)
- }
- navigationController?.pushViewController(detail, animated: true)
- ```
- `prepare(for:sender:)` hâlâ çalışıyor ve segue ağırlıklı eski kodlarda gayet yeterli. Sorunu şu: property'yi nesne oluştuktan sonra dolduruyor, bu yüzden hedef ekranın aslında hiç olmaması gereken "boş" bir durumu ele alması gerekiyor. Yeni kodda kullanmamanızı öneririm.
- ### Geri: closure mı, delegate mi
- "Kullanıcı yeni bir başlık kaydetti" gibi tek bir sonuç için en az törenli yol closure:
- ```swift
- let editor = EditTitleViewController(initialTitle: order.title)
- editor.onSave = { [weak self] newTitle in
- self?.rename(order, to: newTitle)
- }
- present(UINavigationController(rootViewController: editor), animated: true)
- ```
- Alt ekran birbiriyle ilişkili birkaç olay bildiriyorsa (kaydedildi, iptal edildi, silindi, doğrulama başarısız oldu) delegate protokolü daha uygun. Sözleşmeyi tek bir yerde belgeliyor, `weak var delegate` de sahipliği açıkça gösteriyor:
- ```swift
- protocol EditTitleViewControllerDelegate: AnyObject {
- func editTitleViewController(_ controller: EditTitleViewController, didSave title: String)
- func editTitleViewControllerDidCancel(_ controller: EditTitleViewController)
- }
- ```
- İki yaklaşımın da tek bir kuralı var: alt ekran üst ekranı hayatta tutmamalı. Delegate'i `weak` yapın, saklanan closure'larda `[weak self]` kullanın.
- ## SwiftUI'da Observation (iOS 17+)
- Observation framework'ü ve `@Observable` macro'su iOS 17 ile geldi ve bu sahiplik sorularının çoğunu çok daha basit hale getirdi. Geçiş yaparken şu eşleştirme iyi bir başlangıç noktası:
- | Eskiden (iOS 13–16) | Şimdi (iOS 17+) |
- |---|---|
- | `class Model: ObservableObject` + `@Published var` | `@Observable class Model`, düz `var` ile |
- | `@StateObject var model = Model()` | `@State var model = Model()` |
- | `@ObservedObject var model: Model` | `let model: Model` ya da `@Bindable var model: Model` |
- | `.environmentObject(model)` + `@EnvironmentObject` | `.environment(model)` + `@Environment(Model.self)` |
- Küçük ama eksiksiz bir örnek: en üstte sahiplenilen ve environment üzerinden paylaşılan bir sepet.
- ```swift
- import SwiftUI
- import Observation
- @Observable
- final class CartItem: Identifiable {
- let id = UUID()
- var name: String
- var quantity: Int
- var note = ""
- init(name: String, quantity: Int = 1) {
- self.name = name
- self.quantity = quantity
- }
- }
- @Observable
- final class Cart {
- var items: [CartItem] = []
- var totalQuantity: Int { items.reduce(0) { $0 + $1.quantity } }
- }
- @main
- struct ShopApp: App {
- @State private var cart = Cart() // sahibi burası
- var body: some Scene {
- WindowGroup {
- CartScreen()
- .environment(cart) // iOS 17+
- }
- }
- }
- struct CartScreen: View {
- @Environment(Cart.self) private var cart // iOS 17+
- var body: some View {
- NavigationStack {
- List(cart.items) { item in
- NavigationLink(value: item.id) {
- HStack {
- Text(item.name)
- Spacer()
- Text("x\(item.quantity)")
- }
- }
- }
- .navigationDestination(for: UUID.self) { id in // iOS 16+
- if let item = cart.items.first(where: { $0.id == id }) {
- CartItemEditor(item: item)
- }
- }
- .toolbar { Text("\(cart.totalQuantity) ürün") }
- }
- }
- }
- struct CartItemEditor: View {
- @Bindable var item: CartItem // iOS 17+, $item.quantity'yi sağlar
- var body: some View {
- Form {
- Stepper("Adet: \(item.quantity)", value: $item.quantity, in: 1...99)
- TextField("Not", text: $item.note)
- }
- }
- }
- ```
- Altını çizmek istediğim birkaç nokta:
- - **Sadece okuyan alt view'lar wrapper istemez.** Bir view `@Observable` bir nesneyi yalnızca okuyorsa düz bir `let` yeterli. SwiftUI, `body`'nin gerçekten okuduğu property'leri takip ediyor.
- - **`@Bindable` sadece binding içindir.** `Stepper` ya da `TextField` için `$item.quantity` gerektiğinde kullanın. Environment'tan gelen bir nesne için `body` içinde yerel olarak da oluşturabilirsiniz: `@Bindable var cart = cart`.
- - **Değerlerle navigasyon yapın.** `NavigationLink(value:)` ile `navigationDestination(for:)` birlikte kullanıldığında hedef ekran, `Hashable` bir değerden ihtiyaç anında oluşturuluyor. Bir ID geçirip paylaşılan modelde aramak, tek bir doğruluk kaynağı (single source of truth) bırakıyor.
- ### Ayrıntılı takip ve iç içe nesneler
- `ObservableObject` ile bir üst nesne yalnızca kendi `@Published` property'sine yeni bir değer atandığında değişiklik yayınlar. `items` nesnelerden oluşan bir array ise ve siz `items[3].quantity`'yi değiştirirseniz, array hâlâ aynı referansları tuttuğu için üst nesne hiçbir şey yayınlamaz ve liste güncellenmez. Klasik "array güncellenmiyor" problemi tam olarak bu.
- Observation değişiklikleri her nesnede property bazında takip ediyor. `item.quantity`'yi okuyan satır o item'ın o property'sine bağımlı hale geliyor, değişince de satır güncelleniyor. İç içe değişiklikleri elle iletmenize artık gerek yok.
- ## Tuzaklar
- ### @State başlangıç değeri ve Xcode 27
- Bu konu dikkat istiyor, çünkü davranış 2026'da değişti.
- Xcode 26'ya kadar Apple'ın `State` dokümantasyonu, property'nin *SwiftUI view'u her oluşturduğunda varsayılan değerini de oluşturduğunu* söylüyor. SwiftUI storage olarak yalnızca ilk örneği tutuyor, ama üst view struct'ı yeniden oluşturduğu her seferde `Model()` yeniden değerlendiriliyor ve bu sık olabiliyor. `@StateObject`'te bu sorun hiç yoktu, çünkü başlangıç değerini autoclosure olarak alıyor. Bu yüzden `@State private var model = ExpensiveModel()` sinsi bir performans ve yan etki tuzağıydı.
- Xcode 27 ile `@State` artık property wrapper değil, bir Swift macro'su. Apple'ın dokümantasyonu ve TN3211 teknik notu, başlangıç değerinin artık lazy olarak, view ilk oluşturulduğunda yalnızca bir kez değerlendirildiğini söylüyor. Davranış, derlediğiniz Xcode sürümüne bağlı. Yani hâlâ Xcode 26 kullanan bir ekip eski davranışla çalışıyor. TN3211 ayrıca kaynak uyumsuzluklarını da listeliyor. Bunlardan biri şu: bir `@State` property'nin inline varsayılan değeri varken `init` içinde de atama yaparsanız inline değer kazanıyor ve sizin atamanız yok sayılıyor.
- İki toolchain'de de işe yarayan kurallarım:
- - `@State` başlangıç değerlerini ucuz ve yan etkisiz tutun.
- - Ağ çağrısı, dosya erişimi ya da veritabanı kurulumu gibi pahalı işleri initializer'larda değil, `.task` (iOS 15+) içinde yapın.
- - Model bir parametreye bağlıysa onu `.task(id:)` içinde oluşturun. Böylece parametre değiştiğinde gerçekten yeni bir model oluşur:
- ```swift
- @MainActor
- @Observable
- final class ProfileModel {
- let userID: Int
- var name = ""
- init(userID: Int) { self.userID = userID }
- func load() async { /* profili getir */ }
- }
- struct ProfileScreen: View {
- let userID: Int
- @State private var model: ProfileModel?
- var body: some View {
- Group {
- if let model { ProfileContent(model: model) } else { ProgressView() }
- }
- .task(id: userID) {
- let model = ProfileModel(userID: userID)
- self.model = model
- await model.load()
- }
- }
- }
- ```
- Parametreye bağlı modellerde kilit nokta bu: state, view kimliği (identity) başına bir kez oluşturulur. Sonradan farklı bir parametre geçirmek onu değiştirmez. Kimliği değiştirmeniz (`.id(userID)` ile) ya da yukarıdaki gibi modeli kendiniz yeniden oluşturmanız gerekir.
- ### Diğer tuzaklar
- - **Sahibi olmadığınız nesne için `@State`.** Üst view bir model geçiriyorsa, alt view onu `@State` ile sarmamalı. Bu, eski `@StateObject` ve `@ObservedObject` karışıklığının yeni isimlerle tekrarı.
- - **Sahibi olmayan environment.** Optional olmayan `@Environment(Cart.self)`, bir üst view'un `.environment(cart)` çağırdığını varsayar. Çağıran yoksa bunu derleme zamanında değil, çalışma zamanında öğrenirsiniz. Bu genelde preview'larda başınıza gelir. Nesnenin olmaması meşru bir durumsa property'yi `Cart?` olarak tanımlayın. Apple, optional versiyonun `nil` döndürdüğünü belgeliyor.
- - **İki dünyayı karıştırmak.** `@State` içinde saklanan bir `ObservableObject`, view'u yalnızca referans değiştiğinde günceller, `@Published` property'leri değiştiğinde değil. Bir modeli taşıyacaksanız tamamen taşıyın.
- ## Özet
- 1. UIKit ileri yön: initializer injection. Storyboard için `instantiateViewController(identifier:creator:)` (iOS 13+).
- 2. UIKit geri yön: tek sonuç için closure, çok olay için `weak` delegate.
- 3. SwiftUI, iOS 17+: sahibinde `@Observable` + `@State`, paylaşmak için `.environment` / `@Environment(Type.self)`, sadece binding gereken yerde `@Bindable`.
- 4. `NavigationStack` ve `navigationDestination(for:)` (iOS 16+) ile değerlerle navigasyon yapın.
- 5. `@State` başlangıç değerlerini ucuz tutun, ağır kurulumu `.task` ya da `.task(id:)` içine taşıyın ve Xcode 27'nin bu değerin ne zaman değerlendirildiğini değiştirdiğini unutmayın.
- Veriyi ekranlar arasında taşımak büyük ölçüde verinin sahibinin kim olduğu meselesi. O netleştiğinde doğru API genelde kendiliğinden belli oluyor.