Source
Затишна Галера | iOSКомпас 🧭1️⃣5️⃣7️⃣ Завдання 157Життєвий цикл SwiftUI View: коли `onA...
569 Views/Reach
2026-06-03 10:07
Message №2693
#iOSКомпас 🧭1️⃣5️⃣7️⃣ Завдання 157Життєвий цикл SwiftUI View: коли `onAppear` викликається не так, як очікували?З вами знову той самий автор каналу @badlinkschannel. Сподіваюсь вам ще не набридла ця ідея капітана галери про iOS розробника.🤔 На перший погляд, `onAppear` здається простим і зрозумілим: показалася view - спрацювало, сховалася - спрацювало `onDisappear`. Але на практиці все складніше. Залежно від того, де лежить view - у `TabView`, `NavigationStack` або `List` - поведінка може кардинально відрізнятися. І якщо не розуміти цих нюансів, можна зловити баги, які важко відтворити.🔣 Дві різні системи:Щоб зрозуміти, що відбувається, треба розділити два поняття: час життя вузла (`node lifetime`) і видимість на екрані (`visibility`). Вузол живе, поки існує ідентичність view. До вузла привʼязані `@State` і `@StateObject`. А видимість - це те, чи бачить користувач view прямо зараз. `onAppear` і `onDisappear` реагують не на створення struct, а на появу або зникнення view у SwiftUI-ієрархії.У простих випадках ці дві шкали збігаються. Але у складних контейнерах - розʼїжджаються.🔣 `TabView` - вузли живуть, видимість змінюється:Коли ви перемикаєте вкладки, view зазвичай не знищуються повністю. Вони залишаються повʼязаними зі своєю ідентичністю, змінюється лише те, яка вкладка зараз активна. Тому `onAppear` може спрацьовувати при кожному поверненні на вкладку, але `@State` при цьому зберігає своє значення.Нюанс: на iOS 17 і iOS 18+ TabView може поводитися по-різному. У частині сценаріїв iOS 17 створював вкладки більш eager, а в iOS 18+ поведінка стала ближчою до lazy-створення при першому відкритті. Один і той самий код може поводитися по-різному на різних версіях.🔣 `NavigationStack` - при поверненні вузол знищується:У навігаційному стеку при поверненні назад (`pop`) детальна view прибирається зі стеку. Її ідентичність зникає, а локальний `@State` скидається. При повторному переході створюється новий вузол. На відміну від `TabView`, тут дані не варто очікувати збереженими між показами, якщо вони живуть тільки всередині detail view.🔣 `List` і `LazyVStack` - вузли залежать від скролу:У лінивих списках елементи створюються тоді, коли вони потрібні для відображення, і їхній lifecycle залежить від контейнера, скролу, ідентичності та версії системи. Тому `@State` всередині комірки не можна використовувати як місце для важливих даних. При повторній появі комірки цей стан може виявитися не тим, який ви очікували.Дані комірки мають жити вище: у моделі, view model або parent state. Комірка - це відображення, а не сейф для стану.🔣 Головне правило - `body` виконується до `onAppear`:body обчислюється до того, як спрацює onAppear або .task. Не можна розраховувати, що в onAppear дані підвантажаться до першого виклику body. Завжди треба мати початковий стан: placeholder, loading, empty або fallback.#️⃣ onAppear у SwiftUI - це не «view створена», а «view зʼявилася у lifecycle SwiftUI». Час життя вузла і видимість - це дві різні шкали. Розуміння цього рятує від багів, коли @State раптом не скидається або, навпаки, скидається не вчасно. Різні контейнери поводяться по-різному, і це треба тестувати, а не вгадувати.@Zatishna_Galera