0

I have written a generic ViewPager with TabView and it works perfectly. However, I want to pause the timer (auto swipe) when user starts dragging and resume it when user finishes the dragging. Is there anyway to do that?

This is my ViewPager:

struct ViewPager<Data, Content> : View
where Data : RandomAccessCollection, Data.Element : Identifiable, Content : View {

private var timer = Timer.publish(every: 2, on: .main, in: .common).autoconnect()

@Binding var currentIndex: Int

private let data: [Data.Element]
private let content: (Data.Element) -> Content
private let isTimerEnabled: Bool
private let showIndicator: PageTabViewStyle.IndexDisplayMode

init(_ data: Data,
     currentIndex: Binding<Int>,
     isTimerEnabled: Bool = false,
     showIndicator: PageTabViewStyle.IndexDisplayMode = .never,
     @ViewBuilder content: @escaping (Data.Element) -> Content) {
    
    _currentIndex = currentIndex
    self.data = data.map { $0 }
    self.content = content
    self.isTimerEnabled = isTimerEnabled
    self.showIndicator = showIndicator
}

private var totalCount: Int {
    data.count
}

var body: some View {
    TabView(selection: $currentIndex) {
        ForEach(data) { item in
            self.content(item)
                .tag(item.id)
        }
    }.tabViewStyle(PageTabViewStyle(indexDisplayMode: showIndicator))
        .onReceive(timer) { _ in
            if !isTimerEnabled {
                timer.upstream.connect().cancel()
            } else {
                withAnimation {
                    currentIndex = currentIndex < (totalCount - 1) ? currentIndex + 1 : 0
                }
            }
            
        }
    }
}
BabakHSL
  • 622
  • 1
  • 8
  • 18
  • if you only want to "pause" while user is dragging, you can exchange `in: .common` with `in: .default` in the Timer. But what you probably also want is setting the timer back to 2 secs once the dragging is over ... and I didn't figure this out yet. I managed to cancel the timer `.onChange(of: currentIndex)` but I don't know how to "reset" without cancelling. – ChrisR Feb 01 '22 at 21:15
  • pause or stop the timer doesn't matter to me. I just want to do it when user drags the pages and resume or start a new timer when dragging is finished. I don't know how to do manage the dragging part. – BabakHSL Feb 02 '22 at 06:43
  • look at my code, it does what you want. – ChrisR Feb 02 '22 at 08:52
  • The thing is I want to manage the timer inside the struct but your code works with a global timer. – BabakHSL Feb 02 '22 at 09:13
  • then just do one thing in your code: exchange `in: .common` with `in: .default` in the Timer setup. – ChrisR Feb 02 '22 at 09:31
  • when I do that my tabview animation gets messed up. .default makes pages pop instead of swiping. – BabakHSL Feb 02 '22 at 12:11
  • works perfectly with me. – ChrisR Feb 03 '22 at 08:15

2 Answers2

0

To "pause" while user is dragging, you can exchange .common with .default in the Timer. But what you probably also want is setting the timer back to 2 secs once the dragging is over ...

I got this to work but I use a global var, so the timer stays around and this feels wrong – can someone help further?

// global var – this seems wrong, but works
var timer = Timer.publish(every: 2, on: .main, in: .default).autoconnect()


