3

I'm having a weird problem that I can't seem to figure out with SwiftUI. I have a TabView in my ContentView, there are 3 tabs (chat list, user list, and profile) the app loads up on the chat list tab. The problem is, when I select the second tab (user list) it goes to that tab for a split second, then goes right back to the chat list. It doesn't make any sense to me. The 2 main tabs make an API call to get information from a server, and everything is working great, except that first click.

The app loads up, I click the user list tab and it shows for a split second, then goes back to the chat list tab. I can then click the user list tab again and it will go to that tab and stay there, but the first click on that tab always sends you back to the chat list tab.

I'll post up some of my code, hopefully someone will be able to see what I'm doing wrong, because I sure can't.

ContentView

struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext

@State var selectedTab = 0
@State var setup: Bool = false
@State var notificationChatID: String = ""

@ObservedObject var userModel: UserModel = UserModel()
@ObservedObject var chatModel: ChatModel = ChatModel()
@ObservedObject var appState: AppState = AppState.shared

var pushNavigationBinding : Binding<Bool> {
    .init { () -> Bool in
        appState.selectedChatID != nil
    }
    set: { (newValue) in
        if !newValue {
            appState.selectedChatID = nil
        }
    }
}

let settings = UserDefaults.standard

var body: some View {
    ZStack {
        if setup {
            TabView(selection: $selectedTab) {
                ChatList(launchedChatID: appState.selectedChatID ?? "", userModel: userModel, chatModel: chatModel)
                    .tabItem {
                        Image(systemName: "message.circle.fill")
                        Text("Active Chats")
                    }
                UserList(userModel: userModel)
                    .tabItem {
                        Image(systemName: "person.3.fill")
                        Text("User List")
                    }
                ProfileView()
                    .tabItem {
                        Image(systemName: "person.crop.circle")
                        Text("Profile")
                    }
            }
        } else {
            Onboarding(userModel: userModel, isSetup: $setup)
        }
    }
    .onReceive(NotificationCenter.default.publisher(for: Notification.Name.NewMessage), perform: { notification in
        if let info = notification.userInfo {
            let chatID = info["chatID"] as? String ?? ""
            if chatID != "" {
                chatModel.selectedChat = chatID
                appState.selectedChatID = chatID
                self.notificationChatID = chatID
            }
        }
    })
}

Then my UserList

struct UserList: View {

@ObservedObject var userModel: UserModel
@ObservedObject var chatModel: ChatModel = ChatModel()

@State var action: Int? = -1

var body: some View {
    NavigationView {
        VStack {
            List {
                ForEach(0..<userModel.arrayOfUsers.count, id: \.self) { index in
                    ZStack(alignment: .leading) {
                        NavigationLink(
                            destination: ChatView(model: chatModel, userModel: userModel, item: $action),
                            tag: index,
                            selection: $action
                        ) {
                            EmptyView().frame(width: .zero, height: .zero, alignment: .center)
                        }
                        Button(action: {
                            print("You selected \(userModel.arrayOfUsers[index].name)")
                            userModel.selectedUserName = userModel.arrayOfUsers[index].name
                            userModel.selectedUserID = userModel.arrayOfUsers[index].id
                            self.action = index
                        }) {
                            Text(userModel.arrayOfUsers[index].name)
                        }
                    }
                }
            }
            Spacer()
        }.navigationBarTitle(Text("Users"), displayMode: .inline)
    }.onAppear() {
        print("Inside the userlist on appear")
        if userModel.arrayOfUsers.count == 0 {
            ApiService.getUsers() { (res) in
                switch(res) {
                case .success(let response):
                    print("In success")
                    let users = response!.users!
                    DispatchQueue.main.async {
                        for user in users.users {
                            userModel.arrayOfUsers.append(user)
                        }
                    }
                    break
                case .failure(let error):
                    print("Error getting users")
                    print(error)
                    break
                }
            }
        }
    }
}
}

My userModel.arrayOfUsers is @Published UserModel

class UserModel: ObservableObject {
    var name: String = ""
    var id: String = ""
    var myUserID: String = ""
    @Published var arrayOfUsers: [User] = []
    var selectedUserID: String = ""
    var selectedUserName: String = ""
}

In the console in Xcode I see

Inside the userlist on appear
...(network stuff)
In the success
In the on appear in ChatList

So it's loading the UserList, it shows the network call out to my API, it shows the In the success from the API call in the UserList, then the very next thing is back to the In the on appear in ChatList I can't figure out why it's kicking me back to the chat list.

Neglected Sanity
  • 1,770
  • 6
  • 23
  • 46

1 Answers1

4

You're binding your TabView's current tab to $selectedTab, but not providing SwiftUI with any information on how to alter that value when the user changes tabs. And so, because selectedTab hasn't changed, when the drawing system comes to review your view structure, it still concludes that you want to see the first tab.

You should add a .tag modifier after each .tabItem to tell SwiftUI what values represent each tab. Then, when the user selects each tab, selectedTab will be updated and the tab choice will "stick".

For example:

TabView(selection: $selectedTab) {
    ChatList(launchedChatID: appState.selectedChatID ?? "", userModel: userModel, chatModel: chatModel)
        .tabItem {
            Image(systemName: "message.circle.fill")
            Text("Active Chats")
        }
        .tag(0)
    UserList(userModel: userModel)
        .tabItem {
            Image(systemName: "person.3.fill")
            Text("User List")
        }
        .tag(1)
    ProfileView()
        .tabItem {
            Image(systemName: "person.crop.circle")
            Text("Profile")
        }
        .tag(2)
}

Note that unless you're persisting the user's choice in some way (e.g., by declaring your state variable with @SceneStorage) you can get the same effect by not using a selection argument at all.

ScottM
  • 7,108
  • 1
  • 25
  • 42