You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 07:45:34