Strange behavior with IfCaseLet, SwitchStore, and TabView #1348
Replies: 3 comments 7 replies
-
This is definitely strange, and I think this would happen even on main right now, but it seems to be a bug in SwiftUI. Take for example this simple vanilla SwiftUI view: struct ContentView: View {
@State var isLoading = true
var body: some View {
TabView {
VStack {
Text("Tab 1")
if self.isLoading {
ProgressView()
Text("Loading")
.onAppear { print("onAppear") }
}
}
.tabItem { Label("First", systemImage: "list.dash") }
Text("Tab 2")
.tabItem { Label("Second", systemImage: "list.dash") }
}
.task {
try? await Task.sleep(nanoseconds: NSEC_PER_SEC * 2)
self.isLoading = false
}
}
} If I run this, and quickly navigate to the 2nd tab before the 2 second sleep finishes, I will see "onAppear" printed to the console twice. Once when the loading text actually appears, and then again 2 seconds later when it should be disappearing. So it seems that SwiftUI is sending an |
Beta Was this translation helpful? Give feedback.
-
I'm a bit late to this thread but the behaviour you are describing seems suspiciously similar to what I've just discovered with TabView in SwiftUI. It seems that the Here's a quick gist that shows the behaviour. https://gist.github.com/oliverfoggin/25f54a66c7eb8dfa8a3ed6f0db239764 When you tap on "Login" and then navigate to another tab then tap "Logout" you will see the "On Appear" in the console for the tabs you visited. |
Beta Was this translation helpful? Give feedback.
-
This happened to me and lead to firing unnecessary effects. Here is my TabView implementation to workaround the useless renders to the non-current-selected tab and also have import ComposableArchitecture
import Foundation
import Generated
import HomeFeature
import NotificationsFeature
import Styleguide
import SwiftUI
public struct TabView: View {
let store: StoreOf<Tab>
public init(store: StoreOf<Tab>) {
self.store = store
}
public var body: some View {
WithViewStore(self.store.scope(state: ViewState.init, action: Tab.Action.init)) { viewStore in
SwiftUI.TabView(selection: viewStore.binding(get: \.selectedTab, send: ViewAction.selectTab)) {
Group {
if viewStore.selectedTab == .home {
NavigationView {
HomeView(
store: store.scope(
state: \.homeState,
action: Tab.Action.home
)
)
}
.accentColor(.white)
.navigationViewStyle(StackNavigationViewStyle())
} else {
Text("Not Initialised: Home")
}
}
.tabItem {
Label("Home", systemImage: "calendar")
}
.tag(TabItem.home)
Group {
if viewStore.selectedTab == .notifications {
NavigationView {
NotificationsView(
store: store.scope(
state: \.notificationsState,
action: Tab.Action.notifications
)
)
}
.accentColor(.white)
.navigationViewStyle(StackNavigationViewStyle())
} else {
Text("Not Initialised: Notifications")
}
}
.tabItem {
Label(L10n.notifications, systemImage: "bell.fill")
}
.backport
.badge(viewStore.unreadNotificationCount)
.tag(TabItem.notifications)
}
}
.alert(self.store.scope(state: \.alert), dismiss: .alertDismissed)
}
}
public extension TabView {
struct ViewState: Hashable {
var selectedTab: TabItem
var unreadNotificationCount: Int
init(_ state: Tab.State) {
self.selectedTab = state.selectedTab
self.unreadNotificationCount = state
.notificationsState
.unreadCount
}
}
}
public extension TabView {
enum ViewAction: Hashable {
case selectTab(TabItem)
}
}
extension Tab.Action {
init(_ action: TabView.ViewAction) {
switch action {
case let .selectTab(tabItem):
self = .selectTab(tabItem)
}
}
}
// MARK: Tab Previews
struct Tab_Previews: PreviewProvider {
static var previews: some View {
TabView(
store: .init(
initialState: .init(
homeState: .init(timeZone: .autoupdatingCurrent),
notificationsState: .init(timeZone: .autoupdatingCurrent),
timeZone: .autoupdatingCurrent
),
reducer: Tab()
)
)
}
} |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Hi! I'm trying out
protocol-beta
branch with Xcode 14 RC, iOS 16 and I see some behaviors I don't quite understand. I'd appreciate any insights on what's happening here.There's TabView with two tabs. The first one has some kind of an async loading process and SwitchStore with ifCaseLet reducers. The second one is a dumb empty tab just for demonstration purposes.
To simulate the loading process in the first tab I return
.task
effect withTask.sleep
inside of it, expecting, that SwitchStore will replace the loading view with the content view. And it does, but changing tabs during this sleep simulated loading breaks just about everything. I see a warning mentioning ifCaseLet reducer and the first tab's view stops updating.I'm not sure if the problem is with how I configured things or if there's a bug in some of the TCA new fancy tools. Also, I suppose there might be better ways to organize states like load data from the first screen's main reducer instead of doing it in the loading state reducer.
Here's sample project: https://github.com/swasta/tca-switchstore-test
Beta Was this translation helpful? Give feedback.
All reactions