SwiftUI анимирует появление дополнительных представлений при раскрытии родительского элемента
Я создал это расширяющееся представление, и в нем есть дочерние представления, которые анимировались неожиданным образом.
В анимации ниже...
В разделе "Атлас"...
Имена и круги изображений остаются на месте, когда родительский элемент сворачивается. Текст заголовка перемещается вниз, но имена и аватары исчезают, оставаясь на месте. (Имена и текст заголовка перемещаются относительно друг друга)
В разделе "Луна"...
Имена и аватары будут скрыты при сворачивании представления. (Имена и текст заголовка всегда находятся в одном и том же относительном месте)
В идеале я бы хотел, чтобы они действовали как раздел «Луна» для всех представлений.
Моё тело зрения сейчас такое...
VStack(alignment: .leading, spacing: 16) {
HStack {
VStack(alignment: .leading, spacing: 8) {
(
Text(session.startDate, style: .time)
+ Text(" - ")
+ Text(session.endDate, style: .time)
)
.font(.caption1)
Text(session.title)
.font(.headline6)
}
Spacer()
if session.canExpand {
if expanded {
Image(systemName: "chevron.up")
} else {
Image(systemName: "chevron.down")
}
}
}
if expanded {
ForEach(session.speakers) { speaker in
HStack {
Avatar(name: speaker.name, size: 24, imagePath: speaker.image)
Text("Test name \(Int.random(in: 1...30))")
.font(.body1)
}
}
}
}
Я пробовал изменить переход раздела «Аватар и имя», но это, похоже, не дало эффекта.
1 ответ
Лучший способ решить эту проблему, который я знаю, — это избавиться отif expanded
и безоговорочно включать строки динамиков, но помещать строки динамиков в контейнер с высотой кадра, равной 0, когда сеанс сворачивается. Добавьте модификатор обрезки во внешний вид сеанса (представление, которое рисует закругленный прямоугольник и тень), чтобы скрыть строки динамиков при сворачивании карточки. Вот результат:
Вот мойSessionView
код:
struct SessionView: View {
var session: Session
@Binding var expanded: Bool
var body: some View {
VStack(alignment: .leading, spacing: 0) {
VStack(alignment: .leading, spacing: 16) {
HStack {
VStack(alignment: .leading, spacing: 8) {
(
Text(session.startDate, style: .time)
+ Text(" - ")
+ Text(session.endDate, style: .time)
)
.font(.caption)
Text(session.title)
.font(.headline)
}
Spacer()
if session.canExpand {
Image(systemName: "chevron.down")
.rotationEffect(.degrees(expanded ? 180 : 360))
}
}
}
VStack(alignment: .leading, spacing: 16) {
Spacer().frame(height: 0)
ForEach(session.speakers) { speaker in
SpeakerRow(speaker: speaker)
}
}
.frame(height: expanded ? nil : 0, alignment: .top)
}
.padding()
.contentShape(shape)
.clipShape(shape)
.onTapGesture {
if session.canExpand {
expanded.toggle()
}
}
.background {
shape
.fill(.white)
.padding(3)
.shadow(radius: 2, x: 0, y: 1)
}
}
private var shape: some Shape {
RoundedRectangle(cornerRadius: 10, style: .continuous)
}
}
Вот основные моменты, которые следует отметить в моем коде:
Я безоговорочно включаю ряды динамиков.
Я заворачиваю ряды динамиков в свои. Это имеет
frame
модификатор с нулевой высотой, если сеанс не расширен.Я применяю
clipShape
модификатор внешнегоVStack
чтобы строки динамиков обрезались при сворачивании сеанса.
Вот остальная часть кода для экспериментов:
struct Speaker: Identifiable {
var name: String
var image: String
var id: String { name }
}
struct Session: Identifiable {
var startDate: Date
var endDate: Date
var title: String
var speakers: [Speaker]
var canExpand: Bool { !speakers.isEmpty }
var id: String { title }
}
struct Avatar: View {
var name: String
var size: CGFloat
var imagePath: String
var body: some View {
Image(systemName: "person.circle.fill")
.resizable()
.frame(width: size, height: size)
}
}
struct SpeakerRow: View {
var speaker: Speaker
var body: some View {
HStack {
Avatar(name: speaker.name, size: 24, imagePath: speaker.image)
Text(speaker.name)
}
}
}
struct AgendaView: View {
var sessions: [Session]
@State var expandedSessionId: String? = nil
var body: some View {
ScrollView {
VStack {
ForEach(sessions) { session in
SessionView(
session: session,
expanded: .init(
get: { expandedSessionId == session.id },
set: { expand in
if expand {
expandedSessionId = session.id
} else if expandedSessionId == session.id {
expandedSessionId = nil
}
}
)
)
}
}
.animation(.easeInOut(duration: 1), value: expandedSessionId)
}
.padding()
}
}
#Preview {
AgendaView(sessions: [
.init(
startDate: .init(timeIntervalSinceReferenceDate: 9000),
endDate: .init(timeIntervalSinceReferenceDate: 9900),
title: "Keynote",
speakers: [
.init(name: "Tim Cook", image: "tim.jpg"),
.init(name: "Johnny Appleseed", image: "apple.jpg"),
]
),
.init(
startDate: .init(timeIntervalSince1970: 10000),
endDate: .init(timeIntervalSince1970: 10900),
title: "Gettysburg Address",
speakers: [
.init(name: "Abraham Lincoln", image: "abe.jpg"),
.init(name: "Abe's Beard", image: "beard.jpg"),
]
),
.init(
startDate: .init(timeIntervalSince1970: 11000),
endDate: .init(timeIntervalSince1970: 11900),
title: "Ted Talk",
speakers: [
.init(name: "Ted Lasso", image: "lasso.jpg"),
.init(name: "Ted ‘Theodore’ Logan", image: "ted.jpg"),
]
)
])
}