SwiftUI-CustomTabView icon indicating copy to clipboard operation
SwiftUI-CustomTabView copied to clipboard

TabView is stale in if ... else ...

Open wchen258 opened this issue 8 months ago • 3 comments

The following View when used as a View for a Tab, will keep using the stale View, and not updating

struct HomeViewWrapper: View {
    
    var body: some View {
            if let _ = Me.shared.user, let _ = Me.shared.token {
                HomeView()
            } else {
                LoginView()
            }
    }
}

However, the code will work, if the if else is wrapped in a ZStack i.e.

struct HomeViewWrapper: View {
    
    var body: some View {
            ZStack {
                    if let _ = Me.shared.user, let _ = Me.shared.token {
                           HomeView()
                     } else {  
                            LoginView()
                     }
            }
    }
}

The UI will update correctly. To be more sure, the following code

struct HomeViewWrapper: View {
    
    @State var isLoggedIn: Bool = false
    
    var body: some View {
        Group {
            switch isLoggedIn {
            case true:
                let _ = print("Should see Home")
                HomeView()
            case false:
                LoginView()
            }
        }
        .onAppear {
            if let _ = Me.shared.user, let _ = Me.shared.token {
                isLoggedIn = true
                print("find token")
            } else {
                print("cannot find token")
            }
        }       
    }
}

Will print "find token" and "Should see Home" when onAppear, however the UI stuck at LoginView(). Considering the one without the ZStack always works when I using the native SwiftUI TabView, I would consider this is a bug :)))

BTW, CustomTabView is indeed highly customizable and easy to use. Thank you for the great project

wchen258 avatar May 03 '25 02:05 wchen258

Hi @wchen258!

Thank you, I really appreciate 🙏

Regarding your problem, could you please provide me with more details about the project?

Particularly:

  • What is the definition of the Me type?
  • Ho do you instantiate the CustomTabView view?
  • What iOS version are you using?

Feel free to share anything else that could help me understand what is going on.

Thank you

NicFontana avatar May 03 '25 15:05 NicFontana

Hi Nic, here are more details :D

Xcode Version 16.1 (16B40) iOS Deployment Target iOS 17.5 Physical device iPhone 15 pro max

Me is defined as

@Observable class Me {
    static let shared = Me()
    var token: String? = nil
    var user: UserViewModel? = nil

The CustomTabView is used in CentralTabView

struct CentralTabView: View {
    
    // code omitted ...

    @State private var tabSelection: Tabb = .browse
    
    private var tabBarView: ThisTabBarView {
        ThisTabBarView(selection: $tabSelection) { tab in
            
        }
    }
    
    var body: some View {
        CustomTabView(tabBarView: tabBarView, tabs: Tabb.allCases, selection: tabSelection) {
            // code omitted
        
            HomeViewWrapper()
                .environment(\.tabSelection, $tabSelection)
        }
        .ignoresSafeArea()
        .tabBarPosition(.floating(.bottom))
        .persistentSystemOverlays(.hidden)
    }

ThisTabBarView is pretty much a modification to the README example

enum Tabb: String, Hashable, CaseIterable {
    case browse, create, me
}

struct ThisTabBarView: View {
    @Binding var selection: Tabb
    let onTabSelection: (Tabb) -> Void
    
    var body: some View {
        HStack {
            ForEach(Tabb.allCases, id: \.self) { tab in
                tabBarItem(for: tab)
                    .frame(maxWidth: .infinity)
                    .contentShape(Rectangle())
                    .onTapGesture {
                        selection = tab
                        onTabSelection(tab)
                    }
            }
        }
        .animation(.easeInOut, value: self.selection)
        .frame(width: UIScreen.main.bounds.width * 0.48, height: 64)
        .background(
            Capsule(style: .circular)
                .foregroundStyle(.ultraThickMaterial)
        )
        .opacity(selection == .create ? 0 : 1)
        .padding(.bottom, 20)
    }
    
    private func tabBarItem(for tab: Tabb) -> some View {
        Group {
            switch (tab) {
            case .browse:
                Image(systemName: "square.grid.2x2.fill")
                        // omitted...
                        }
            case .create:
                Image(systemName: "plus")
                    // omitted
                    }
            case .me:
                Group {
                    if let myId = Me.shared.user?.id {
                        ProfilePicture(selectedAvatarUIImage: .constant(nil), avatarURL: Me.shared.user?.avatarUrl, size: 44, userId: myId )
                    } else {
                        Image(systemName: "person.circle")
                            .imageScale(.large)
                            .foregroundStyle(self.selection == .me ? .white : .gray)
                            .frame(width: 60, height: 60)
                    }
                }
                .background {
                    Circle()
                        .frame(width: 54, height: 54)
                        .foregroundStyle(selection == .me ? .gray : .clear)
                }
            }
        }
    }
}

One thing to notice is that, I used Group in tabBarItem to pass compilation, which might be the culprit of the bug(?.

The App's main is the CentralTabView (a wrapper to CustomTabView). but I have a NavigationStack wrapping around it

    
    var body: some Scene {
        WindowGroup {
            NavigationStack(path: $navStack) {
                CentralTabView()
                    .environment(postEngine)
                    .navigationDestination(
                        // omitted
                    }
                    .overlay {
                        if !self.isReadyLanding {
                            ZStack {
                                ViewBlocker()
                            }
                            .ignoresSafeArea()
                            .animation(.easeInOut(duration: 1), value: self.isReadyLanding)
                        }
                    }
            }
            .environment(\.navStack, $navStack)

            .task {

Hope these are helpful!

wchen258 avatar May 03 '25 20:05 wchen258

I noticed the same thing. In my fork (which has other substantial and breaking changes) I solved this by updating the root view of each tab's UIHostingController in updateUIViewController: https://github.com/nomasystems/swiftui-custom-tab-view/blob/main/Sources/CustomTabView/iOS/UITabBarControllerRepresentable.swift#L38-L41 Without this the initial copy of the tab root view's, set in makeUIViewController, is displayed infinitely.

bobergj avatar May 07 '25 06:05 bobergj

Thanks @wchen258 @bobergj!

I've just released a new version of the library with the fix.

Happy coding 🙂

NicFontana avatar Jul 29 '25 13:07 NicFontana