0

I have a tabview (page style) that I am using to create an automatic slideshow. Each slide is not assigned to the currentIndex for some odd reason. Neither does the timer work or manually switching tabs with the default dot controls given with tabview.

If i set the tag to selectedIndex the tabview will default to the first slide. No errors to the console. Any help is greatly appreciated!

struct HomeView: View {
    @EnvironmentObject private var hvm: HomeViewModel
    
    @State private var selectedFilter: String = "Popular"
    @State private var selectedMedia: MediaModelResult? = nil
    @State private var showDetailView: Bool = false
    @State private var currentIndex: Int = 0
    @State var isFilterSelected: Bool = true
    
    @Namespace var animation
    
    private let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
    
    var body: some View {
        NavigationView {
            ZStack {
                Color.theme.background.ignoresSafeArea()
                VStack {
                    header
                    VStack(alignment: .leading, spacing: 15) {
                        Text("Upcoming Movies")
                            .foregroundColor(Color.theme.secondaryAccent)
                            .font(.subheadline)
                            .padding(.leading, 15)
                        TabView(selection: $currentIndex) {
                            ForEach(hvm.upcomingFilms) { film in
                                MediaImageView(mediaPath: film, poster: false)
                                    .scaledToFill()
                                    .tag(film)
                            }
                        }
                        .tabViewStyle(PageTabViewStyle())
                        .clipShape(RoundedRectangle(cornerRadius: 5))
                        .padding(.horizontal, 15)
                        .frame(width: UIScreen.main.bounds.width, height: 180)
                        .onReceive(timer, perform: { _ in
                            withAnimation(.default) {
                                currentIndex = currentIndex < hvm.upcomingFilms.count ? currentIndex + 1 : 0
                            }
                        })
                    }
                    scrollViewContent
                }
            }
            .preferredColorScheme(.dark)
            .navigationBarHidden(true)
        }
    }
}
struct MediaImageView: View {
    @StateObject var mivm: MediaImageViewModel
    
    var poster: Bool
    
    init(mediaPath: MediaModelResult, poster: Bool) {
        self.poster = poster
        _mivm = StateObject(wrappedValue: MediaImageViewModel(mediaPath: mediaPath))
    }
    
    var body: some View {
        if poster {
            ZStack {
                if let image = mivm.poster {
                    Image(uiImage: image)
                        .resizable()
                        .scaledToFit()
                } else if mivm.isLoading {
                    ProgressView()
                }
            }
        } else {
            ZStack {
                if let image = mivm.backdrop {
                    Image(uiImage: image)
                        .resizable()
                } else if mivm.isLoading {
                    ProgressView()
                }
            }
        }
    }
}
class HomeViewModel: ObservableObject {
    @Published var tabBarImageNames = ["house", "rectangle.stack", "clock.arrow.circlepath", "magnifyingglass"]
    @Published var filterTitles = ["Popular Now", "Top Rated", "New"]
    @Published var popularFilms: [MediaModelResult] = []
    @Published var topRatedFilms: [MediaModelResult] = []
    @Published var upcomingFilms: [MediaModelResult] = []
    @Published var popularTV: [MediaModelResult] = []
    @Published var topRatedTV: [MediaModelResult] = []
    
    private let dataService = MediaDataService()
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        addSubscribers()
    }
    
    func addSubscribers() {
        dataService.$popularFilms
            .sink { [weak self] (returnedFilms) in
                self?.popularFilms = returnedFilms
            }
            .store(in: &cancellables)
        dataService.$topRatedFilms
            .sink { [weak self] (returnedFilms) in
                self?.topRatedFilms = returnedFilms
            }
            .store(in: &cancellables)
        dataService.$upcomingFilms
            .sink { [weak self] (returnedFilms) in
                self?.upcomingFilms = returnedFilms
            }
            .store(in: &cancellables)
        dataService.$popularTV
            .sink { [weak self] (returnedFilms) in
                self?.popularTV = returnedFilms
            }
            .store(in: &cancellables)
        dataService.$topRatedTV
            .sink { [weak self] (returnedFilms) in
                self?.topRatedTV = returnedFilms
            }
            .store(in: &cancellables)
    }
}
Trevor
  • 580
  • 5
  • 16
  • Welcome to Stack Overflow. While I am glad you were able to resolve your problem, I would recommend reviewing [How do I ask a good question?](https://stackoverflow.com/help/how-to-ask) and [How to create a Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example). Also, you will often find that making a minimal, reproducible example makes it simple for you to solve your problem before actually posting. – Yrb Aug 18 '21 at 20:38

1 Answers1

0

Solution:

TabView(selection: $currentIndex) {
    ForEach(0..<hvm.upcomingFilms.count) { film in
       MediaImageView(mediaPath: hvm.upcomingFilms[film], poster: false)
           .scaledToFill()
    }
}
Trevor
  • 580
  • 5
  • 16
  • You are better off refactoring your logic out of the view and into the view model. The view also should not _imperatively_ read values and display them when it "thinks" it may do so, but only when the view model says so. So, your timer goes to the view model. Your index calculation is in the view model. When everything is composed to be rendered, set the view state and the view _then_ just renders it as desired. When the user performs an action, don't handle it in the view, instead send it to the view model where it will be processed. – CouchDeveloper Aug 16 '21 at 22:37
  • @CouchDeveloper I actually moved most of those things after solving the problem, but I did not show that in my answer to keep the answer as simple as possible. But your absolutely right, the view should not decide what to display only when. Im glad you commented this on the answer so others can read this and understand proper mvvm. – Trevor Aug 17 '21 at 06:33