SwiftUI динамически добавляет subview, но анимация не работает

Я хотел бы создать представление в SwiftUI, которое добавляло бы подпредставление динамически и с анимацией.

struct ContentView : View {
    @State private var isButtonVisible = false

    var body: some View {
        VStack {
            Toggle(isOn: $isButtonVisible.animation()) {
                Text("add view button")
            }

           if isButtonVisible {
                 AnyView(DetailView())
                      .transition(.move(edge: .trailing))
                      .animation(Animation.linear(duration: 2))
             }else{
                    AnyView(Text("test"))
            }
        }
    }
}

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


struct ContentView : View {
    @State private var isButtonVisible = false
    var body: some View {
        VStack {
            Toggle(isOn: $isButtonVisible.animation()) {
                Text("add view button")
            }

            subView().transition(.move(edge: .trailing))
                     .animation(Animation.linear(duration: 2))   
    }

     func subView() -> some View {
         if isButtonVisible {
             return AnyView(DetailView())
         }else{
            return AnyView(Text("test"))
        }
    }
}

Мне это кажется совершенно одинаковым, однако я не понимаю, почему у них другой результат. Может кто-нибудь объяснить мне, почему? и какие-нибудь лучшие решения? большое спасибо!

3 ответа

Решение

Вот ваш код, измененный так, чтобы он работал:

struct ContentView : View {
    @State private var isButtonVisible = false

    var body: some View {
        VStack {
            Toggle(isOn: $isButtonVisible.animation()) {
                Text("add view button")
            }

            subView()
                .transition(.move(edge: .trailing))
                .animation(Animation.linear(duration: 2))
        }
    }

    func subView() -> some View {
        Group {
            if isButtonVisible {
                DetailView()
            } else {
                Text("test")
            }
        }
    }
}

Обратите внимание на две вещи:

  1. Ваши два приведенных выше примера разные, поэтому вы получаете разные результаты. Первый применяет переход и анимацию к DetailView, а затем стирает их с помощью AnyView. Второй тип стирает DetailView с AnyView, затем применяет переход и анимацию.
  2. Скорее, используя AnyView и стирание типа, я предпочитаю инкапсулировать условную логику внутри GroupПосмотреть. Тогда тип, который вы возвращаете, будетGroup, который будет правильно анимирован.
  3. Если вам нужны разные анимации для двух возможностей для вашего подпредставления, теперь вы можете применить их непосредственно к DetailView() или Text("test").

Обновить

В Group метод будет работать только с if, elseif, а также elseзаявления. Если вы хотите использовать переключатель, вам придется обернуть каждую ветвь вAnyView(). Однако это нарушает переходы / анимацию. С помощьюswitch и установка пользовательской анимации в настоящее время невозможна.

Мне удалось заставить его работать с оператором switch, заключив функцию, которая возвращает AnyView в VStack. Мне также пришлось датьAnyView ан .idпоэтому SwiftUI может знать, когда он изменится. Это в Xcode 11.3 и iOS 13.3

struct EnumView: View {

    @ObservedObject var viewModel: ViewModel

    var body: some View {
        VStack {
            view(for: viewModel.viewState)
                .id(viewModel.viewState)
                .transition(.opacity)
        }
    }

    func view(for viewState: ViewModel.ViewState) -> AnyView {
        switch viewState {
        case .loading:
            return AnyView(LoadingStateView(viewModel: self.viewModel))
        case .dataLoaded:
            return AnyView(LoadedStateView(viewModel: self.viewModel))
        case let .error(error):
            return AnyView(ErrorView(error: error, viewModel: viewModel))
        }
    }

}

Также для моего примера в ViewModel Мне нужно завернуть viewState изменения в withAnimation блокировать

withAnimation {
    self.viewState = .loading
}

В iOS 14 добавили возможность использовать if let а также switchоператоры в построителях функций. Может быть, это поможет в ваших проблемах:

https://www.hackingwithswift.com/articles/221/whats-new-in-swiftui-for-ios-14 (внизу статьи)

Другие вопросы по тегам