admin管理员组文章数量:1352167
I'm trying to animate the height and widths of a view with different animation durations, but it just chooses one animation duration. How is this done, given an initial width and height, and final width and height?
An example of what I've tried and thought would work but doesn't.
struct TestView: View {
@State var currentWidth: CGFloat = 300
@State var currentHeight: CGFloat = 100
var body: some View {
VStack {
Color.purple
.frame(width:currentWidth, height: currentHeight)
.animation(.spring(duration:1.0), value:currentWidth)
.animation(.spring(duration:20.0), value:currentHeight)
}
.onAppear {
currentWidth = 450
currentHeight = 900
}
}
}
struct TestView: View {
@State var currentWidth: CGFloat = 300
@State var currentHeight: CGFloat = 100
var body: some View {
VStack {
Color.purple
.frame(width:currentWidth, height: currentHeight)
}
.onAppear {
withAnimation(.spring(duration:1.0)) {
currentWidth = 450
}
withAnimation(.spring(duration:20.0)) {
currentHeight = 900
}
}
}
}
I'm trying to animate the height and widths of a view with different animation durations, but it just chooses one animation duration. How is this done, given an initial width and height, and final width and height?
An example of what I've tried and thought would work but doesn't.
struct TestView: View {
@State var currentWidth: CGFloat = 300
@State var currentHeight: CGFloat = 100
var body: some View {
VStack {
Color.purple
.frame(width:currentWidth, height: currentHeight)
.animation(.spring(duration:1.0), value:currentWidth)
.animation(.spring(duration:20.0), value:currentHeight)
}
.onAppear {
currentWidth = 450
currentHeight = 900
}
}
}
struct TestView: View {
@State var currentWidth: CGFloat = 300
@State var currentHeight: CGFloat = 100
var body: some View {
VStack {
Color.purple
.frame(width:currentWidth, height: currentHeight)
}
.onAppear {
withAnimation(.spring(duration:1.0)) {
currentWidth = 450
}
withAnimation(.spring(duration:20.0)) {
currentHeight = 900
}
}
}
}
Share
Improve this question
asked Apr 1 at 3:06
AntcDevAntcDev
897 bronze badges
2 Answers
Reset to default 1maybe you need use Keyframe Animation
struct ContentView: View {
@State private var start = false
var body: some View {
VStack {
Button {
start.toggle()
} label: {
Text("Trigger")
}
Color.red
.keyframeAnimator(
initialValue: 0,
trigger: start,
content: { content, value in
content.frame(
width: min(200, 100 + value * 10),
height: 100 + value * 10
)
},
keyframes: { value in
LinearKeyframe(30, duration: 0.5, timingCurve: .easeOut)
}
)
}
}
}
You should use the animation
modifier that take a body
closure. This is how you separate animations into different "domains". Rather than animating a value change, it only animates the ViewModifier
s in the body
that are Animatable
.
Unfortunately, frame
is not an Animatable
modifier (but scaleEffect
is, so consider using that if you can), so you need to make an Animatable
wrapper around it.
struct AnimatableFrameModifier: ViewModifier, Animatable {
var animatableData: CGFloat
let isWidth: Bool
init(width: CGFloat) {
self.animatableData = width
isWidth = true
}
init(height: CGFloat) {
self.animatableData = height
isWidth = false
}
func body(content: Content) -> some View {
content.frame(width: isWidth ? animatableData : nil,
height: isWidth ? nil : animatableData)
}
}
struct TestView: View {
@State var currentWidth: CGFloat = 10
@State var currentHeight: CGFloat = 10
var body: some View {
VStack {
Color.purple
.animation(.spring(duration:1.0)) {
$0.modifier(AnimatableFrameModifier(width: currentWidth))
}
.animation(.spring(duration:20.0)) {
$0.modifier(AnimatableFrameModifier(height: currentHeight))
}
}
.onAppear {
currentWidth = 100
currentHeight = 100
}
}
}
The alternative that uses scaleEffect
, which might be undesirable depending on what you are doing.
struct TestView: View {
@State var currentWidth: CGFloat = 10
@State var currentHeight: CGFloat = 10
@State var xScale: CGFloat = 1
@State var yScale: CGFloat = 1
var body: some View {
VStack {
Color.purple
.frame(width: 10, height: 10)
.animation(.spring(duration:1.0)) {
$0.scaleEffect(x: xScale)
}
.animation(.spring(duration:20.0)) {
$0.scaleEffect(y: yScale)
}
.frame(width: currentWidth, height: currentHeight)
}
.onAppear {
currentWidth = 100
currentHeight = 100
xScale = 10
yScale = 10
}
}
}
Before iOS 17, you can achieve a similar effect by waiting for a short amount of time between two withAnimation
calls.
@State var currentWidth: CGFloat = 10
@State var currentHeight: CGFloat = 10
var body: some View {
VStack {
Color.purple
.frame(width: currentWidth, height: currentHeight)
}
.task {
withAnimation(.linear(duration: 1)) {
currentWidth = 100
}
// here I used Task.yield to wait for the next frame
// you can also use Task.sleep, DisptachQueue.main.asyncAfter, etc
await Task.yield()
withAnimation(.linear(duration: 2)) {
currentHeight = 400
}
}
}
But this only works for animations that merge in a desirable way. The way spring
animations merge are not desirable for this purpose.
本文标签: iosAnimate a height and width change separately in a SwiftUI viewStack Overflow
版权声明:本文标题:ios - Animate a height and width change separately in a SwiftUI view - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1743910345a2560279.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论