Анимированные переходы и содержимое в ScrollView в SwiftUI

Я не совсем ветеран SwiftUI, но у меня есть пара приложений средней сложности. Тем не менее, я не могу утверждать, что полностью понимаю это, и я надеюсь, что кто-то с более глубокими знаниями сможет пролить свет на эту проблему:

У меня есть некоторый контент, который я хочу включать и выключать, в отличие от .sheet (), но мне нужен больший контроль над ним. Вот некоторый "реконструированный" код, но он должен уловить суть:

struct ContentView: View {

    @State private var isShown = false

    var body: some View {

        GeometryReader { g in

            VStack {

                ZStack(alignment: .top) {

                    // This element "holds" the size
                    // while the content is hidden
                    Color.clear

                    // Content to be toggled
                    if self.isShown {
                        ScrollView {
                            Rectangle()
                                .aspectRatio(1, contentMode: .fit)
                                .frame(width: g.size.width) // This is a "work-around"
                        } // ScrollView
                            .transition(.move(edge: .bottom))
                            .animation(.easeOut)
                    }

                } // ZStack

                // Button to show / hide the content
                Button(action: {
                    self.isShown.toggle()
                }) {
                    Text(self.isShown ? "Hide" : "Show")
                }

            } // VStack

        } // GeometryReader

    }
}

Что он делает, так это то, что он включает и выключает некоторый блок содержимого (представленный здесь прямоугольником в ScrollView). Когда это происходит, представление содержимого меняется, перемещаясь снизу с некоторой анимацией. Обратное происходит при повторном нажатии кнопки.

Этот конкретный фрагмент кода работает так, как задумано, но только благодаря этой строке:

.frame(width: g.size.width) // This is a "work-around"

Что, в свою очередь, требует дополнительного GeometryReader, в противном случае ширина содержимого анимируется, создавая нежелательный эффект (еще одно обнаруженное мною "исправление" - использование модификатора .fixedSize(), но для получения разумных эффектов требуется контент, который принимает собственную ширину, например текст)

Мой вопрос к мудрым: можно ли красиво перейти к контенту, инкапсулированному в ScrollView, без использования таких "исправлений"? В качестве альтернативы, есть ли более элегантное решение для этого?

Быстрое добавление к вопросу, следующему за ответом @Asperi: содержимое должно оставаться анимированным. Ты моя единственная надежда,

–Баглан

1 ответ

Решение

Вот решение (обновлено body без GeometryReader). Протестировано с Xcode 11.4 / iOS 13.4

var body: some View {
    VStack {
        ZStack(alignment: .top) {
            // This element "holds" the size
            // while the content is hidden
            Color.clear

            // Content to be toggled
            if self.isShown {
                ScrollView {
                    Rectangle()
                        .aspectRatio(1, contentMode: .fit)
                        .animation(nil)     // << here !!
                } // ScrollView
                    .transition(.move(edge: .bottom))
                    .animation(.easeOut)
            }
        } // ZStack

        // Button to show / hide the content
        Button(action: {
            self.isShown.toggle()
        }) {
            Text(self.isShown ? "Hide" : "Show")
        }
    } // VStack
}
Другие вопросы по тегам