Почему SwiftUI-transition работает не так, как ожидалось, когда я использую его в UIHostingController?

#swiftui #swiftui-animation

#swiftui #swiftui-анимация

Вопрос:

Я пытаюсь получить хороший переход для представления, в котором должна отображаться дата. Я даю идентификатор представлению, чтобы SwiftUI знал, что это новая метка, и анимирует ее переходом. Вот сокращенная версия без форматирования и стиля и с большой продолжительностью для лучшей визуализации:

 struct ContentView: View {
    @State var date = Date()

    var body: some View {
        VStack {
            Text("(date.description)")
                .id("DateLabel"   date.description)
                .transition(.slide)
                .animation(.easeInOut(duration: 5))

            Button(action: { date.addTimeInterval(24*60*60) }) {
                Text("Click")
            }
        }
    }
}
 

Результат, он работает так, как ожидалось, старая метка анимируется, а новая анимируется:

Результат1

Но как только я оберну его внутри UIHostingController:

 struct ContentView: View {
    @State var date = Date()

    var body: some View {
        AnyHostingView {
            VStack {
                Text("(date.description)")
                    .id("DateLabel"   date.description)
                    .transition(.slide)
                    .animation(.easeInOut(duration: 5))

                Button(action: { date.addTimeInterval(24*60*60) }) {
                    Text("Click")
                }
            }
        }
    }
}

struct AnyHostingView<Content: View>: UIViewControllerRepresentable {
    typealias UIViewControllerType = UIHostingController<Content>
    let content: Content

    init(content: () -> Content) {
        self.content = content()
    }

    func makeUIViewController(context: Context) -> UIHostingController<Content> {
        let vc = UIHostingController(rootView: content)
        return vc
    }

    func updateUIViewController(_ uiViewController: UIHostingController<Content>, context: Context) {
        uiViewController.rootView = content
    }
}
 

В результате новая метка не анимируется, а просто вставляется в конечную позицию, в то время как старая метка анимируется:

введите описание изображения здесь

У меня более сложный хостинг-контроллер, но это демонстрирует проблему. Я делаю что-то не так с тем, как я обновляю представление контроллера хостинга, или это ошибка в SwiftUI или что-то еще?

Ответ №1:

Состояние не работает должным образом между различными контроллерами хостинга (неясно, является ли это ограничением или ошибкой, просто эмпирическим наблюдением).

Решение заключается в внедрении зависимого состояния внутри представления хостинга. Протестировано с Xcode 12.1 / iOS 14.1.

 struct ContentView: View {
    var body: some View {
        AnyHostingView {
            InternalView()
        }
    }
}

struct InternalView: View {
    @State private var date = Date()   // keep relative state inside
    var body: some View {
        VStack {
             Text("(date.description)")
                  .id("DateLabel"   date.description)
                  .transition(.slide)
                  .animation(.easeInOut(duration: 5))

             Button(action: { date.addTimeInterval(24*60*60) }) {
                  Text("Click")
             }
        }
    }
}
 

Примечание: вы также можете поэкспериментировать с ObservableObject/ObservedObject моделью представления на основе — этот шаблон имеет другой жизненный цикл.

Комментарии:

1. Это правильно! Но для меня невозможно сохранить состояние в представлении, поскольку оно поступает извне, не так, как в примере. Я буду играть с ObservableObject tho! Спасибо!