Search code examples
swiftswiftuiswiftui-tabview

TabView and Timer is not working for automated carousel in swiftUI


I am trying to have a carousel in SwiftUI which automatically sliding to the next page. I implemented the solution below:

struct CarouselView: View {
    let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
    
    @State var selection = 0
    
    let carouselViews: Array<WelcomeImageModel> =[....]
    
    var body: some View {
        VStack {
            TabView(selection: $selection) {
                ForEach(carouselViews) { view in
                    ZStack {
                        Image(view.image)
                            .resizable()
                            .aspectRatio(contentMode: .fill)
                            .ignoresSafeArea()
                        VStack {
                            Text(view.title)
                 
                            Text(view.description)
                               
                        }
                    }
                }
            }
            .tabViewStyle(PageTabViewStyle())
            .indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
            .onReceive(timer, perform: { _ in
                withAnimation{
                    selection = selection < 5 ? selection + 1 : 0
                }
            })
            
        }
        .ignoresSafeArea()
    }
}

The WelcomeImageModel is just a struct to better handle the data, and it's not relevent here but there is 5 elements in it.

It seems that the selection is not updating properly. If I manually slide, it came back to 0 automatically. any idea why selection is not properly increasing.

Thanks


Solution

  • The tag of your tabs probably doesn't match your selection! Your tabs are automatically tagged with the ids of your WelcomeImageModels, by the ForEach, and not 0 to 5 as your selection expects.

    An easy solution is to change the ForEach to:

    ForEach(carouselViews.indices, id: \.self) { i in
        // use carouselViews[i] in here...
    }
    

    This will tag each tab with the index of tab.

    Alternatively, add a index property in WelcomeImageModel.

    struct WelcomeImageModel: Identifiable {
        let image: String
        let title: String
        let description: String
        let index: Int // add this
    
        // I'm not sure how you implemented id
    }
    

    Give the models the indices 0 to 4, in the order that they appear.

    let carouselViews: [WelcomeImageModel] = [
        .init(...., index: 0),
        .init(...., index: 1),
        .init(...., index: 2),
        .init(...., index: 3),
        .init(...., index: 4),
    ]
    

    Then you can do:

    ForEach(carouselViews) { view in
        ZStack {
            // ...    
        }.tag(view.index)
    }