TabView - Index out of range

I want to create a swiping TabView with PageTabViewStyle, each tab will show String content from a Firestore document.

like this:

I succeed to use ForEach to show the contents in a VStack

But when I use ForEach to show the contents in a TabView

I got “Thread 1: Fatal error: Index out of range” but can’t find the cause yet

This is the code of the TabView:

import SwiftUI

struct CardListView: View {

    @EnvironmentObject var FService: MyFirebaseService
    @EnvironmentObject var CardListVM: CardsViewModel
    var colors: [Color] = [ .orange, .green, .yellow, .pink, .purple]
    

    var body: some View {

        TabView(){
            ForEach(CardListVM.Cards){ item in
                ZStack{
                    Rectangle()
                        .fill(colors.randomElement() ?? Color.yellow)
                        .frame(width: nil, height: 120)
                        .cornerRadius(10)
                        .padding(.horizontal, 10)
                        .padding(.vertical, 15)
                    ScrollView{
                        Text(item.content)
                    }
                    .frame(width: nil, height: 100)
                    .padding(.horizontal, 20)
                    .padding(.vertical, 25)
                }
            }
        }
        .tabViewStyle(PageTabViewStyle())
        .onAppear {
            CardListVM.getCards()
        }
    }
}

This is the fetch data code

import Foundation
import Firebase
import FirebaseFirestoreSwift

class CardsViewModel: ObservableObject{
    @Published var Cards = [Card]()
    
    func getCards(){
        let db = Firestore.firestore()
        let collection = db.collection("Card")
        
        collection.whereField("userID", isEqualTo: Auth.auth().currentUser?.uid)
            .getDocuments { QuerySnapshot, Error in
                if let ErrorUW = Error {
                    print("Error: \(ErrorUW)")
                } else {
                for document in QuerySnapshot!.documents{
                    do {
                        let object = try document.data(as: Card.self)
                        self.Cards.append(object!)
                    } catch {
                        print("could not parse doc data")
                    }
                }
            }
        }
    }
}

Model

import Foundation
import Firebase
import FirebaseFirestoreSwift

struct Card: Identifiable, Codable{
    @DocumentID var id: String?
    var content: String
    var userID: String
    var category: String
}

This is when I use VStack, there are no Index out of range issue

import SwiftUI

struct CardListView: View {
    @EnvironmentObject var FService: MyFirebaseService
    @EnvironmentObject var CardListVM: CardsViewModel
    var colors: [Color] = [ .orange, .green, .yellow, .pink, .purple].shuffled()
    

    var body: some View {
                
        VStack{
            ForEach(CardListVM.Cards){ item in
                ZStack{
                    Rectangle()
                        .fill(colors.randomElement() ?? Color.yellow)
                        .frame(width: nil, height: 120)
                        .cornerRadius(10)
                        .padding(.horizontal, 10)
                        .padding(.vertical, 15)
                    ScrollView{
                        Text(item.content)
                    }
                    .frame(width: nil, height: 100)
                    .padding(.horizontal, 20)
                    .padding(.vertical, 25)
                }
            }
        }
        .onAppear {
            CardListVM.getCards()
        }
    }
}

Is there possibly a limit to the number of items you can have in a TabView?

Currently it is depends on how many Documents from Firestore is being added to the array
The program will only know after finish the query

Is there any limitation of number of item for TabView?

What I mean is, can TabView only handle a limited number of items?

If you are referring to the number of Tab Bar items you can have then I don’t know but if you have a ridiculous number of tab items then I would think that you should be redesigning the App.

As far as I can tell the maximum number of tab items you have visible in the Tab Bar is 5 as per this screenshot.

If you have more than 5 then you get a More button which opens to a List View containing the additional options.

Is that what you are getting at?

1 Like

It would help if you indicated on which line you are getting the error. I don’t see anything obvious that would cause and index of out range error.

There is a modifier of TabView which is
.tabViewStyle(PageTabViewStyle())
It will create an effect like slideshow, this is what it look like:

image
or
https://swiftontap.com/pagetabviewstyle

What I want to create is a slideshow like this, but the content in each card is from a separated Firestore Document

(This sample photo is captured from another prototype I created, which are using TabView with.tabViewStyle(PageTabViewStyle()) but the data of each card is from an local array)

Here is the screen cap

And I pushed my project to GitHub

There are some other View in my project,

As you can see there are 2 tab item in the main view, one view is “Content”, which is the view in the screen cap, another is “CardList” which should show my slideshow with data from Firestore

My app can run (and all the Firebase Auth, Sign In with Apple, set data, fetch data works well)
until I click the “CardList” tab, the bug occur:
“Thread 1: Fatal error: Index out of range”

When I use VStack ( but not tabView with .tabViewStyle(PageTabViewStyle()) ) to show data from Firestore in the CardListView, there are no bug will occur, the CardListView can show, the VStack and data from Firestore can show

I can’t find related information, but in the prototype, The Firestore query will just return 5 Document, so it shouldn’t exceed the limit if there are any

I’m just throwing out ideas here. It looks like you have a TabView inside a TabView, which might be okay, but I’m wondering if a horizontal ScrollView might solve the problem.

I uploaded my project to GitHub
Hopes that can better explain my idea

I was having a hard time to study how to implement gitignore into an already pushed project with file that should be ignored.

At last I just manually copied all the .swift file to a new Xcode project, and ignore all the firebase related files. I hope it doesn’t add the difficulties to read the code, because I can’t find a better way to show the code

I had this same issue, and after some investigation I’ve found the root cause of the problem. But it has nothing to do with nested TabViews or the ForEach.

Problem?

The main problem here is that the initial value of Cards is []. So when the view is first rendered, the ForEach returns an empty view. But unfortunately a page tab view crashes when it’s empty. Try running this and see what happens.

TabView {
    EmptyView()
}
.tabViewStyle(.page)

Solution

I’ve submitted a report to Apple, so (hopefully) they will fix that, but in the meantime, here’s a simple workaround. Just make sure not to give an empty view to the TabView.

TabView {
    if items.isEmpty {
        // EmptyView won't work here because it's literally empty
        Spacer()
    } else {
        // ForEach and stuff
    }
}
.tabViewStyle(.page)

It’s up to the Developer to handle the situation when an array is likely to be empty and display an appropriate message under those circumstances. Allowing the result of code to return no view at all is really not good programming practices in my view.

Oh if you mean that spacer, it’s just a placeholder thing I put in there. Not for production. In my app, I used a ProgressView at that place to indicate that it’s communicating with the server. Sorry about the confusion

It’s not so much that but what I am getting at is that you have submitted a report to Apple saying that there is an issue with the TabView when there is an empty view. A programmer should never allow an empty view to occur if all steps have been taken to trap those circumstances.

From my point of view this:

TabView {
    EmptyView()
}
.tabViewStyle(.page)

does not constitute an issue since you have deliberately given a TabView an empty view so it will crash.