I'm migrating a large iOS project developed using VIPER architecture from ViewControllers to SwiftUi. To do this I'm using ViewAdapters to connect VIPER routers and presenters with SwiftUI views. Although this is working fine I'm having problems with a specific case:
The app has a tabbar and each tab has navigation controllers. I need that if the users is navigating insdide tab1, then changes to tab2 and then goes back to tab1, tab1 must show the last view, not the root one.
I'm trying to show a reduced version of my app:
MyView:
struct MainView: View {
@ObservedObject var observedModel: MainViewAdapter
@State private var tab1Id = UUID()
@State private var tab2Id = UUID()
let tab1 = Tab1Router()
let tab2 = Tab2Router()
init(observedModel: MainViewAdapter) {
self.observedModel = observedModel
}
var body: some View {
VStack(spacing: 0) {
TabView(selection: $observedModel.currentTab) {
tab1.swiftUIView
.tag("tab1")
.id(tab1Id)
tab2.swiftUIView
.tag("tab2")
.id(tab2Id)
}
MyCustomTabBar(observedModel: observedModel,
currentTab: $observedModel.currentTab)
}
.ignoresSafeArea(.keyboard, edges: .bottom)
}
}
Tab2 View (Tab1 View and its child views would be equivalent):
struct Tab2: View {
@ObservedObject var observedModel: Tab2ViewAdapter
@State private var hasToGoToChild2 = false
init(observedModel: Tab2ViewAdapter) {
self.observedModel = observedModel
}
var body: some View {
NavigationView {
VStack(spacing: 0) {
if hasToGoToChild2 {
NavigationLink(destination: Child2Router().swiftUIView.navigationBarHidden(true),
isActive: $hasToGoToChild2) {
}
.navigationBarHidden(true)
.hidden()
}
myLogFunction("refreshing Tab2")
Button {
hasToGoToChild2 = true
} label: {
Text("Go to child 2")
}
}
.navigationBarHidden(true)
.onAppear {
print("Tab2 onAppear")
hasToGoToChild2 = false
observedModel.presenter?.readData()
}
}
}
}
Child2 View:
struct Child2: View {
@ObservedObject var observedModel: Child2ViewAdapter
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
init(observedModel: Child2ViewAdapter) {
self.observedModel = observedModel
}
var body: some View {
NavigationView {
VStack(spacing: 0) {
Button {
presentationMode.wrappedValue.dismiss()
} label: {
Text("Back")
}
if observedModel.hasToGoToChild2_1 {
NavigationLink(destination: Child2_1_Router().swiftUIView.navigationBarHidden(true),
isActive: $observedModel.hasToGoToChild2_1) {
}
.navigationBarHidden(true)
.hidden()
}
...
if observedModel.hasToGoToChild2_n {
NavigationLink(destination: Child2_n_Router().swiftUIView.navigationBarHidden(true),
isActive: $observedModel.hasToGoToChild2_n) {
}
.navigationBarHidden(true)
.hidden()
}
List() {
let items = observedModel.items
myLogFunction("refreshing Child2 with items.count \(items.count)")
ForEach(items) { item in
Button(action: item.handler) {
Text("Go to item destination")
}
}
}
}
.navigationBarHidden(true)
.onAppear {
print("Child2 onAppear")
observedModel.hasToGoToChild2_1 = false
...
observedModel.hasToGoToChild2_n = false
observedModel.presenter?.readData()
}
}
}
}
Finally, Child2_x view:
struct Child2_x: View {
@ObservedObject var observedModel: Child2_xViewAdapter
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
init(observedModel: Child2_xViewAdapter) {
self.observedModel = observedModel
}
var body: some View {
NavigationView {
VStack(spacing: 0) {
Button {
presentationMode.wrappedValue.dismiss()
} label: {
Text("Back")
}
List() {
let items = observedModel.items
myLogFunction("refreshing Child2_x with items.count \(items.count)")
ForEach(items) { item in
Text(item.name)
}
}
}
.navigationBarHidden(true)
.onAppear {
print("Child2_x onAppear")
observedModel.presenter?.readData()
}
}
}
}
If I do:
- enter the app (tab1 by default) > click on tab2 > go to child2 > click on tab1 > go back to tab 2 then everything goes well and child2 is shown.
- enter the app (tab1 by default) > click on tab2 > go to child2 > go to child2_x > click on tab1 > go back to tab 2 then child2_x is shown for a second and then a blank view appears.
As you can see I've added logs inside the views to check what's going on and I don't understand anything. Could you help me understand whats wrong with the code?
Logs for case 1)
- enter the app (tab1 by default)
refreshing Tab2
- click on tab2
Tab2 onAppear
refreshing Tab2
refreshing Tab2
- go to child2
refreshing Tab2
refreshing Child2 with items.count 0
Child2 onAppear
refreshing Child2 with items.count 2
refreshing Child2 with items.count 2
refreshing Child2 with items.count 2
Child2 onAppear Question: Why is it calling onAppear again??
refreshing Child2 with items.count 2
- click on tab1
refreshing Child2 with items.count 2
- go back to tab 2
refreshing Child2 with items.count 2
refreshing Child2 with items.count 2
Child2 onAppear
refreshing Child2 with items.count 2
refreshing Child2 with items.count 2
refreshing Child2 with items.count 2
refreshing Child2 with items.count 2
Everything is OK, Child2 is shown with its 2 items.
Logs for case 2)
- enter the app (tab1 by default)
refreshing Tab2
- click on tab2
Tab2 onAppear
refreshing Tab2
refreshing Tab2
- go to child2
refreshing Tab2
refreshing Child2 with items.count 0
Child2 onAppear
refreshing Child2 with items.count 2
refreshing Child2 with items.count 2
refreshing Child2 with items.count 2
Child2 onAppear Question: Why is it calling onAppear again??
refreshing Child2 with items.count 2
- go to Child2_x
refreshing Child2 with items.count 2
refreshing Child2_x with items.count 0
Child2_x onAppear
refreshing Child2_x with items.count 1
refreshing Child2_x with items.count 1
refreshing Child2_x with items.count 1
- click on tab1
refreshing Child2 with items.count 2 Question: Why is it refreshing its parent view (Child2)??
refreshing Child2_x with items.count 1
refreshing Child2_x with items.count 0 Question: Why is it refreshing the view without items??
- go back to tab 2
refreshing Child2 with items.count 2 Question: Why is it refreshing its parent view (Child2)??
refreshing Child2_x with items.count 0
refreshing Child2 with items.count 2
refreshing Child2_x with items.count 0
Child2_x onAppear
refreshing Child2_x with items.count 1
refreshing Child2 with items.count 2
refreshing Child2_x with items.count 1
refreshing Child2_x with items.count 1
refreshing Child2 with items.count 2
refreshing Child2 with items.count 2
refreshing Child2_x with items.count 1
refreshing Child2_x with items.count 0 Question: Why is it refreshing the view without items??
I think this is the problem, this 0 items shows no items in the list and therefore a blank view