72. Отслеживаем видимость вьюхи
Иногда нужно знать наверняка, видна ли вьюха на экране. При этом обычные модификаторы onAppear + onDisappear не решают задачу. В этой статье покажу вариант решения при помощи GeometryReader, в котором будет лежать ScrollView. Бонусом будет камень в огород SwiftUI Preview 👩🚀
Демо
Стартовый код
struct OnVisibilityChangeExample: View {
@State private var isBottomElementVisible = false
var body: some View {
ScrollView {
LazyVStack(spacing: 24) {
ForEach(0..<20) { number in
Capsule()
.fill(.green.opacity(0.5))
.frame(width: 150, height: 70)
.overlay {
Text("\(number)")
}
}
Text("самый нижний элемент") // <- будем отслеживать его
}
}
.overlay {
Text("isBottomElementVisible: \(isBottomElementVisible)")
.bold()
.padding()
.background {
Rectangle()
.fill(.yellow.opacity(0.5))
}
}
}
}
Пробуем onAppear + onDisappear
Добавим два модификатора, остальной код прежний:
Text("самый нижний элемент")
.onAppear { isBottomElementVisible = true }
.onDisappear { isBottomElementVisible = false }
В таком сценарии, когда мы используем простые вьюхи и отслеживаем появление/исчезновение текста на экране, стандартные модификаторы onAppear/onDisappear вполне подходят.
Сравнение с GeometryReader
Теперь добавим еще одно стейт-свойство, поменяем текст на мелкий квадрат и посмотрим на разницу.
За решение поблагодарим автора этого комментария, наш код теперь выглядит так:
struct OnVisibilityChangeExample: View {
@State private var isBottomElementVisible = false
@State private var isBottomElementVisible2 = false // <- новое свойство
var body: some View {
GeometryReader { proxy in
ScrollView {
LazyVStack(spacing: 24) {
ForEach(0..<20) { number in
Capsule()
.fill(.green.opacity(0.5))
.frame(width: 150, height: 70)
.overlay {
Text("\(number)")
}
}
Rectangle() // <- поставили вместо текста
.frame(width: 5, height: 5)
.onAppear { isBottomElementVisible = true }
.onDisappear { isBottomElementVisible = false }
.onVisibilityChange(proxy: proxy) { isVisible in // <- новый модификатор
isBottomElementVisible2 = isVisible
}
}
}
}
.overlay {
VStack {
Text("isBottomElementVisible: \(isBottomElementVisible)")
Text("isBottomElementVisible2: \(isBottomElementVisible2)")
}
.bold()
.padding()
.background {
Rectangle()
.fill(.yellow.opacity(0.5))
}
}
}
}
Конкретно в моем случае более точный результат дает именно GeometryReader (на гифке это вариант №2), а onDisappear (вариант №1) вызывается слишком поздно, когда вьюху уже давно не видно на экране - и это проблема для моего кейса.
Заключение
Задача может казаться тривиальной, потому что есть стандартные модификаторы для её решения, но не всегда стандартные инструменты приводят к нужному результату, и в таких ситуациях опыт + желание разобраться приводят к успеху 😁
Кстати, onAppear/onDisappear очень плохо работают в превью, поэтому при использовании этих модификаторов желательно проверять UI на стимуляторе (видео для статьи записывал на симуляторе).
Код для этой статьи можно посмотреть тут, а другие статьи - тут.