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

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


person justicepenny    schedule 29.09.2019    source источник


Ответы (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 и настройки пользовательской анимации в настоящее время невозможно.

person John M.    schedule 30.09.2019
comment
Спасибо большое за вашу помощь. Вы мне очень помогаете. У меня другая проблема. Я не мог установить переключатель внутри группы. Я получаю сообщение об ошибке. Замыкание, содержащее оператор потока управления, нельзя использовать с построителем функций 'ViewBuilder' func subView () - ›some View {Group {switch status {case .start: Text (start) break case .main: Text (main) break case .unknown: текст (неизвестно) break}}} - person justicepenny; 30.09.2019
comment
@justicepenny Смотрите мое обновление. По сути, вы не можете, если хотите сохранить анимацию. Используйте elseif утверждения и отправьте отзыв в Apple, если вы хотите использовать здесь switch. - person John M.; 30.09.2019
comment
да, я должен использовать оператор if else. и перечисление должно иметь необработанное значение. в любом случае большое спасибо за вашу помощь - person justicepenny; 01.10.2019

Мне удалось заставить его работать с оператором 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
}
person MinnesotaGus    schedule 12.02.2020
comment
2020 Xcode 11, это решение работает. Браво. Идентификационный трюк - лучший. Спасибо, @MinnesotaGus. - person Ilya; 30.08.2020

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

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

person blackjacx    schedule 22.06.2020
comment
Мне было интересно, почему OP использовал функцию, которая возвращает View вместо другой структуры View - person malhal; 13.12.2020