SwiftUI:使用非@State var 时出现意外动画 [英] SwiftUI: Unexpected Animation when using a non @State var

查看:35
本文介绍了SwiftUI:使用非@State var 时出现意外动画的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要一些帮助.我创建了以下可重用视图,我们称之为 MyCustomCapsuleView:

没什么特别的,只是一个 Text 和一个 Capsule 在背景中.当用户打开此视图时,它会有不同的外观.

当用户打开它时,MyCustomCapsuleView 填充应该增加到 17 以考虑新外观所需的额外空间.此外,它应该:

  1. 放大和缩小
  2. 开始摆动

当用户关闭它时,MyCustomCapsuleView 填充应该回到 3,因为不再需要额外的空间.此外,它应该:

  1. 停止摆动
  2. 再次放大和缩小

我能够获得此功能的版本,但填充无法正常工作.当 MyCustomCapsuleView 摆动时,内边距在 3 到 17 之间来回移动(见黄色背景).

  1. 任何想法我做错了什么?我怎样才能实现我需要的行为?注意:由于这个视图是可重用的,我需要关闭"视图.我的视图版本保持 padding = 3.我将在我的应用程序的不同部分使用它,并且仅在某些地方打开/关闭"版本将可用,只有在这些地方,我的视图填充为 17.

  2. 为什么旋转(摆动效果)看起来这么奇怪?我尝试了不同的锚点单元 poi.ts(中心、尾随等),但似乎没有一个让我好看.

感谢您的帮助!

这是我的代码:

