0

I am building a custom tab bar in SwiftUI. The views are attached to tab bar buttons and load without any issues. But the data stored in the @State variable in the screen resets while tapping on the button.

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)
        }
    }
}

Screenshot of the app

Whenever the tab button is clicked, the count in that view resets to 0, but the init method is called once. Why is this happening? How can I preserve the state variable during the tab switch?

TIA

  • A SwiftUI view is not what you think it is ;) It's basically a _means_ to construct and mutate state of an "underlying view". Don't think a SwiftUI view is similar to a UIKit view object. So, specifically, `let tabs: [AnyView]` and the initialiser makes no sense. I would recommend introductory tutorials. – CouchDeveloper Jul 27 '23 at 08:33
  • @CouchDeveloper Can you suggest any such tutorial? – Aravind Bhuvanendran Jul 27 '23 at 10:42
  • 1
    There are quite a couple of very good tutorials in the web, inclusive Apple introduction. However, many rather quickly go into "how to build an app with SwiftUI" rather than explaining the underlying concepts. First you need to realise is, that SwiftUI is _very different_ than UIKit! SwiftUI is _declarative_, and how you build view systems with Swift is like using a DSL. So, while there are many very good tutorials explaining every aspect, this may be for you if you come from UIKit: https://swiftcraft.io/blog/swiftui-for-uikit-developers-part-1 – CouchDeveloper Jul 27 '23 at 11:17
  • 1
    Also, going deeper into details, Apple's official docs: https://developer.apple.com/tutorials/swiftui-concepts In additions to this, I would also recommend searching for specific topics elsewhere, for example https://www.hackingwithswift.com is one site, but there are many others, too. – CouchDeveloper Jul 27 '23 at 11:27
  • 1
    And of course WWDC videos, especially: https://developer.apple.com/videos/play/wwdc2019/216/ – CouchDeveloper Jul 27 '23 at 11:40

2 Answers2

1

This can help?

import SwiftUI

struct ContentView: View {
    @State var index = 0
    @State private var count1: UInt = 0
    @State private var count2: UInt = 0
    
    var body: some View {
        VStack(spacing: 0) {
            ZStack {
                switch index {
                case 0:
                    WelcomeView(count: $count1)
                default:
                    ProfileScreen(count: $count2)
                }
            }
            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 {
    @Binding var count: UInt
    @State private var firstAppear = false
    
    init(count: Binding<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 {
    @Binding var count: UInt
    
    init(count: Binding<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)
        }
    }
}
Dinox
  • 141
  • 1
  • 5
0

It has been a while since I used swift UI, but I believe the issue is the fact that you use tabs[index] in order to switch between components. Although you are creating a single instance of each view, the view’s body does not behave like a single instance. @State is not persistent between renders, hence the state disappears. You should hoist the variables into the ContentView and use a binding to make the state persistent since the ContentView will not need to rerender. Another solution is using something like MVVM in order to more professionally manage state in your application.

  • So you mean I cannot keep the values inside the views in tabbar because while rendering, the data will reset. The only way to retain is to keep those data in the content view or create a ViewModel for views to keep this data. – Aravind Bhuvanendran Jul 27 '23 at 05:58
  • That’s right. A state variable will reset to its default value any time it is rerendered. – stronglyTyped Jul 27 '23 at 06:44
  • @AndrewHamilton Actually, the `@State` is there to declare, that storage should be allocated for the underlying view. The life-time of this storage is bound to the life-time of the underlying view. The storage will be initialised _once_ (and only once) when the SwiftUI view takes the role as an initialiser and creates (for the first time) its associated underlying view. It then uses _this_ storage when rendered and when the body mutates it. In short, it's actually the other way around ;) – CouchDeveloper Jul 27 '23 at 08:37
  • Thanks for the clarification @CouchDeveloper! – stronglyTyped Jul 28 '23 at 05:06