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

SwiftUI自定义TabBar切换时视图@State变量重置问题求助

SwiftUI自定义TabBar切换时@State变量重置问题解析与解决

问题描述

使用SwiftUI构建自定义TabBar时,视图能正常加载,但点击切换标签时,页面内的@State变量数据会重置。示例代码如下:

struct ContentView: View {
    @State var index = 0
    let tabs: [AnyView]
    
    init() {
        print("### Contentview initialized ")
        tabs = [AnyView(WelcomeView(count: 0)),
                AnyView(ProfileScreen(count: 0))]
    }
    
    var body: some View {
        VStack(spacing: 0) {
            ZStack {
                tabs[index]
            }
            CustomTabbar(index: self.$index)
        }
        .navigationBarBackButtonHidden(true)
    }
}

struct CustomTabbar: View {
    
    @Binding var index: Int
    
    var body: some View {
        HStack(alignment: .bottom) {
            Button(action: {
                self.index = 0
            }) {
                VStack(spacing: 6) {
                    Image(systemName: "house")
                    Text("Welcome")
                        .font(.system(size: 12))
                }
                .foregroundColor(index == 0 ? .blue : .black)
            }
            
            Spacer(minLength: 15)
            
            Button(action: {
                self.index = 4
            }) {
                VStack(spacing: 6) {
                    Image(systemName: "person")
                    Text("My Profile")
                        .font(.system(size: 12))
                }
                .foregroundColor(index == 4 ? .blue : .black)
            }
        }
        .frame(height: 50)
        .padding(.horizontal, 25)
        .background(Color.white)
    }
}

struct WelcomeView: View {
    @State private var count: UInt = 0
    @State private var firstAppear = false
    
    init(count: UInt, firstAppear: Bool = false) {
        print("WelcomeView init")
        self.count = count
        self.firstAppear = firstAppear
    }
    
    var body: some View {
        VStack {
            Spacer()
            Text("Welcome view")
            Spacer()
        }.onAppear {
            count += 1
            print("WelcomeView appear",count)
        }
    }
}

struct ProfileScreen: View {
    
    @State private var count: UInt = 0
    
    init(count: UInt) {
        print("profile screen init")
        self.count = count
    }

    var body: some View {
        VStack {
            Spacer()
            Text("Profile screen")
            Spacer()
        }.onAppear {
            count += 1
            print("Profile view appear",count)
        }
    }
}

现象:每次点击标签按钮,对应视图的count会重置为0,但视图的init方法仅调用一次。

问题原因

  1. @State变量的生命周期与视图在SwiftUI视图树中的存在性绑定:当视图被从视图树中移除时,@State的存储空间会被销毁,状态数据丢失。
  2. 当前实现中,ZStack仅显示tabs[index]对应的视图,切换标签时,未选中的视图会被从视图树中移除。虽然tabs数组中的视图实例在ContentView初始化时已创建,但只有当视图处于活动状态(在视图树中)时,@State才会保持其状态;一旦被移除,状态就会被重置。

解决方案

方案一:保持所有视图在视图树中(通过隐藏而非移除)

修改ContentView的ZStack部分,让所有标签视图始终存在于视图树中,通过opacity或hidden控制显示与隐藏:

var body: some View {
    VStack(spacing: 0) {
        ZStack {
            ForEach(tabs.indices, id: \.self) { tabIndex in
                tabs[tabIndex]
                    .opacity(index == tabIndex ? 1 : 0)
                    .allowsHitTesting(index == tabIndex) // 只允许当前视图响应交互
            }
        }
        CustomTabbar(index: self.$index)
    }
    .navigationBarBackButtonHidden(true)
}

方案二:将状态提升到父视图或使用ViewModel管理

把子视图的@State变量提升到父视图,通过绑定传递给子视图;或者使用@StateObject/@ObservableObject创建独立的ViewModel,让状态与视图生命周期解耦:

方式1:状态提升到父视图

修改ContentView和子视图:

struct ContentView: View {
    @State var index = 0
    @State private var welcomeCount: UInt = 0
    @State private var profileCount: UInt = 0
    
    var body: some View {
        VStack(spacing: 0) {
            ZStack {
                WelcomeView(count: $welcomeCount)
                    .opacity(index == 0 ? 1 : 0)
                    .allowsHitTesting(index == 0)
                ProfileScreen(count: $profileCount)
                    .opacity(index == 4 ? 1 : 0)
                    .allowsHitTesting(index == 4)
            }
            CustomTabbar(index: self.$index)
        }
        .navigationBarBackButtonHidden(true)
    }
}

struct WelcomeView: View {
    @Binding var count: UInt
    
    var body: some View {
        VStack {
            Spacer()
            Text("Welcome view")
            Spacer()
        }.onAppear {
            count += 1
            print("WelcomeView appear",count)
        }
    }
}

struct ProfileScreen: View {
    @Binding var count: UInt
    
    var body: some View {
        VStack {
            Spacer()
            Text("Profile screen")
            Spacer()
        }.onAppear {
            count += 1
            print("Profile view appear",count)
        }
    }
}

方式2:使用ViewModel管理状态

创建独立的ViewModel:

class TabViewModel: ObservableObject {
    @Published var welcomeCount: UInt = 0
    @Published var profileCount: UInt = 0
}

struct ContentView: View {
    @State var index = 0
    @StateObject private var viewModel = TabViewModel()
    
    var body: some View {
        VStack(spacing: 0) {
            ZStack {
                WelcomeView(viewModel: viewModel)
                    .opacity(index == 0 ? 1 : 0)
                    .allowsHitTesting(index == 0)
                ProfileScreen(viewModel: viewModel)
                    .opacity(index == 4 ? 1 : 0)
                    .allowsHitTesting(index == 4)
            }
            CustomTabbar(index: self.$index)
        }
        .navigationBarBackButtonHidden(true)
    }
}

struct WelcomeView: View {
    @ObservedObject var viewModel: TabViewModel
    
    var body: some View {
        VStack {
            Spacer()
            Text("Welcome view")
            Spacer()
        }.onAppear {
            viewModel.welcomeCount += 1
            print("WelcomeView appear",viewModel.welcomeCount)
        }
    }
}

struct ProfileScreen: View {
    @ObservedObject var viewModel: TabViewModel
    
    var body: some View {
        VStack {
            Spacer()
            Text("Profile screen")
            Spacer()
        }.onAppear {
            viewModel.profileCount += 1
            print("Profile view appear",viewModel.profileCount)
        }
    }
}

注意事项

  • 方案一适合简单场景,但如果标签视图较多,会占用更多内存,因为所有视图都保持活跃。
  • 方案二更适合复杂场景,状态管理更清晰,也符合SwiftUI的单向数据流设计理念。

内容的提问来源于stack exchange,提问作者Aravind Bhuvanendran

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 19:23:13