Вопрос или проблема
Я разрабатываю индивидуальный подход к обработке DisclosureGroups из-за ограничений, с которыми я сталкиваюсь с текущим путем DisclosureGroupStyle
. У меня все так, как я хочу с визуальной и функциональной точки зрения, за исключением одного – анимации появления/исчезновения содержимого.
Я создал демонстрационный проект, чтобы показать упрощенную версию проблемы.
struct SectionView: View {
let title: String
let data: [String]
@State private var isExpanded: Bool = true
var body: some View {
VStack {
HStack {
Text(title)
.font(.title2)
.fontWeight(.semibold)
Text(data.count.description)
.font(.body)
.fontWeight(.light)
.foregroundStyle(.secondary)
Spacer()
Button {
self.isExpanded.toggle()
} label: {
Label("развернуть", systemImage: "chevron.down")
.labelStyle(.iconOnly)
.rotationEffect(.degrees(isExpanded ? 0 : -90))
.animation(.easeInOut(duration: 0.2), value: isExpanded)
}
}
.padding(.horizontal, 16)
Divider()
VStack {
if isExpanded {
ForEach(data, id: \.self) { item in
Text(item)
}
}
}
}
}
}
struct ContentView: View {
let data: [String: [String]] = [
"Секция A": ["Элемент A.1", "Элемент A.2", "Элемент A.3", "Элемент A.4", "Элемент A.5" ],
"Секция B": ["Элемент B.1", "Элемент B.2"],
"Секция C": ["Элемент C.1", "Элемент C.2", "Элемент C.3", "Элемент C.4"]
]
var body: some View {
NavigationStack {
VStack {
List {
ForEach(Array(data.keys).sorted(by: { $0 < $1 }), id: \.self) { key in
SectionView(title: key, data: data[key]!)
}
}
.padding()
.listStyle(.inset)
.navigationTitle("Пример проекта")
}
}
}
}
#Preview {
ContentView()
}
Когда я нажимаю кнопку переключения, содержимое исчезает мгновенно из-за условной проверки, в которую я обернул ForEach
. Есть ли способ анимировать содержимое цикла ForEach, поднимаясь вверх к Divider
и исчезая?
Я попробовал следующее внизу VStack
, но, похоже, он не осознает, что содержимое исчезает, поэтому не анимируется.
.animation(.easeOut(duration: 0.2), value: isExpanded)
Divider()
VStack {
if isExpanded {
ForEach(data, id: \.self) { item in
Text(item)
}
}
}.animation(.easeOut(duration: 0.2), value: isExpanded)
Буду признателен за любой совет по этому вопросу – спасибо!
Ответ или решение
Для реализации анимированного показа и скрытия содержимого ForEach
в SwiftUI можно использовать несколько подходов. В данном случае, основная проблема заключается в том, что обычное условное выражение if
не поддерживает анимацию при смене состояния isExpanded
. Вместо этого можно использовать метод withAnimation
и анимацию Opacity
для достижения желаемого эффекта.
Шаги по реализации анимации
Вот как можно модифицировать ваш код, чтобы достичь плавного появления и исчезновения содержимого в ForEach
:
1. Изменение состояния с анимацией
Вместо того чтобы менять состояние видимости ForEach
с помощью if
, воспользуемся анимацией для изменения непрозрачности элементов внутри VStack
.
2. Код
Ниже приводится обновленный пример вашего SectionView
, который включает плавную анимацию скрытия и показа элементов списка:
struct SectionView: View {
let title: String
let data: [String]
@State private var isExpanded: Bool = true
var body: some View {
VStack {
HStack {
Text(title)
.font(.title2)
.fontWeight(.semibold)
Text(data.count.description)
.font(.body)
.fontWeight(.light)
.foregroundStyle(.secondary)
Spacer()
Button {
withAnimation(.easeInOut(duration: 0.2)) {
self.isExpanded.toggle()
}
} label: {
Label("expand", systemImage: "chevron.down")
.labelStyle(.iconOnly)
.rotationEffect(.degrees(isExpanded ? 0 : -90))
.animation(.easeInOut, value: isExpanded)
}
}
.padding(.horizontal, 16)
Divider()
VStack {
ForEach(data, id: \.self) { item in
Text(item)
.transition(.opacity)
.animation(.easeOut(duration: 0.2), value: isExpanded)
.opacity(isExpanded ? 1 : 0) // Изменение непрозрачности
}
}
.animation(.easeOut(duration: 0.2), value: isExpanded) // Анимация при изменении состояния
}
}
}
3. Объяснение изменений:
-
withAnimation
: Окруживself.isExpanded.toggle()
сwithAnimation
, вы вводите возможность анимации смены состояния. -
opacity
: Добавление изменения непрозрачности для текстового элемента позволяет управлять его видимостью с плавным переходом. КогдаisExpanded
становитсяfalse
, opacity становится0
, и элемент плавно исчезает. -
transition(.opacity)
: Использованиеtransition
позволяет настраивать анимацию появления и исчезновения. В данном случае мы указываем анимацию с изменением непрозрачности.
Заключение
Эти изменения помогут вам достичь плавной анимации при скрытии и показе элементов в ForEach
. Такой подход делает интерфейс более отзывчивым и приятным для пользователей. Не забывайте тестировать свою реализацию на разных устройствах, чтобы убедиться, что анимация выглядит хорошо.