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)
    }
}

Вот основные моменты, которые следует отметить в моем коде:

  1. Я безоговорочно включаю ряды динамиков.

  2. Я заворачиваю ряды динамиков в свои. Это имеетframeмодификатор с нулевой высотой, если сеанс не расширен.

  3. Я применяю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"),
            ]
        )
    ])
}
Другие вопросы по тегам