SwiftUI落地页需求:点击按钮不跳转新视图且保留背景
问题描述
现有一个分两部分的落地页:一部分展示图片,另一部分包含「登录(Login)」和「注册(Get Started)」按钮。需要实现两个效果:
- 点击按钮时不跳转到全新视图,而是在当前视图内切换内容
- 导航切换过程中,背景的渐变动画保持不变
目前已通过ZStack设置了渐变背景,但点击NavigationLink仍会完全跳转到新视图,无法实现预期效果。
附当前代码:
extension View { func animatableGradient(fromGradient: Gradient, toGradient: Gradient, progress: CGFloat) -> some View { self.modifier(AnimatableGradientModifier(fromGradient: fromGradient, toGradient: toGradient, progress: progress)) } } struct AnimatableGradientModifier: AnimatableModifier { let fromGradient: Gradient let toGradient: Gradient var progress: CGFloat = 0.0 //keeps track of gradient change var animatableData: CGFloat { get { progress } set { progress = newValue } } func body(content: Content) -> some View { var gradientColors = [Color]() for i in 0..<fromGradient.stops.count { let fromColor = UIColor(fromGradient.stops[i].color) let toColor = UIColor(toGradient.stops[i].color) gradientColors.append(colorMixer(fromColor: fromColor, toColor: toColor, progress: progress)) } return LinearGradient(gradient: Gradient(colors: gradientColors), startPoint: .topLeading, endPoint: .bottomTrailing) } func colorMixer(fromColor: UIColor, toColor: UIColor, progress: CGFloat) -> Color { guard let fromColor = fromColor.cgColor.components else { return Color(fromColor) } guard let toColor = toColor.cgColor.components else { return Color(toColor) } let red = fromColor[0] + (toColor[0] - fromColor[0]) * progress let green = fromColor[1] + (toColor[1] - fromColor[1]) * progress let blue = fromColor[2] + (toColor[2] - fromColor[2]) * progress return Color(red: Double(red), green: Double(green), blue: Double(blue)) } } struct LandingPage: View { @AppStorage("signedIn") var signedIn = false @State private var isPressed = false @Environment (\.dismiss) var dismiss @State private var progress: CGFloat = 0 //colors for background on landing page let gradient1 = Gradient(colors: [.yellow, .orange]) let gradient2 = Gradient(colors: [.yellow, .pink]) @State private var animateGradient = false @State var scale: CGFloat = 1.0 @State var offsetValue: CGFloat = -60 // << image @State var isMealJournalTitleShowing = false var body: some View { NavigationView{ ZStack{ Rectangle() .animatableGradient(fromGradient: gradient1, toGradient: gradient2, progress: progress) .ignoresSafeArea() VStack{ test() VStack{ NavigationLink(destination: testb() .navigationBarHidden(true), label:{ Text("Get Started").fontWeight(.bold) .frame(minWidth: 0, maxWidth: 200) .padding(10) .foregroundColor(.white) //draw rectange around buttons .background( RoundedRectangle(cornerRadius: 20) .fill( LinearGradient( colors: [.orange, .yellow], startPoint: .topLeading, endPoint: .bottomTrailing ))) }) .simultaneousGesture(TapGesture().onEnded{ offsetValue = -125 scale -= 0.50 isMealJournalTitleShowing = true }) NavigationLink(destination: testb().navigationBarHidden(true), label: { Text("Login").fontWeight(.semibold) .frame(minWidth:0, maxWidth: 200) .padding(10) .foregroundColor(.black) .overlay( RoundedRectangle(cornerRadius: 25) .stroke(Color.gray, lineWidth: 3) ) }) } .frame(height: 500) } .frame(height: 500) } .onAppear { DispatchQueue.main.async { withAnimation(.linear(duration: 2.0).repeatForever(autoreverses: true)) { self.progress = 1.0 } } } .animation(.easeInOut(duration: 0.50), value: offsetValue) } } }
解决方案
问题核心在于NavigationLink默认会将新视图推送至导航栈,替换当前视图层级。要实现「当前视图内切换+背景保持」的效果,需用状态变量控制视图显隐替代跳转逻辑:
1. 添加状态变量控制视图切换
在LandingPage中新增状态变量,控制登录/注册视图的显示:
@State private var showAuthView = false
2. 替换NavigationLink为按钮+状态切换
将原NavigationLink改为普通按钮,点击时切换showAuthView状态,保留原有动画逻辑:
// 替换Get Started的NavigationLink Button(action: { offsetValue = -125 scale -= 0.50 isMealJournalTitleShowing = true showAuthView = true // 触发视图显示 }) { Text("Get Started").fontWeight(.bold) .frame(minWidth: 0, maxWidth: 200) .padding(10) .foregroundColor(.white) .background( RoundedRectangle(cornerRadius: 20) .fill( LinearGradient( colors: [.orange, .yellow], startPoint: .topLeading, endPoint: .bottomTrailing ))) } // 替换Login的NavigationLink Button(action: { showAuthView = true // 触发视图显示 }) { Text("Login").fontWeight(.semibold) .frame(minWidth:0, maxWidth: 200) .padding(10) .foregroundColor(.black) .overlay( RoundedRectangle(cornerRadius: 25) .stroke(Color.gray, lineWidth: 3) ) }
3. 在ZStack中叠加目标视图
在原ZStack内添加目标视图(testb()),用showAuthView控制显隐,并添加过渡动画:
ZStack{ // 原背景和内容... // 叠加显示登录/注册视图 if showAuthView { testb() .navigationBarHidden(true) .transition(.move(edge: .trailing)) // 自定义过渡动画 .onTapGesture { showAuthView = false // 可选:点击空白关闭视图 } } }
4. 移除多余的NavigationView
原代码中的NavigationView已不再需要,直接移除即可避免不必要的导航层级干扰。
修改后的完整代码
extension View { func animatableGradient(fromGradient: Gradient, toGradient: Gradient, progress: CGFloat) -> some View { self.modifier(AnimatableGradientModifier(fromGradient: fromGradient, toGradient: toGradient, progress: progress)) } } struct AnimatableGradientModifier: AnimatableModifier { let fromGradient: Gradient let toGradient: Gradient var progress: CGFloat = 0.0 var animatableData: CGFloat { get { progress } set { progress = newValue } } func body(content: Content) -> some View { var gradientColors = [Color]() for i in 0..<fromGradient.stops.count { let fromColor = UIColor(fromGradient.stops[i].color) let toColor = UIColor(toGradient.stops[i].color) gradientColors.append(colorMixer(fromColor: fromColor, toColor: toColor, progress: progress)) } return LinearGradient(gradient: Gradient(colors: gradientColors), startPoint: .topLeading, endPoint: .bottomTrailing) } func colorMixer(fromColor: UIColor, toColor: UIColor, progress: CGFloat) -> Color { guard let fromColor = fromColor.cgColor.components else { return Color(fromColor) } guard let toColor = toColor.cgColor.components else { return Color(toColor) } let red = fromColor[0] + (toColor[0] - fromColor[0]) * progress let green = fromColor[1] + (toColor[1] - fromColor[1]) * progress let blue = fromColor[2] + (toColor[2] - fromColor[2]) * progress return Color(red: Double(red), green: Double(green), blue: Double(blue)) } } struct LandingPage: View { @AppStorage("signedIn") var signedIn = false @State private var isPressed = false @Environment (\.dismiss) var dismiss @State private var progress: CGFloat = 0 let gradient1 = Gradient(colors: [.yellow, .orange]) let gradient2 = Gradient(colors: [.yellow, .pink]) @State private var animateGradient = false @State var scale: CGFloat = 1.0 @State var offsetValue: CGFloat = -60 @State var isMealJournalTitleShowing = false // 新增状态变量控制视图显示 @State private var showAuthView = false var body: some View { ZStack{ Rectangle() .animatableGradient(fromGradient: gradient1, toGradient: gradient2, progress: progress) .ignoresSafeArea() VStack{ test() VStack{ // 替换为Button Button(action: { offsetValue = -125 scale -= 0.50 isMealJournalTitleShowing = true showAuthView = true }) { Text("Get Started").fontWeight(.bold) .frame(minWidth: 0, maxWidth: 200) .padding(10) .foregroundColor(.white) .background( RoundedRectangle(cornerRadius: 20) .fill( LinearGradient( colors: [.orange, .yellow], startPoint: .topLeading, endPoint: .bottomTrailing ))) } // 替换为Button Button(action: { showAuthView = true }) { Text("Login").fontWeight(.semibold) .frame(minWidth:0, maxWidth: 200) .padding(10) .foregroundColor(.black) .overlay( RoundedRectangle(cornerRadius: 25) .stroke(Color.gray, lineWidth: 3) ) } } .frame(height: 500) } .frame(height: 500) // 叠加显示目标视图 if showAuthView { testb() .navigationBarHidden(true) .transition(.move(edge: .trailing)) .onTapGesture { showAuthView = false } } } .onAppear { DispatchQueue.main.async { withAnimation(.linear(duration: 2.0).repeatForever(autoreverses: true)) { self.progress = 1.0 } } } .animation(.easeInOut(duration: 0.50), value: offsetValue) } } // 示例占位视图 struct test: View { var body: some View { Text("图片展示区域") .font(.largeTitle) } } struct testb: View { var body: some View { VStack{ Text("登录/注册页面") .font(.title) // 此处添加登录/注册表单内容 } .padding() .background(Color.white) .cornerRadius(20) .padding(.horizontal) } }
关键说明
- 用
@State变量控制视图显隐,避免NavigationLink跳转导致的视图层级替换,确保背景渐变动画持续运行 - 目标视图通过
ZStack叠加在原视图上方,可自定义过渡动画(如.opacity、.scale等)匹配预期效果 - 移除多余的
NavigationView,简化视图层级
内容的提问来源于stack exchange,提问作者Swink
相关产品推荐
相关产品推荐

