90. Прячем вьюху со знанием дела
Кто регулярно верстает на SwiftUI, знает как скрыть вьюху при необходимости. В этой статье покажу несколько способов как это сделать, и возможные неочевидные последствия, о которых нужно знать.
План
- Сделаем класс, который будет символизировать сервис, делающий важную работу для экрана (можно назвать вьюмоделью). Для удобства дебага будем хранить в классе константу (
Int) - Для наглядности будем делать принты при создании (инициализация) и удалении (деинициализация) этого класса
- Сверстаем вьюху, которая будет хранить в себе этот класс-вьюмодель в качестве
@StateObject, и выводить на экран текст с номером из этого класса - Сверстаем родительскую вьюху, которая будет символизировать большой и сложный экран. На этом экране будет кнопка для скрытия вьюхи из шага 3
- Накидаем в родительскую вьюху несколько вьюх из шага 3 и будем скрывать их разными способами
- Сделаем выводы
Реализация
Класс-вьюмодель
final class TestViewModel: ObservableObject {
let number: Int
init(number: Int) {
self.number = number
print("создаем \(number)")
}
deinit {
print("удаляем \(number)")
}
}
Используем ObservableObject для образа - нужно помнить, что внутри там могут быть самые разные @Published-свойства, состояния которых нам важны.
Вьюха с вьюмоделью
struct DemoSubview123: View {
@StateObject private var viewModel: TestViewModel
init(number: Int) {
self._viewModel = .init(wrappedValue: .init(number: number))
}
var body: some View {
Text("Вьюха № \(viewModel.number)")
}
}
Обычная вьюха с вьюмоделью, которая должна создаваться только 1 раз, после чего эту вьюмодель нельзя изменить без осложнений (о чем можно почитать в документации).
Демо экран
struct DemoLargeContentView: View {
@State private var showSubview = true
var body: some View {
VStack(spacing: 20) {
Button("\(showSubview ? "Скрыть" : "Показать")") {
withAnimation {
showSubview.toggle()
}
}
VStack(spacing: 20) {
if showSubview {
DemoSubview123(number: 1)
.background(.green.opacity(0.5))
}
DemoSubview123(number: 2)
.opacity(showSubview ? 1 : 0)
DemoSubview123(number: 3)
.frame(
width: showSubview ? nil : 0,
height: showSubview ? nil : 0
)
}
}
}
}
В нашем примере есть 3 способа скрыть вьюху:
При желании, конечно, можно скрыть вьюху при помощи ZStack, но тогда эксперимент будет не таким интересным на мой взгляд.
Тестируем реализацию
Запускаем превью, видим в консоли принты:
создаем 1 создаем 2 создаем 3
Все как и задумано: три вьюхи создали по одной вьюмодели.
Жмем на кнопку "Скрыть", в консоли появляется одна строка:
удаляем 1
На этом моменте вспоминаем, что вьюмодель держит в себе важные @Published-свойства, каждое из которых теперь обнулилось. Там могли быть данные, которые долго загружались, или которые нигде не сохранены, но нужны пользователю.
Жмем на кнопку "Показать", в консоли появляется одна строка:
создаем 1
Если при инициализации вьюмодели или при "первом" появлении вьюхи у нас выполняется какая-то логика, то в этот момент она стартовала, и это важно понимать.
Бонус 1
Что если нас устраивает поведение вьюмоделей, и главное UI?
Добавим для каждой вьюхи разный цвет в бэкграунд, а на стек с вьюхами добавим бордюр, и нажмем "Скрыть", получим такой результат - дело во второй вьюхе, которая скрывается через прозрачность. Если убрать вторую вьюху, то весь VStack будет корректно скрыт.
Бонус 2
Что если нам очень хочется скрыть вьюху через if/else, но нужно сохранить все данные во вьюмодели?
Все очень просто: достаточно вынести источник истины за пределы этой вьюхи и if/else, главное чтобы источник истины (другая вьюмодель или стейт-свойства) стабильно существовали при изменении условия для скрытия вьюхи, например:
struct DemoParentView: View {
@StateObject private var viewModel = TestViewModel(number: 1)
@State private var showSubview = true
var body: some View {
VStack(spacing: 20) {
Button("\(showSubview ? "Скрыть" : "Показать")") {
withAnimation {
showSubview.toggle()
}
}
if showSubview {
Text("Вьюха № \(viewModel.number)")
}
}
}
}
Заключение
Важно помнить, что скрытие вьюхи при помощи if/else приводит к ее пересозданию с нуля, поэтому нужно либо быть готовыми к потере данных на ней, либо использовать ее для отображения данных из родительской вьюхи.
Если у кого-то чуть-чуть поедет верстка из-за скрытия при помощи прозрачности, то ничего страшного не случится. Зато будет повод пообщаться с дизайнерами/тестировщиками 😁
Код для этой статьи можно посмотреть тут, другие статьи по разработке - тут, а про инвестиции - тут.