SwiftUI中实现类似Stripe的旗帜风格渐变背景动画
解决SwiftUI渐变背景无法持续动画的问题
问题描述
我希望在用户个人资料视图上方创建类似Stripe官网的旗帜风格渐变动画。当前编写的SwiftUI代码仅在切换包含该视图的页面时会切换渐变外观,页面打开后无法持续播放动画,请问该如何调整代码以实现所需的动画效果?
原代码:
import SwiftUI struct UserProfileHeaderView: View { let plan: String let headerTitle: String let greeting: String let planDescription: String var isTrailingTitle: Bool let description: String @State private var animateGradient = false var body: some View { let startColor = plan == "Classic" ? Color.purple : plan == "Plus" ? Color.red : Color(hex: "#636363") let endColor = plan == "Classic" ? Color.blue : plan == "Plus" ? Color.orange : plan == "Ultimate" ? Color(hex: "#010057") : Color(hex: "#636363") let gradient = LinearGradient( gradient: Gradient(colors: [startColor, endColor]), startPoint: animateGradient ? .topLeading : .bottomTrailing, endPoint: animateGradient ? .bottomTrailing : .topLeading ) VStack(alignment: .leading, spacing: 8) { HStack { if !isTrailingTitle { TextInfinity(text: headerTitle) .font(.largeTitle) .foregroundColor(.white) .fontWeight(.bold) .padding() .frame(maxWidth: .infinity, alignment: .leading) } else { Spacer() TextInfinity(text: headerTitle) .font(.largeTitle) .foregroundColor(.white) .fontWeight(.bold) .padding() .frame(maxWidth: .infinity, alignment: .trailing) } } HStack { if !isTrailingTitle { TextInfinity(text: greeting) .font(.title) .fontWeight(.bold) .foregroundColor(.white) .padding(.leading) } else { Spacer() TextInfinity(text: greeting) .font(.title) .fontWeight(.bold) .foregroundColor(.white) .padding(.leading) .padding(.trailing) } } HStack { if !isTrailingTitle { TextInfinity(text: description) .font(.title3) .foregroundColor(.white) .padding(.leading) } else { Spacer() TextInfinity(text: description) .font(.title3) .foregroundColor(.white) .padding(.leading) .padding(.trailing) } } HStack { Spacer() TextInfinity(text: plan == "none" ? "NOT SUBSCRIBED" : planDescription.uppercased()) .font(.headline) .fontWeight(.bold) .foregroundColor(.white) .padding([.trailing, .bottom], 10) } } .padding(.top, 35) .frame(maxWidth: .infinity) .background(gradient) .padding(.horizontal, 0) .padding(.vertical, 20) .ignoresSafeArea() .onAppear { withAnimation(Animation.easeInOut(duration: 2).repeatForever(autoreverses: true)) { animateGradient.toggle() } } } } // Preview Provider struct UserProfileHeaderView_Previews: PreviewProvider { static var previews: some View { UserProfileHeaderView( plan: "Classic", headerTitle: "Your Profile", greeting: "Welcome!", planDescription: "Classic Plan", isTrailingTitle: false, description: "\( DeveloperPreview.shared.user.name ?? "" )\(( DeveloperPreview.shared.user.name != nil && DeveloperPreview.shared.user.name != "" ) ? " | " : "" )\( DeveloperPreview.shared.user.email )" ) } }
解决方案
问题出在你用布尔值切换渐变起点/终点,且仅触发一次状态变更——即便设置了repeatForever,SwiftUI也无法基于单次状态变化维持无限循环动画。改用浮点型状态变量控制渐变位置的平滑过渡,能实现类似Stripe的流畅动画效果:
修改后的完整代码
import SwiftUI struct UserProfileHeaderView: View { let plan: String let headerTitle: String let greeting: String let planDescription: String var isTrailingTitle: Bool let description: String // 改用浮点型变量控制渐变位置,实现平滑过渡 @State private var animateGradient = 0.0 var body: some View { let startColor = plan == "Classic" ? Color.purple : plan == "Plus" ? Color.red : Color(hex: "#636363") let endColor = plan == "Classic" ? Color.blue : plan == "Plus" ? Color.orange : plan == "Ultimate" ? Color(hex: "#010057") : Color(hex: "#636363") let gradient = LinearGradient( gradient: Gradient(colors: [startColor, endColor]), // 根据浮点值计算渐变起点和终点,实现平滑移动 startPoint: UnitPoint(x: animateGradient, y: 1 - animateGradient), endPoint: UnitPoint(x: 1 - animateGradient, y: animateGradient) ) VStack(alignment: .leading, spacing: 8) { // 原有布局代码保持不变 HStack { if !isTrailingTitle { TextInfinity(text: headerTitle) .font(.largeTitle) .foregroundColor(.white) .fontWeight(.bold) .padding() .frame(maxWidth: .infinity, alignment: .leading) } else { Spacer() TextInfinity(text: headerTitle) .font(.largeTitle) .foregroundColor(.white) .fontWeight(.bold) .padding() .frame(maxWidth: .infinity, alignment: .trailing) } } HStack { if !isTrailingTitle { TextInfinity(text: greeting) .font(.title) .fontWeight(.bold) .foregroundColor(.white) .padding(.leading) } else { Spacer() TextInfinity(text: greeting) .font(.title) .fontWeight(.bold) .foregroundColor(.white) .padding(.leading) .padding(.trailing) } } HStack { if !isTrailingTitle { TextInfinity(text: description) .font(.title3) .foregroundColor(.white) .padding(.leading) } else { Spacer() TextInfinity(text: description) .font(.title3) .foregroundColor(.white) .padding(.leading) .padding(.trailing) } } HStack { Spacer() TextInfinity(text: plan == "none" ? "NOT SUBSCRIBED" : planDescription.uppercased()) .font(.headline) .fontWeight(.bold) .foregroundColor(.white) .padding([.trailing, .bottom], 10) } } .padding(.top, 35) .frame(maxWidth: .infinity) .background(gradient) .padding(.horizontal, 0) .padding(.vertical, 20) .ignoresSafeArea() .onAppear { // 启动无限循环动画,让浮点值在0到1之间来回平滑变化 withAnimation(Animation.easeInOut(duration: 4).repeatForever(autoreverses: true)) { animateGradient = 1.0 } } } } // Preview Provider struct UserProfileHeaderView_Previews: PreviewProvider { static var previews: some View { UserProfileHeaderView( plan: "Classic", headerTitle: "Your Profile", greeting: "Welcome!", planDescription: "Classic Plan", isTrailingTitle: false, description: "\( DeveloperPreview.shared.user.name ?? "" )\(( DeveloperPreview.shared.user.name != nil && DeveloperPreview.shared.user.name != "" ) ? " | " : "" )\( DeveloperPreview.shared.user.email )" ) } }
关键修改说明
- 状态变量类型调整:将
@State private var animateGradient从布尔型改为浮点型,用于精确控制渐变位置的过渡。 - 渐变位置计算:通过
UnitPoint根据浮点值动态计算渐变的起点和终点,实现平滑的渐变流动效果,而非生硬的切换。 - 动画逻辑优化:在
onAppear中启动动画时,直接将浮点值从0过渡到1,结合repeatForever(autoreverses: true)实现无限循环的来回动画,持续时间设为4秒(可根据需求调整),更接近Stripe官网的动画节奏。
内容的提问来源于stack exchange,提问作者Alk
相关产品推荐
相关产品推荐

