Представление SwiftUI не анимируется при привязке к @Published var, если действие не выполнено немедленно - PullRequest
4 голосов
/ 10 марта 2020

У меня есть представление SwiftUI, которое заменяет определенные элементы управления в зависимости от состояния. Я пытаюсь использовать MVVM, поэтому большая часть / все мои логи c перенесены в модель представления. Я обнаружил, что при выполнении сложного действия, которое изменяет @Published var на модели представления, View не будет анимироваться.

Вот пример, где таймер 1,0 с в модели представления моделирует другую работу, являющуюся сделано до изменения значения @Published var:

struct ContentView: View {
    @State var showCircle = true
    @ObservedObject var viewModel = ViewModel()

    var body: some View {

        VStack {
            VStack {
                if showCircle {
                    Circle().frame(width: 100, height: 100)
                }

                Button(action: {
                    withAnimation {
                        self.showCircle.toggle()
                    }

                }) {
                    Text("With State Variable")
                }
            }

            VStack {
                if viewModel.showCircle {
                    Circle().frame(width: 100, height: 100)
                }
                Button(action: {
                    withAnimation {
                        self.viewModel.toggle()
                    }
                }) {
                    Text("With ViewModel Observation")
                }
            }
        }
    }


    class ViewModel: ObservableObject {
        @Published var showCircle = true

        public func toggle() {
            // Do some amount of work here. The Time is just to simulate work being done that may not complete immediately.
            Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { [weak self] _ in
                self?.showCircle.toggle()
            }

        }
    }

Ответы [ 3 ]

1 голос
/ 10 марта 2020

Родительское представление оживляет скрытие и отображение дочерних представлений. Если вы поместите .animation(.easeIn) (или .easeOut или что вам нравится) в конце вашего первого VStack, он должен работать как положено.

Как таковые ...

struct ContentView: View {
@State var showCircle = true
@ObservedObject var viewModel = ViewModel()

var body: some View {
    VStack {
        VStack {
            if showCircle {
                Circle().frame(width: 100, height: 100)
            }

            Button(action: {
                withAnimation {
                    self.showCircle.toggle()
                }

            }) {
                Text("With State Variable")
            }
        }

        VStack {
            if viewModel.showCircle {
                Circle().frame(width: 100, height: 100)
            }
            Button(action: {
                withAnimation {
                    self.viewModel.toggle()
                }
            }) {
                Text("With ViewModel Observation")
            }
        }
    }.animation(.easeIn)
  }
}
1 голос
/ 10 марта 2020

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

Button(action: {
    self.viewModel.toggle()  // removed from here
}) {
    Text("With ViewModel Observation")
}

...

Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { [weak self] _ in
    withAnimation { // << added here
        self?.showCircle.toggle()
    }
}

Однако я бы скорее рекомендовал переосмыслить дизайн вида ... как

VStack {
    if showCircle2 { // same declaration as showCircle
        Circle().frame(width: 100, height: 100)
    }
    Button(action: {
        self.viewModel.toggle()
    }) {
        Text("With ViewModel Observation")
    }
    .onReceive(viewModel.$showCircle) { value in
        withAnimation {
            self.showCircle2 = value
        }
    }
}

Протестировано с Xcode 11.2 / iOS 13.2

0 голосов
/ 10 марта 2020

Вы должны использовать задержку для анимации вместо использования таймера для задержки анимации.

Button(action: {
    withAnimation(Animation.linear.delay(1.0)) {
        self.viewModel.showCircle.toggle()
    }
}) {
    Text("With ViewModel Observation")
}

В противном случае изменения в вашей модели представления будут вести себя так, как ожидается, если вы не используете таймер.

class ViewModel: ObservableObject {
    @Published var showCircle = true

    public func toggle() {

        /* other operations */

        self.showCircle.toggle()
    }

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...