嵌入另一个视图时,SwiftUI 动画不起作用

Posted

技术标签:

【中文标题】嵌入另一个视图时,SwiftUI 动画不起作用【英文标题】:SwiftUI animation not working when embedded inside another view 【发布时间】:2020-05-28 17:16:59 【问题描述】:

我制作了一个可重复使用的圆形进度条。我应用了repeatForever 动画以使其保持旋转,但它仅在直接与@State 或@Published 变量一起使用时有效,并且在嵌入另一个视图时无效。

可重复使用的环视图。哪个是圆形进度条

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 的工作原理时犯了一些错误。

【问题讨论】:

我尝试使用 loginview 重现您的“工作”案例 -> 但它没有用...也许您可以重新检查您的示例甚至更好:使其可重现和可编译...谢谢跨度> @Chris 我已经得到了答案,但已经编辑了我的代码并删除了自定义字符串和变量,这可能会对某人有所帮助。感谢您与我们联系。 【参考方案1】:

动画在状态更改时激活。在提供的代码中没有任何变化,所以根本没有动画。

以下是主要更改,因此我使它起作用了。使用 Xcode 11.4 / ios 13.4 测试(有一些缺少自定义依赖项的复制)

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) 在 Ring 中修复了动画停用以避免累积效应(.none 在这里不起作用),所以

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

【讨论】:

感谢您的快速帮助。我仍然对这部分感到困惑:为什么当我使用来自ProgressButton@Binding showProgressRingView's show 变量时它不起作用。它不会将状态更改从Login 传递到ProgressButton,然后再传递到RingView 吗? @harsh_v,通过了,但是在RingView出现的那一刻,它已经用真值启动了,所以没有触发激活动画。

以上是关于嵌入另一个视图时,SwiftUI 动画不起作用的主要内容,如果未能解决你的问题,请参考以下文章

为啥嵌入到 SwiftUI 表单中的按钮不起作用?

如何动画视图插入/删除的过渡?插入动画不起作用

容器视图内的按钮不起作用

不是根视图控制器时核心动画不起作用

对 SwiftUI 中水平滚动视图内的列表不起作用的操作

添加视图时,SwiftUI 插入转换不起作用