TabView is stale in if ... else ...
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
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
Metype? - Ho do you instantiate the
CustomTabViewview? - What iOS version are you using?
Feel free to share anything else that could help me understand what is going on.
Thank you
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!
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.
Thanks @wchen258 @bobergj!
I've just released a new version of the library with the fix.
Happy coding 🙂