struct MyCustomCapsuleView:查看{@State 私有 var scaleUp = falsevar showRemove: Boolprivate var fillColor: Color { return Color(#colorLiteral(red: 0.4745098054, green: 0.8392156959, blue: 0.9764705896, alpha: 1)) }private var foregroundColor: Color { return Color(#colorLiteral(red: 0.1019607857, green: 0.2784313858, blue: 0.400000006, alpha: 1)) }var主体:一些视图{ZStack(对齐:.topTrailing){文本(你好世界!").foregroundColor(foregroundColor).padding(.水平).padding(.vertical, 10).lineLimit(1).背景(ZStack {胶囊(样式:RoundedCornerStyle.continuous).fill(fillColor)胶囊(样式:RoundedCornerStyle.continuous).stroke(foregroundColor, lineWidth: 2)})圆圈().fill(颜色.红色).frame(宽:30,高:30).覆盖(图像(系统名称:乘法").resizable().padding(7).foregroundColor(.white)).offset(x: 15, y: -15).scaleEffect(showRemove ? 1 : 0, 锚点:UnitPoint.topTrailing).animation(.spring(response: 0.3,dampingFraction: 0.6, blendDuration: 0), value: showRemove)}.padding(showRemove ? 17: 3).background(颜色.黄色).scaleEffect(scaleUp ? 1.2 : 1).animation(.spring(response: 0.3,dampingFraction: 0.6, blendDuration: 0), value: scaleUp).rotationEffect(.degrees(showRemove ? 7 : 0), 锚点:UnitPoint(x: 0.10, y: 0.0)).animation(showRemove ? .easeInOut(duration: 1.15).repeatForever(autoreverses: true).delay(0.3) : .easeInOut(duration: 0.15), value: showRemove).onChange(of: showRemove, perform: { value in如果 !scaleUp {缩放 = 真Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { _ in缩放 = 假}}})}}结构测试:查看{@State var showClose = falsevar主体:一些视图{堆栈{MyCustomCapsuleView(showRemove: showClose)按钮(打开/关闭"){showClose.toggle()}}}}

解决方案

这有点棘手.目前,您的 .repeatForever(autoreverses: true) 动画不仅为 rotationEffect 设置动画,还为 padding 设置动画.为了防止这种情况发生并使用 repeatForever only 用于 rotationEffect,我认为要走的路是

I need some help. I created the following reusable view, let's call it MyCustomCapsuleView:

Nothing special, just a Text with a Capsule in the background. This view will have a different look when the user turns it on.

When the user turns it on, MyCustomCapsuleView padding should increase to 17 to account for the extra space needed for the new look. Also, it should:

  1. Scale up and back
  2. Start wiggling

When the user turns it off, MyCustomCapsuleView padding should go back to 3, as the extra room is not needed anymore. Also, it should:

  1. Stop wiggling
  2. Scale up and down, again

I was able to get a functional version of this, but the paddings are not working properly. When MyCustomCapsuleView is wiggling, the paddings go back and forth between 3 and 17 (see the yellow background).

  1. Any ideas what am I doing wrong? How can I achieve the behaviour I need? Note: Since this view is reusable, I need that the "turned off" version of my view keeps padding = 3. I will use it across different parts of my app and only in some places the "turned on/off" version will be available, and only in those places the padding for my view is 17.

  2. Why the rotation (wiggle effect) looks so weird? I tried different anchor unit poi.ts (center, trailing, etc) and none of them seems to give me a good look.

Thanks for your help!

Here's my code:

struct MyCustomCapsuleView: View {
    @State private var scaleUp = false
    
    var showRemove: Bool
    private var fillColor: Color { return Color(#colorLiteral(red: 0.4745098054, green: 0.8392156959, blue: 0.9764705896, alpha: 1)) }
    private var foregroundColor: Color { return Color(#colorLiteral(red: 0.1019607857, green: 0.2784313858, blue: 0.400000006, alpha: 1)) }
    
    var body: some View {
        ZStack(alignment: .topTrailing) {
            Text("Hello World!")
                .foregroundColor(foregroundColor)
                .padding(.horizontal)
                .padding(.vertical, 10)
                .lineLimit(1)
                .background(
                    ZStack {
                        Capsule(style: RoundedCornerStyle.continuous)
                            .fill(fillColor)
                        Capsule(style: RoundedCornerStyle.continuous)
                            .stroke(foregroundColor, lineWidth: 2)
                    }
                )
            
            Circle()
                .fill(Color.red)
                .frame(width: 30, height: 30)
                .overlay(
                    Image(systemName: "multiply")
                        .resizable()
                        .padding(7)
                        .foregroundColor(.white)
                )
                .offset(x: 15, y: -15)
                .scaleEffect(showRemove ? 1 : 0, anchor: UnitPoint.topTrailing)
                .animation(.spring(response: 0.3, dampingFraction: 0.6, blendDuration: 0), value: showRemove)
        }
        .padding(showRemove ? 17: 3)
        .background(Color.yellow)
        .scaleEffect(scaleUp ? 1.2 : 1)
        .animation(.spring(response: 0.3, dampingFraction: 0.6, blendDuration: 0), value: scaleUp)
        .rotationEffect(.degrees(showRemove ? 7 : 0), anchor: UnitPoint(x: 0.10, y: 0.0))
        .animation(showRemove ? .easeInOut(duration: 1.15).repeatForever(autoreverses: true).delay(0.3) : .easeInOut(duration: 0.15), value: showRemove)
        .onChange(of: showRemove, perform: { value in
            if !scaleUp {
                scaleUp = true
                Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { _ in
                    scaleUp = false
                }
            }
        })
    }
}

struct Test: View {
    @State var showClose = false
    
    var body: some View {
        HStack {
            MyCustomCapsuleView(showRemove: showClose)
            Button("Turn On/Off") {
                showClose.toggle()
            }
        }
    }
}

解决方案

This is kind of tricky. Currently, your .repeatForever(autoreverses: true) animation is animating not only the rotationEffect, but also the padding. To prevent this and use the repeatForever only for rotationEffect, I think the way to go is with explicit animations and 2 separate @States.

  • Explicit animations let you specify which properties to animate.
  • 2 separate @States allows a different animation for each var.
    • @State var showClose is a one-time animation for creating a larger padding() and the scale-up animation
    • @State var rotating is the repeatForever animation for the rotation

Here's what your Test struct would look like:

struct Test: View {
    @State var showClose = false
    @State var rotating = false
    
    var body: some View {
        HStack {
            MyCustomCapsuleView(showRemove: showClose, rotating: rotating)
            
            Button("Turn On/Off") {
                print(showClose)
                withAnimation(.spring(response: 0.3, dampingFraction: 0.6, blendDuration: 0)) {
                    showClose.toggle()
                }
                withAnimation(rotating ? .easeInOut(duration: 0.15) : .easeInOut(duration: 1.15).repeatForever(autoreverses: true).delay(0.3)) {
                    rotating.toggle()
                }
            }
        }
    }
}

Then, modify these lines in your MyCustomCapsuleView:

var showRemove: Bool
var rotating: Bool /// add this

/// replace your `ZStack`'s modifiers with this:
.padding(showRemove ? 17: 3)
.background(Color.yellow)
.scaleEffect(scaleUp ? 1.2 : 1)
.rotationEffect(.degrees(rotating ? 7 : 0))
.onChange(of: showRemove, perform: { value in
    if !scaleUp {
        scaleUp = true

        /// btw, why not just `DispatchQueue.main.asyncAfter(.now() + 0.1)`?
        Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { _ in
            scaleUp = false
        }
    }
})

Result:

这篇关于SwiftUI:使用非@State var 时出现意外动画的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