struct ViewPager<Data, Content> : View
where Data : RandomAccessCollection, Data.Element : Identifiable, Content : View {
    
    
    @Binding var currentIndex: Int
    
    private let data: [Data.Element]
    private let content: (Data.Element) -> Content
    private let isTimerEnabled: Bool
    private let showIndicator: PageTabViewStyle.IndexDisplayMode
    
    init(_ data: Data,
         currentIndex: Binding<Int>,
         isTimerEnabled: Bool = false,
         showIndicator: PageTabViewStyle.IndexDisplayMode = .never,
         @ViewBuilder content: @escaping (Data.Element) -> Content) {
        
        _currentIndex = currentIndex
        self.data = data.map { $0 }
        self.content = content
        self.isTimerEnabled = isTimerEnabled
        self.showIndicator = showIndicator
    }
    
    private var totalCount: Int {
        data.count
    }
    
    var body: some View {
        TabView(selection: $currentIndex) {
            ForEach(data) { item in
                self.content(item)
                    .tag(item.id)
            }
        }
        .tabViewStyle(PageTabViewStyle(indexDisplayMode: showIndicator))
        
        .onReceive(timer) { _ in
            if !isTimerEnabled {
                timer.upstream.connect().cancel()
            } else {
                print("received")
                withAnimation {
                    currentIndex = currentIndex < (totalCount - 1) ? currentIndex + 1 : 0
                    print(currentIndex)
                }
            }
        }
        
        .onChange(of: currentIndex) { _ in
            timer = Timer.publish(every: 2, on: .main, in: .default).autoconnect()
        }
        
    }
}
ChrisR
  • 9,523
  • 1
  • 8
  • 26
0

same principle but used selection for TabView (and tag for view) and timer as not global var

struct MotivationTabView: View {
// MARK: - PROPERTIES
@State private var selectedItem = "Adolf Dobr’aňskŷj"
@State private var isTimerEnabled: Bool = true
@State private var timer = Timer.publish(every: 10, on: .main, in: .default).autoconnect()

let items: KeyValuePairs = ["Adolf Dobr’aňskŷj": "Svij narod treba ľubyty i ne haňbyty s’a za ňoho!",
                            "Fjodor Mychailovič Dostojevskŷj": "Chto ne maje narod, tot ne maje any Boha! Buďte sobi istŷ, že všŷtkŷ totŷ, što perestanuť rozumity svomu narodu i stračajuť z nym perevjazaňa, stračajuť jednočasno viru otc’ovsku, stavajuť buď ateistamy, abo cholodnŷma.",
                            "Pau del Rosso": "Je barz važnŷm uchovaty sobi vlastnu identičnosť. To naš unikatnŷj dar pro druhŷch, unikatnŷj v cilim kozmosi.",
                            "Viktor Hugo": "Velykosť naroda ne mir’ať s’a kiľkosťov, tak jak i velykosť čolovika ne mir’ať s’a vŷškov.",
                            "Lewis Lapham":"Strata identitŷ je vŷhoda pro biznis... pokľa bŷ jem znav, chto jem, čom bŷ jem si bezprestajno kupovav novŷ značkŷ vodŷ po holiňu?"]

private var totalCount: Int {
    items.count
}

private func nextItem(currItem: String) -> String{
    switch currItem {
    case "Adolf Dobr’aňskŷj": return "Fjodor Mychailovič Dostojevskŷj"
    case "Fjodor Mychailovič Dostojevskŷj": return "Pau del Rosso"
    case "Pau del Rosso": return "Viktor Hugo"
    case "Viktor Hugo": return "Lewis Lapham"
    default: return  "Adolf Dobr’aňskŷj"
    }
}


// MARK: - BODY
var body: some View {
    GroupBox {
        TabView(selection: $selectedItem){
            ForEach(items, id: \.self.key) { item in
                VStack(alignment: .trailing){
                    Text(item.value)
                    Text(item.key)
                        .font(.caption)
                        .padding(.top, 5)
                    
                }.tag(item.key)
            }
        } //: TABS
        .tabViewStyle(PageTabViewStyle())
        .frame(height: 240)
        .onReceive(timer) { _ in
            if !isTimerEnabled {
                timer.upstream.connect().cancel()
            } else {
                print("received")
                withAnimation() {
                    selectedItem = nextItem(currItem: selectedItem)
                    print(selectedItem)
                }
            }
        }
        .onAppear{
            isTimerEnabled = true
            timer = Timer.publish(every: 10, on: .main, in: .default).autoconnect()
        }
        .onDisappear{
            isTimerEnabled = false
        }
    } //: BOX
}
Peter
  • 260
  • 5
  • 3