Анимация SwiftUI не работает при встраивании в другое представление - PullRequest
1 голос
/ 28 мая 2020

Я сделал многоразовый круглый индикатор выполнения. Я применил анимацию repeatForever, чтобы она вращалась, но она работает только при прямом использовании с переменной @State или @Published и не работает, когда она встроена в другое представление.

Многоразовый RingView. Это круговой индикатор выполнения

struct RingView: View {
    private let percent: CGFloat = 80   // Keeping it fixed 80 for test purpose
    var color = Color.primaryLightColor // Some random color
    @Binding var show: Bool

    var body: some View {
        ZStack {
            GeometryReader { bounds in

                Circle()
                    .trim(from: self.show ? self.progress : 1, to: 1)
                    .stroke(
                        LinearGradient(gradient: Gradient(colors: [self.color]), startPoint: .topTrailing, endPoint: .bottomLeading),
                        style: StrokeStyle(lineWidth: self.lineWidth(width: bounds.size.width), lineCap: .round, lineJoin: .round, miterLimit: .infinity, dash: [20, 0], dashPhase: 0)
                )
                    .rotationEffect(Angle(degrees: 90))
                    .animation(.none)
                    .frame(width: bounds.size.width, height: bounds.size.height)
            }
        }
        .rotationEffect(.degrees(show ? 360.0 : 0.0))
        .animation(show ? Animation.linear(duration: 1.0).repeatForever(autoreverses: false) : .none)
    }

    func multiplier(width: CGFloat) -> CGFloat {
        width / 44
    }

    func lineWidth(width: CGFloat) -> CGFloat {
        5 * self.multiplier(width: width)
    }

    var progress: CGFloat {
        1 - (percent / 100)
    }
}

Кнопка выполнения. Он использует RingView выше. Общий вариант использования - показать анимацию при выполнении длительной фоновой задачи.

Анимация RingView не работает в этом ProgressButton

struct ProgressButton: View {
        var action: () -> Void
        var image: Image? = nil
        var text: String = ""
        var backgroundColor: Color = Color.blue
        var textColor: Color = .white
        @Binding var showProgress: Bool

        var body: some View {
            Button(action: action) {
                HStack {
                    if showProgress {
                        RingView(color: textColor, show: self.$showProgress)
                            .frame(width: 25, height: 25)
                        .transition(.scale)
                            .animation(.Spring())
                    } else {
                        image?
                            .renderingMode(.original)
                            .resizable()
                            .frame(width: 25, height: 25)
                    }
                    Text(text)
                        .font(.headline)
                        .foregroundColor(textColor)
                }
                .padding()
                .frame(minWidth: 0, maxWidth: .infinity, minHeight: 50, alignment: .center)
                .background(backgroundColor)
                .clipShape(RoundedRectangle(cornerRadius: 5, style: .continuous))
            }.buttonStyle(PlainButtonStyle())
        }
    }

Когда работает анимация RingView. Когда я использую его напрямую, как показано ниже.

struct LoginView: View {
    @State var showProgress = true
    var body: some View {
        VStack {

            // WORKING
            RingView(show: self.$showProgress)
                .frame(width: 25, height: 25)

            // NOT WORKING
            ProgressButton(action: {
                self.showProgress = true
            }, image: Image("password-lock"), text: "Login", backgroundColor: .blue, showProgress: self.showProgress)
        }
    }
}

Думаю, я ошибаюсь, понимая, как работают @Binding и @State.

1 Ответ

1 голос
/ 28 мая 2020

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

Ниже приведены основные изменения, поэтому я заставил их работать. Протестировано с Xcode 11.4 / iOS 13.4 (с некоторой репликацией отсутствующих пользовательских зависимостей)

demo

1) Первоначальная анимация отключена

struct LoginView: View {
    @State var showProgress = false

// ... other code

    // no model provided so used same state for progress
    ProgressButton(action: {
        self.showProgress.toggle()     // << activate change !!!
    }, image: Image("password-lock"), text: L.Login.LoginSecurely, backgroundColor: self.isLoginButtonEnabled ? Color.primaryLightColor : Color.gray, showProgress: $showProgress) 

2) Добавить состояние активации внутреннего кольца внутри кнопки прогресса; такое же состояние, используемое для скрытия / отображения, и активация не работает, опять же, потому что, когда появилось кольцо, нет никаких изменений (это уже верно), поэтому кольцо не активируется

@State private var actiavteRing = false     // << here !!
var body: some View {
    Button(action: action) {
        HStack {

            if showProgress {
                RingView(color: textColor, show: self.$actiavteRing) // !!
                    .frame(width: 25, height: 25)
                    .transition(.opacity)
                    .animation(.spring())
                    .onAppear { self.actiavteRing = true } // << 
                    .onDisappear { self.actiavteRing = false } // <<

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

.rotationEffect(.degrees(show ? 360.0 : 0.0))
.animation(show ? Animation.linear(duration: 
    1.0).repeatForever(autoreverses: false) : .default)   // << here !!
...