SwiftUI:停止一个永远重复的动画

The*_*Fox 13 xcode swift swiftui

我想在屏幕上有一个“徽章”,当条件满足时,它会从正常大小反弹到更大,然后反复恢复正常,直到不再满足条件。不过,我似乎无法让徽章停止“弹跳”。一旦开始,势不可挡。

我尝试过的: 我尝试过使用一些动画,但它们可以分为使用“repeatForever”来达到预期效果的动画和不使用的动画。例如:

Animation.default.repeatForever(autoreverses: true)

Animation.spring(response: 1, dampingFraction: 0, blendDuration: 1)(将阻尼设置为 0 使其永远消失)

然后用 .animation(nil) 把它换掉。似乎不起作用。有没有人有任何想法?非常感谢您提前!这是重现它的代码:

struct theProblem: View {
    @State var active: Bool = false

    var body: some View {
        Circle()
            .scaleEffect( active ? 1.08: 1)
            .animation( active ? Animation.default.repeatForever(autoreverses: true): nil )
            .frame(width: 100, height: 100)
            .onTapGesture {
                self.active = !self.active

        }
    }
}
Run Code Online (Sandbox Code Playgroud)

The*_*Fox 41

我想到了!

.repeatForever() 如果您将动画替换为 ,则 使用的动画不会停止nil如果您用相同的动画替换它但没有 .repeatForever(). (或者使用任何其他停止的动画,因此您可以使用持续时间为 0 的线性动画来立即停止)

换句话说,这行不通: .animation(active ? Animation.default.repeatForever() : nil)

但这确实有效: .animation(active ? Animation.default.repeatForever() : Animation.default)

为了使其更具可读性和易于使用,我将其放入一个扩展中,您可以像这样使用: .animation(Animation.default.repeat(while: active))

这是一个使用我的扩展程序的交互式示例,您可以使用实时预览来测试它:

import SwiftUI

extension Animation {
    func `repeat`(while expression: Bool, autoreverses: Bool = true) -> Animation {
        if expression {
            return self.repeatForever(autoreverses: autoreverses)
        } else {
            return self
        }
    }
}

struct TheSolution: View {
    @State var active: Bool = false
    var body: some View {
        Circle()
            .scaleEffect( active ? 1.08: 1)
            .animation(Animation.default.repeat(while: active))
            .frame(width: 100, height: 100)
            .onTapGesture {
                self.active.toggle()
            }
    }
}

struct TheSolution_Previews: PreviewProvider {
    static var previews: some View {
        TheSolution()
    }
}
Run Code Online (Sandbox Code Playgroud)

据我所知,一旦您分配动画,它永远不会消失,直到您的视图完全停止。因此,如果您将 .default 动画设置为永远重复并自动反转,然后您分配了一个持续时间为 4 的线性动画,您会注意到默认的重复动画仍在进行,但它的运动变得越来越慢,直到它在我们的 4 秒结束时完全停止。因此,我们通过线性动画将默认动画设置为停止动画。

  • 我想在 Xcode 更新之后它不再对我有用 (9认同)

小智 6

在经历了很多事情之后,我发现了一些对我有用的东西。至少暂时如此,直到我有时间找出更好的方法为止。

struct WiggleAnimation<Content: View>: View {
    var content: Content
    @Binding var animate: Bool
    @State private var wave = true

    var body: some View {
        ZStack {
            content
            if animate {
                Image(systemName: "minus.circle.fill")
                    .foregroundColor(Color(.systemGray))
                    .offset(x: -25, y: -25)
            }
        }
        .id(animate) //THIS IS THE MAGIC
        .onChange(of: animate) { newValue in
            if newValue {
                let baseAnimation = Animation.linear(duration: 0.15)
                withAnimation(baseAnimation.repeatForever(autoreverses: true)) {
                    wave.toggle()
                }
            }
        }
        .rotationEffect(.degrees(animate ? (wave ? 2.5 : -2.5) : 0.0),
                        anchor: .center)
    }

    init(animate: Binding<Bool>,
         @ViewBuilder content: @escaping () -> Content) {
        self.content = content()
        self._animate = animate
    }
}
Run Code Online (Sandbox Code Playgroud)

使用

@State private var editMode = false

WiggleAnimation(animate: $editMode) {
    VStack {
        Image(systemName: image)
            .resizable()
            .frame(width: UIScreen.screenWidth * 0.1,
                   height: UIScreen.screenWidth * 0.1)
            .padding()
            .foregroundColor(.white)
            .background(.gray)
        
        Text(text)
            .multilineTextAlignment(.center)
            .font(KMFont.tiny)
            .foregroundColor(.black)
    }
}
Run Code Online (Sandbox Code Playgroud)

它是如何工作的?这里的 .id(animate) 修饰符不会刷新视图,只是将其替换为新视图,因此它回到了原始状态。

同样,这可能不是最好的解决方案,但它适用于我的情况。


Kup*_*per 5

使用交易怎么样

在下面的代码中,我根据动画的状态关闭或打开动画active

警告:一定要使用withAnimation,否则什么都不起作用

@State var active: Bool = false

var body: some View {
    Circle()
        .scaleEffect(active ? 1.08: 1)
        .animation(Animation.default.repeatForever(autoreverses: true), value: active)
        .frame(width: 100, height: 100)
        .onTapGesture {
            useTransaction()
        }
}

func useTransaction() {
    var transaction = Transaction()
    transaction.disablesAnimations = active ? true : false
    
    withTransaction(transaction) {
        withAnimation {
            active.toggle()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)