SwiftUI:使用非@State var 时出现意外动画 [英] SwiftUI: Unexpected Animation when using a non @State var
问题描述
我需要一些帮助.我创建了以下可重用视图,我们称之为 MyCustomCapsuleView
:
没什么特别的,只是一个 Text
和一个 Capsule
在背景中.当用户打开此视图时,它会有不同的外观.
当用户打开它时,MyCustomCapsuleView
填充应该增加到 17 以考虑新外观所需的额外空间.此外,它应该:
- 放大和缩小
- 开始摆动
当用户关闭它时,MyCustomCapsuleView
填充应该回到 3,因为不再需要额外的空间.此外,它应该:
- 停止摆动
- 再次放大和缩小
我能够获得此功能的版本,但填充无法正常工作.当 MyCustomCapsuleView
摆动时,内边距在 3 到 17 之间来回移动(见黄色背景).
任何想法我做错了什么?我怎样才能实现我需要的行为?注意:由于这个视图是可重用的,我需要关闭"视图.我的视图版本保持 padding = 3.我将在我的应用程序的不同部分使用它,并且仅在某些地方打开/关闭"版本将可用,只有在这些地方,我的视图填充为 17.
为什么旋转(摆动效果)看起来这么奇怪?我尝试了不同的锚点单元 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:
- Scale up and back
- 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:
- Stop wiggling
- 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).
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.
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 @State
s.
- Explicit animations let you specify which properties to animate.
- 2 separate
@State
s allows a different animation for each var.@State var showClose
is a one-time animation for creating a largerpadding()
and the scale-up animation@State var rotating
is therepeatForever
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屋!