There are basically 3 screens in the application, these are splash screen, auth screen and app screen.In Root View, it shows the splash screen first and then directs it to auth or app according to the user status. Here I am making a network request while looking at the user's status. But after I do this, I direct the user to the app screen and the Tab view in the App Screen does not work, it freezes. When i press and hold on tab item tab view works but when i click it doesn't work.
Network Request Code:
func fetchWithURLRequest<T: Decodable>(_ urlRequest: URLRequest) -> AnyPublisher<T, Error> {
URLSession.shared.dataTaskPublisher(for: urlRequest)
.mapError({ $0 as Error })
.flatMap({ result -> AnyPublisher<T, Error> in
guard let urlResponse = result.response as? HTTPURLResponse, (200...299).contains(urlResponse.statusCode) else {
return Just(result.response)
.tryMap({ response in
if let urlResponse = response as? HTTPURLResponse {
let apiError = APIError(statusCode: urlResponse.statusCode)
throw apiError
} else {
throw APIError(statusCode: 0)
}
}).eraseToAnyPublisher()
}
return Just(result.data).decode(type: T.self, decoder: JSONDecoder()).eraseToAnyPublisher()
})
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
I'm using this network request in this ViewModel Class:
class RootViewModel: ObservableObject {
@Published var containedView: ContainedView = .splash
@Published var protectedError = false
var isError = false
var cancellables = Set<AnyCancellable>()
let apiService = APIService()
let keychain = KeychainSwift()
func checkIsAuth() {
if Auth.auth().currentUser == nil {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
withAnimation {
self.containedView = .auth
}
}
} else {
self.getUserInfo()
}
}
func getUserInfo() {
let userID = keychain.get("userId") ?? ""
Route.sharedInstance.urlComponent.path = Route.Path.getUserInfo.rawValue
Route.sharedInstance.urlComponent.queryItems = [
URLQueryItem(name: "user_id", value: userID)
]
var urlRequest = URLRequest(url: Route.sharedInstance.urlComponent.url!)
urlRequest.addValue("Bearer \(BEARER_TOKEN)", forHTTPHeaderField: "authorization")
let publisher: AnyPublisher<User, Error> = apiService.fetchWithURLRequest(urlRequest)
publisher.sink(receiveCompletion: { (completion) in
if case .failure(let error) = completion,
let _ = error as? APIError {
self.containedView = .auth
}
}) { (user) in
CurrentUser.addCurrentUser(user: user)
withAnimation {
if !user.protected {
if !self.isError {
self.containedView = .app
} else {
self.containedView = .auth
}
} else {
self.protectedError = true
}
}
}
.store(in: &cancellables)
}
}
I'm using View Model in View of this screen:
enum ContainedView {
case splash, auth, app
}
struct RootView: View {
@ObservedObject var rootVM = RootViewModel()
var body: some View {
ZStack {
containedView()
.transition(.slide)
}
.onAppear {
NotificationCenter.default.addObserver(forName: NSNotification.Name("signOut"), object: nil, queue: .main) { (_) in
self.rootVM.containedView = .auth
}
self.rootVM.checkIsAuth()
}
.popup(isPresented: $rootVM.protectedError, type: .`default`, closeOnTap: false) {
PopUpView(title: "Oops!", imageName: "protected_account", description: "To use this app, you must be having public account") {
self.rootVM.protectedError = false
self.rootVM.containedView = .auth
}
}
}
func containedView() -> AnyView {
switch rootVM.containedView {
case .splash: return AnyView(SplashView().id("splash"))
case .auth: return AnyView(AuthView().environmentObject(TwitterService()).id("auth"))
case .app: return AnyView(AppView().id("app"))
}
}
And the app screen:
struct AppView: View {
init() {
UITabBar.appearance().barTintColor = UIColor(named: "BackgroundBlue")
}
var body: some View {
TabView {
InspectedView().tabItem {
Image(systemName: "eye")
Text("Inspected")
}.tag(0)
ProfileView().tabItem {
Image(systemName: "house")
Text("Profile")
}.tag(1)
MoreView().tabItem {
Image(systemName: "ellipsis")
Text("More")
}.tag(2)
}
.accentColor(.white)
}
}